public class VendingMachine {
public static enum State { NOCOIN, SELECTABLE }
private State state = State.NOCOIN;
public void insertCoin(int coin) {
switch(state) {
case NOCOIN:
increaseCoin(coin);
state = State.SELECTABLE;
break;
case SELETABLE:
increaseCoin(coin);
}
}
public void select(int productId) {
switch(state) {
case NOCOIN:
break;
case SELECTABLE:
provideProduct(productId);
decreaseCoin();
if (hasNoCoin()) {
state = State.NOCOIN;
}
}
}
... // increaseCoin, provideProduct, decreaseCoin 구현
}
case SOLDOUT:
returnCoin();
public interface State {
State increaseCoin(int coin, VendingMachine vm);
State select(int productId, VendingMachine vm);
}
public class NoCoin implements State {
@Override
public State increaseCoin(final int coin, final VendingMachine vm) {
vm.increaseCoin(coin);
if (vm.isCoinEmpty()) {
return new NoCoin();
}
return new Selectable();
}
@Override
public State select(final int productId, final VendingMachine vm) {
throw new UnsupportedOperationException();
}
}
public class Selectable implements State {
@Override
public State increaseCoin(final int coin, final VendingMachine vm) {
vm.increaseCoin(coin);
return this;
}
@Override
public State select(final int productId, final VendingMachine vm) {
vm.provideProduct(productId);
vm.decreaseByProductPrice(productId);
if (vm.isCoinEmpty()) {
return new NoCoin();
}
return this;
}
}
public class SoldOut implements State {
@Override
public State increaseCoin(final int coin, final VendingMachine vm) {
return new NoCoin();
}
@Override
public State select(final int productId, final VendingMachine vm) {
throw new UnsupportedOperationException();
}
}
public class VendingMachine {
private final static List<Product> items = new ArrayList<>();
static {
items.add(new Product(1, 100));
items.add(new Product(2, 200));
items.add(new Product(3, 300));
items.add(new Product(4, 400));
}
private State state;
private int coin; // coin을 state 내부에서 관리하도록 할 수도 있다.
public VendingMachine() {
state = StateFactory.create();
}
public void insertCoin(final int coin) {
state = state.increaseCoin(coin, this);
}
public void select(final int productId) {
state = state.select(productId, this);
}
public boolean isCoinEmpty() {
return coin == 0;
}
public void increaseCoin(final int coin) {
this.coin += coin;
}
public void decreaseByProductPrice(final int productId) {
this.coin -= items.get(productId).getPrice();
}
public Product provideProduct(final int productId) {
return items.get(productId);
}
}
단일 책임 원칙
: State 구현과 사용을 분리개방 폐쇄 원칙
: 새로운 상태가 추가 되어도 이를 사용하는 코드는 변경 필요없음.리스코프 치환 원칙
: 상태 인터페이스에 정의된 메서드를 상태 구현체가 올바른 용도로 재정의하고 있음인터페이스 분리 원칙
: 상태에 따라 사용하는 메서드와 사용하지 않는 메서드가 구분되기 때문에 제대로 지켜지지 않고 있음의존성 역전 원칙
: State 인터페이스를 만들고 사용과 구현 측면에서 모두 인터페이스를 바라보고 있으므로 잘 따르고 있다.상태 패턴은 객체의 상태에 따라 다른 동작을 구현해야할 때 사용할 수 있는 디자인 패턴이다.
전략패턴과 비슷하여 헷갈릴 수도 있지만 전략패턴은 상속의 한계를 해결하면서, 사용자가 캡슐화된 알고리즘 전략을 쉽게 바꿀 수 있도록 유연성을 제공하는 것에 초점이 맞춰져 있지만, 상태 패턴은 한 객체가 보유한 상태에 따라 동작을 다르게 수행해야 할 때 사용한다.
즉, 다시 말해 전략 패턴은 다형성의 주체가 사용자에 있지만, 상태 패턴은 다형성의 주체가 보유한 상태 객체라고 볼 수 있다.