상태 객체의 내부 상태가 변경될 때 객체의 동작을 변경할 수 있는 behavioral pattern 이다.
이 패턴은 상태 관련 동작을 별도의 상태 클래스로 추출하고 원본 객체가 스스로 행동하는 대신, 작업을 추출한 상태 클래스의 인스턴스에 위임하도록 강제한다.
상태 패턴은 유한 상태 기계의 개념과 밀접한 관련이 있다.
유한 상태 기계
요점은 어떤 주어진 순간에 프로글매이 있을 수 있는 한정된 수의 상태가 있다는 것이다. 어떤 고유한 상태에서 프로그램은 다르게 동작하며, 프로그램은 한 상태에서 다른 상태로 즉시 전환될 수 있다. 그러나 현재 상태에 따라 프로그램이 다른 특정 상태로 전환되거나 전환되지 않을 수 있다. 이 전환이라고 불리는 규칙도 유한하고 미리 정해져 있다.
이 방법을 객체에 적용할 수도 있다. 문서 Class가 있다고 가정해보자. 문서는 초안, 조정 및 게시됨의 세 가지 상태 중 하나일 수 있다. 문서의 게시 방법은 각 상태마다 조금씩 다르다.
초안에서 문서를 조정 상태로 이동한다.
조정 상태에서는 문서를 공갱하지만, 현재 사용자가 관리자인 경우에만 공개한다.
게시됨의 상태에서는 아무것도 하지 않는다.
문서 객체의 가능한 상태 및 전환
상태 기계는 일반적으로 객체의 현재 상태에 따라 적절한 동작을 선택하는 많은 조건부 연산자와 함께 구현된다. 이러한 조건 기반 상태 기계의 가장 큰 약점은 Document
라는 클래스에 상태 및 상태 의존적 동작을 점점 더 추가하기 시작하면 드러난다.
대부분의 메서드는 현재 상태에 따라 메서드의 적절한 동작을 선택하는 괴물 조건을 포함하는데, 이와 같은 코드는 변환 논리의 변경 시 모든 메서드에서 상태 조건을 변경해야 하므로 유지하기가 매우 어렵다.
프로젝트가 진행될수록 문제는 더 커지는 경향이 있다. 설계 단계에서 가능한 모든 상태와 전환을 예측하는 것은 어렵다. 따라서 제한된 조건 집합으로 구축된 간결한 상태 기계는 시간이 지남에 따라 엄청나게 복잡한 기계로 바뀔 수 있다.
상태 패턴은 객체의 가능한 모든 상태에 대해 새 클래스를 만들고 이러한 클래스에 모든 상태별 동작을 추출하는 것을 제안한다.
모든 동작을 스스로 구현하는 대신, 컨텍스트라고 불리는 원래 객체는 현재 상태를 나타내는 상태 객체 중 하나에 대한 참조를 저장하고 모든 상태 관련 작업을 해당 객체에 위임한다.
작업을 상태 객체에 위임하는 문서
컨텍스트를 다른 상태로 변환하려면 활성 상태 객체를 해당 새 상태를 나타내는 다른 객체로 바꾼다. 모든 상태 클래스가 동일한 인터페이스를 따르고 컨텍스트 자체가 해당 인터페이스를 통해 이러한 객체와 함께 작동하는 경우에만 가능하다.
이 구조는 Strategy 패턴과 비슷해 보일 수 있지만 상태 패턴에서 특정 상태는 서로를 인식하고 한 상태에서 다른 상태로 전환을 시작하는 반면, 전략은 서로에 대해서 거의 알지 못한다.
스마트폰의 버튼 및 스위치는 단말기의 현재 상태에 따라 다르게 작동한다.
컨텍스트는 상태 객체 중 하나에 대한 참조를 저장하고 모든 상태별 작업을 위임한다. 컨텍스트는 상태 인터페이스를 통해 상태 객체와 통신한다. 컨텍스트는 새 상태 객체를 전달하기 위해 세터를 노출한다.
상태 인터페이스는 상태별 메서드를 선언한다. 이러한 메서드들은 구체적인 상태에 대해 적용될 수 있는 것들이어야 한다.
구체적 상태는 상태별 메서드를 위한 자체 구현을 제공한다. 여러 상태에서 유사한 코드가 중복되지 않도록 일반적인 동작을 캡슐화하는 중간 추상 클래스를 제공할 수 있다.
상태 객체는 컨텍스트 객체에 대한 백레퍼런스를 저장할 수 있다. 이 참조를 통해 상태는 상태 전환을 시작할 뿐만 아니라 컨텍스트 개체로부터 필요한 모든 정보를 가져올 수 있다.
컨텍스트와 구체적인 상태 모두 컨텍스트의 다음 상태를 설정하고 컨텍스트에 연결된 상태 객체를 대체하여 실제 상태 전환을 수행할 수 있다.
현재 상태에 따라 다르게 동작하는 객체가 있고 상태 수가 엄청나며 상태별 코드가 자주 변경되는 경우 상태 패턴을 사용한다.
클래스의 현재 필드 값에 따라 클래스의 동작 방식을 변경하는 대규모 조건으로 오염된 클래스가 있는 경우 패턴을 사용하라
조건 기반 상태 시스템의 유사한 상태 및 전환에 중복 코드가 많을 경우 상태를 사용한다.
SRP
OCP
부피가 큰 상태 기계 조건을 제거하여 컨텍스트의 코드를 단순화한다.
복잡도: ★☆☆
인기: ★★☆
사용 예: 상태 패턴은 일반적으로 TS에서 대량의 스위치 베이스 상태 머신을 객체로 변환하는 데 사용된다.
식별: 상태 패턴은 외부에서 제어되는 객체의 상태에 따라 동작을 바꾸는 방법으로 인식될 수 있다.
index.ts
// 컨텍스트는 클라이언트의 관심 인터페이스를 정의한다. 또한 컨텍스트의 현재 상태를 나타내는
// 하위 클래스의 인스턴스에 대한 참조를 유지한다.
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();
결과
상태 패턴은 내부 상태에 따른 객체의 동작을 변경할 수 있는 동작설계 패턴이다.
이는 일반적으로 조건문에 따른 분기처리 코드가 프로그램의 부피가 커짐에 따라 오염될 가능성을 막아주고 구체적인 상태에 따른 메서드들을 자체구현하여 Context의 상태가 변화함에 따라 사용할 수 있는 메서드들을 다르게 할 수 있다.