깃허브 코드
컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공
두개의 식당이 합병하는데
한쪽은 메뉴를 List
로 한쪽은 Array
로 관리한다.
하지만 두 식당 모두 메뉴 코드를 건드리는 것을 반대한다.
각 메뉴에 반복자 패턴 인터페이스를 구현해보자.
public class PancakeHouseMenu {
List<MenuItem> menuItemList;
public PancakeHouseMenu() {
menuItemList = new ArrayList<>();
addItem("K&B 팬케이크 세트", "스크램블 에그와 토스트가 곁들여진 팬케이크", true, 2.99);
addItem("레귤러 팬케이크 세트", "달걀 후라이와 소시지가 곁들여진", false, 2.99);
addItem("블루베리 팬케이크 세트", "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크", true, 3.49);
addItem("와플", "취향에 따라 블루베리나 딸기 추가 와플", true, 3.59);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItemList.add(menuItem);
}
public List<MenuItem> getMenuItemList() {
return menuItemList;
}
}
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("채식주의자용 BLT", "통밀 위에 베이컨, 상추, 토마토 얹은 메뉴", false, 2.99);
addItem("BLT", "통밀 위에 상추, 토마토 얹은 메뉴", false, 2.99);
addItem("오늘의 수프", "감자 샐러드를 곁들인 오늘의 스프", false, 3.29);
addItem("핫도그", "사워크림, 양념, 양파, 치즈가 곁들여진 핫도그", false, 3.05);
}
public void addItem(String itemName, String description,
boolean vegan, double price) {
MenuItem menuItem = new MenuItem(itemName, description, vegan, price);
if (numberOfItems >= MAX_ITEMS) {
System.out.println("메뉴가 꽉차서 더 추가할 수 없습니다.");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
}
public interface Iterator {
boolean hasNext();
MenuItem next();
}
public class DinerMenuIterator implements Iterator {
MenuItem[] menuItems;
int position = 0;
public DinerMenuIterator(MenuItem[] menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
return position < menuItems.length && menuItems[position] != null;
}
@Override
public MenuItem next() {
MenuItem menuItem = menuItems[position];
position = position + 1;
return menuItem;
}
}
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("채식주의자용 BLT", "통밀 위에 베이컨, 상추, 토마토 얹은 메뉴", false, 2.99);
addItem("BLT", "통밀 위에 상추, 토마토 얹은 메뉴", false, 2.99);
addItem("오늘의 수프", "감자 샐러드를 곁들인 오늘의 스프", false, 3.29);
addItem("핫도그", "사워크림, 양념, 양파, 치즈가 곁들여진 핫도그", false, 3.05);
}
public void addItem(String itemName, String description,
boolean vegan, double price) {
MenuItem menuItem = new MenuItem(itemName, description, vegan, price);
if (numberOfItems >= MAX_ITEMS) {
System.out.println("메뉴가 꽉차서 더 추가할 수 없습니다.");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
public Iterator createIterator() {
return new DinerMenuIterator(menuItems);
}
}
public class Waitress {
PancakeHouseMenu pancakeHouseMenu;
DinerMenu dinerMenu;
public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator pancakeHouseMenuIterator = pancakeHouseMenu.createIterator();
Iterator dinerMenuIterator = dinerMenu.createIterator();
System.out.println("------아침 메뉴------");
printMenu(pancakeHouseMenuIterator);
System.out.println("------점심 메뉴------");
printMenu(dinerMenuIterator);
}
private void printMenu(Iterator menuIterator) {
while (menuIterator.hasNext()) {
MenuItem menuItem = menuIterator.next();
System.out.println(menuItem.getName());
System.out.println(menuItem.getDescription());
System.out.println(menuItem.getVegetarian());
System.out.println(menuItem.getPrice());
}
}
}
public class Main {
public static void main(String[] args) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
결과
깃허브 코드
객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다.
마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다.
예전에 요구사항으로 만들어 둔 동전 뽑기 기계에서
10분의 1 확률로 두개가 뽑힐 수 있게 만들어 달라는 요청
기존의 코드를 보면
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}
public void insertQuarter() {
if (state == HAS_QUARTER) {
// ...
} else if (state == SOLD) {
// ...
} else if (state == NO_QUARTER) {
// ...
} else if (state == SOLD_OUT) {
// ...
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
// ...
} else if (state == SOLD) {
// ...
} else if (state == NO_QUARTER) {
// ...
} else if (state == SOLD_OUT) {
// ...
}
}
public void turnCrank() {
if (state == HAS_QUARTER) {
// ...
} else if (state == SOLD) {
// ...
} else if (state == NO_QUARTER) {
// ...
} else if (state == SOLD_OUT) {
// ...
}
}
public void dispense() {
if (state == HAS_QUARTER) {
// ...
} else if (state == SOLD) {
// ...
} else if (state == NO_QUARTER) {
// ...
} else if (state == SOLD_OUT) {
// ...
}
}
}
각 상황별로 모든 상태에 따른 조건 처리를해서 구현한 이전 코드
상태 패턴으로 리팩토링
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("동전을 넣으셨습니다.");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("동전을 넣어주세요.");
}
@Override
public void turnCrank() {
System.out.println("동전을 넣어주세요.");
}
@Override
public void dispense() {
System.out.println("동전을 넣어주세요.");
}
}
GumballMachine
수정
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
State state;
int count = 0;
public GumballMachine(int numberGumballs) {
this.soldOutState = new SoldOutState(this);
this.noQuarterState = new NoQuarterState(this);
this.hasQuarterState = new HasQuarterState(this);
this.soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("알맹이를 내보내고 있습니다.");
if (count > 0) {
count = count - 1;
}
}
public State getSoldOutState() {
return soldOutState;
}
public void setSoldOutState(State soldOutState) {
this.soldOutState = soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public void setNoQuarterState(State noQuarterState) {
this.noQuarterState = noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public void setHasQuarterState(State hasQuarterState) {
this.hasQuarterState = hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public void setSoldState(State soldState) {
this.soldState = soldState;
}
public int getCount() {
return count;
}
}
깃허브 코드
특정 객체로의 접근을 제어하는 대리인을 제공합니다.
프록시 패턴을 사용하면 원격 객체라든가 생성하기 힘든 객체, 보안이 중요한 객체와 같은 다른 객체로의 접근을 제어하는 대리인 객체를 만들 수 있습니다.
마을 사람들의 제 착을 찾아주는 데이팅 서비스를 만들려고 합니다.
상대방의 괴짜 지수를 매기는 기능을 더하려고 합니다.
누군가가 타인의 관심 사항을 바꾸고, 본인의 자기 선호도 점수를 조작하는걸 막아주세요
public interface Person {
String getName();
String getGender();
String getInterests();
int getGeekRating();
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setGeekRating(int geekRating);
}
public class PersonImpl implements Person {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
@Override
public String getName() {
return name;
}
@Override
public String getGender() {
return gender;
}
@Override
public String getInterests() {
return interests;
}
@Override
public int getGeekRating() {
if (ratingCount == 0) return 0;
return rating/ratingCount;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void setInterests(String interests) {
this.interests = interests;
}
@Override
public void setGeekRating(int geekRating) {
this.rating += geekRating;
ratingCount++;
}
}
Person 인터페이스용 동적 프록시를 만들어보자
프록시로 해결할 문제를 정리해보면
- 자기 괴짜 지수를 직접 조작할 수 없어야 한다.
- 다른 사람들의 개인정보를 수정할 수 없어야 한다.
두가지 프록시를 만들어보자
하나는 자신의 객체에 접근하는 프록시, 하나는 다른 객체에 접근하는 프록시
public class OwnerInvocationHandler implements InvocationHandler {
private Person person;
public OwnerInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setGeekRating")) {
throw new IllegalAccessException("You cannot rate yourself!");
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
return null;
}
}
public class NonOwnerInvocationHandler implements InvocationHandler {
private Person person;
public NonOwnerInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setGeekRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException("You cannot modify someone else's personal information!");
}
return null;
}
}
package proxy;
import java.lang.reflect.Proxy;
public class ProxyFactory {
public static Person getOwnerProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person)
);
}
public static Person getNonOwnerProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person)
);
}
}
테스트
public class Main {
public static void main(String[] args) {
PersonImpl joe = new PersonImpl();
joe.setName("민수");
joe.setGender("남");
joe.setInterests("프로그래밍, 맛집 탐방");
// 본인 프록시 생성
Person ownerProxy = ProxyFactory.getOwnerProxy(joe);
System.out.println("이름 (OwnerProxy): " + ownerProxy.getName());
ownerProxy.setInterests("컴퓨터 게임, 윙슈트");
System.out.println("수정된 Interests: " + ownerProxy.getInterests());
try {
ownerProxy.setGeekRating(5); // 본인은 본인의 괴짜 지수를 설정할 수 없음
} catch (Exception e) {
System.out.println("본인 프록시에는 괴짜 지수를 매길 수 없습니다.");
}
// 다른 사람이 접근하는 프록시 생성
System.out.println();
Person nonOwnerProxy = ProxyFactory.getNonOwnerProxy(joe);
System.out.println("이름 (NonOwnerProxy): " + nonOwnerProxy.getName());
try {
nonOwnerProxy.setInterests("등산"); // 다른 사람의 개인정보 변경 불가
} catch (Exception e) {
System.out.println("타인 프록시에 관심 사항을 수정할 수 없습니다.");
}
nonOwnerProxy.setGeekRating(10); // 다른 사람이 괴짜 지수 평가 가능
System.out.println("괴짜 지수: " + joe.getGeekRating());
}
}
결과