FireDrago
[객체지향과 디자인패턴] 상태 패턴 (State Pattern) 본문
상태 패턴 (State Pattern)
상태 패턴(State Pattern)은 객체의 내부 상태에 따라 그 객체의 행동이 달라지게 하는 디자인 패턴이다.
이 패턴은 객체의 상태를 각각의 클래스로 분리하여 관리함으로써 조건문을 줄이고,
새로운 상태 추가나 상태별 동작 수정을 쉽게 만든다.
기능이 상태에따라 다르게 동작해야 할 때 상태패턴의 사용을 고려해보자
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 SELECTABLE:
increaseCoin(coin);
case SOLDOUT:
returnCoin();
}
}
public void select(int productId) {
switch(state) {
case NOCOIN:
// 아무것도 하지 않음
break;
case SELECTABLE:
provideProduct(productId);
decreaseCoin();
if (hasNoCoin) {
state = State.NOCOIN;
}
case SOLDOUT:
// 아무 것도 하지 않음
}
}
... // increaseCoin, provideProduct, decreaseCoin 구현
}
위 코드에서 자판기의 상태가 추가될수록 case 문의 개수도 증가할 것이다.
상태가 많아질 수록 복잡한 조건문이 생긴다.
상태패턴은 각 상태를 별도의 타입으로 분리하고, 각 상태별로 알맞은 하위타입으로 구성한다.
public class VendingMachine {
private State state;
public VendingMachine() {
state = new NoCoinState();
}
public void inserCoin(int coin) {
State.increaseCoin(coin, this); // 상태 객체에 위임
}
public void select(int productId) {
State.select(productId, this); // 상태 객체에 위임
}
public void changeState(State newState) {
this.state = newState;
}
... // increaseCoin, provideProduct, decreaseCoin 구현
}
public class NoCoinState implements State {
@Override
public void increaseCoin(int coin, VendingMachine vm) {
vm.increaseCoin(coin);
vm.changeState(new SelectableState());
}
@Override
public void select(int productId, VendingMachine vm) {
SoundUtil.beep();
}
}
public class SelectableState implements State {
@Override
public void increaseCoin(int coin, VendingMachine vm) {
vm.increaseCoin(coin);
}
@Override
public void select(int productId, VendingMachine vm) {
vm.provideProduct(productId);
vm.decreaseCoin();
if (vm.hasNoCoin()) {
vm.changeState(new NoCoinState());
}
}
}
VendingMachine 클래스의 상태 별 동작코드가 각 상태의 구현 클래스로 이동했다.
vendingMachine 클래스의 코드는 상태 객체에 위임하는 방식으로 단순해졌다.
상태 패턴은 새로운 상태가 추가되더라도 콘텍스트 코드가 받는 영향은 최소화된다.
콘텍스트 코드의 변경없이 새로운 상태 클래스만 추가하면 된다.
상태별 동작을 수정하기가 쉽다. 관련된 코드가 상태별 클래스 한 곳에 모여 있기 때문이다.
상태 패턴 (State Pattern) 요약
- 정의: 객체의 내부 상태에 따라 행동이 달라지는 디자인 패턴.
- 목적: 조건문을 줄이고 상태별 동작을 쉽게 수정 및 확장 가능.
- 구현: 각 상태를 별도의 클래스로 분리하여 관리.
- 장점:
- 조건문 감소로 코드 가독성 증가.
- 새로운 상태 추가 시 기존 코드에 최소한의 영향.
- 상태별 코드가 캡슐화되어 유지보수 용이.
- 적용: 기능이 상태에 따라 다르게 동작해야 할 때 사용 고려.
상태 변경은 누가?
상태패턴을 적용할 때는 콘텍스트의 상태를 누가 변경할것인지 고민해야 한다.
상태 객체에서 변경, 콘텍스트에서 변경 두가지 방법이 있는데 각각의 장단점은 다음과 같다.
상황에 맞게 상태 변경의 책임을 잘 고려하자
1. 상태 객체에서 상태를 변경하는 경우 (상태가 많고 상태 간 전환 규칙이 자주 변경)
장점 : 콘텍스트의 코드가 단순해진다. 상태 변경 규칙을 변경하거나 새로운 상태를 추가해도 콘텍스트에 영향없다.
단점 : 콘텍스트에 상태 변경을 위한 추가적인 메서드가 필요하다
상태 객체들이 서로를 직접 참조하여 결합도가 증가할수 있다.
2. 콘텍스트에서 상태를 변경하는 경우 (상태가 적고 상태 전환 규칙이 간단, 변경 가능성이 낮은 경우)
장점 : 상태 변경 규칙이 간단한 경우, 콘텍스트에서 상태를 관리함으로써 코드가 직관적이고 간결하게 유지될 수 있다.
상태 객체가 다른 상태를 직접 참조하지 않으므로, 상태 간 독립성이 유지됩니다. 결합도를 낮출 수 있다
단점 : 새로운 상태를 추가하거나 상태 전환 규칙이 변경될 때마다 콘텍스트 코드를 수정해야 한다.
확장성이 떨어지고 콘텍스트 코드가 복잡해질 수 있다.
public class VendingMachine {
private State state;
public VendingMachine() {
state = new NoCoinState();
}
public void inserCoin(int coin) {
State.increaseCoin(coin, this);
// 콘텍스트에서 상태 변경
if (hasCoin()) {
changeState(new SelectableState());
}
}
public void select(int productId) {
State.select(productId, this);
// 콘텍스트에서 상태 변경
if (state.isSelectable() && hasNoCoin()) {
changeState(new NoCoinState());
}
}
private void changeState(State state) {
this.state = newState;
}
private boolean hasCoin() {
...
}
private boolean hasNoCoin() {
return !hasCoin();
}
... // 기타 다른 기능
}
