의도
- 상태 => 객체의 내부 상태가 변경될 때 해당 객체가 행동을 변경할 수 있도록 하는 행동 디자인 패턴
문제점
- 다음 그림과 같은 문서 상태가 있다 가정해보자
- 이 상태를 관리하고자 할 경우 행동에 따라 조건문을 만들어 상태를 변경해야 할 것이다.
- 그런데 문서를 관리하는 객체가 복잡해지면 조건문 역시 매우 복잡해지는 문제가 생기게 된다.
해결책
- 객체의 상태들을 별도의 클래스로 추출하기
- 원래 객체는 행동을 자체적으로 구현하는 대신 상태 객체의 참조를 저장한 다음 상태와 관련된 작업을 해당 클래스에 위임함
- 컨텍스트를 다른 상태로 전환하려면 활성 상태 객체를 다른 객체로 바꾸면 됨
구조
class Context {
private state: State;
constructor(state: State) {
this.transitionTo(state);
}
public transitionTo(state: State): void {
console.log(`Context: Transition to ${(<any>state).constructor.name}.`);
this.state = state;
this.state.setContext(this);
}
public request1(): void {
this.state.handle1();
}
public request2(): void {
this.state.handle2();
}
}
abstract class State {
protected context: Context;
public setContext(context: Context) {
this.context = context;
}
public abstract handle1(): void;
public abstract handle2(): void;
}
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.');
console.log('ConcreteStateA wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateB());
}
public handle2(): void {
console.log('ConcreteStateA handles request2.');
}
}
class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.');
}
public handle2(): void {
console.log('ConcreteStateB handles request2.');
console.log('ConcreteStateB wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateA());
}
}
const context = new Context(new ConcreteStateA());
context.request1();
context.request2();
적용
- 상태들의 수가 많거나, 상태에 따라 행동이 달라지는 객체가 있거나, 상태별 코드가 자주 변경될 때 사용
상태별 코드를 서로 다른 클래스의 집합으로 추출해 유지보수를 편하게 할 수 있음
- 클래스가 많은 매개변수에 따라 행동이 변경될 때 사용
상태 패턴을 사용함으로써 클래스에 있는 긴 조건문과 중복 코드들을 제거할 수 있음
구현 방법
- 어떤 클래스가 컨텍스트로 작동할지 결정하기
- state 인터페이스를 선언하고 상태별로 동작을 결정하는 메서드들을 정의
- 모든 실제 상태에 대해 인터페이스에서 파생된 클래스를 만들고 상태와 관련된 모든 클래스들을 추출하기
- 컨텍스트 클래스에서 상태 인터페이스의 참조 필드와 값을 오버라이딩 할 수 있는 setter를 설정
- 컨텍스트의 메서드를 다시 확인하고 상태 조건문을 상태 객체의 해당하는 메서드들에 대한 호출들로 바꾸기
- 컨텍스트의 상태를 저장하기 위해서는 상태 클래스 중 하나의 인스턴스를 만들어 컨텍스트에 전달하기만 하면 됨
장단점
- 상태들과 관련된 코드를 별도로 분리하기 때문에 단일 책임 원칙 준수
- 상태 클래스나 컨텍스트를 변경하지 않고 새로운 상태를 도입할 수 있기 때문에 개방, 폐쇄 원칙 준수
- 거대한 상태 조건문을 제거해 컨텍스트의 코드를 단순화할 수 있음
- 상태가 단순한 경우 상태 패턴을 적용하는 것은 과도할 수 있음