상태 패턴
상태 패턴(State Pattern)은 객체의 상태를 클래스로 분리하고, 상태가 변할 때 객체의 동작을 변경할 수 있도록 해 조건문을 사용하지 않고도 상태 전환을 관리할 수 있다. 이를 통해 코드의 복잡도를 낮추고 유지보수성을 높일 수 있으며, 새로운 상태를 추가할 때 기존 코드에 영향을 최소화할 수 있다. 또한, 각 상태를 독립적인 클래스로 분리함으로써 상태별 동작을 명확하게 정의할 수 있어 객체의 책임이 분리되고 코드의 가독성이 향상된다.
테이블 조회 방법은 상태 전이 규칙을 데이터로 관리하기 때문에 새로운 상태를 추가할 때 기존 로직을 수정하지 않아도 된다는 장점이 있지만, 상태별 동작이 복잡해질 경우 테이블만으로 이를 표현하기 어려울 수 있다. 반면, 상태 패턴은 각 상태를 개별 클래스로 구현하므로 상태별 동작을 직접 정의할 수 있어 코드의 응집도가 높아지고, 객체 지향적 설계를 통해 확장성을 확보할 수 있다.
그러나 상태 패턴은 클래스 수가 증가하여 코드가 복잡해질 수 있으며, 단순한 상태 전이를 구현하는 경우에는 오히려 과도한 구조가 될 수도 있다. 따라서, 상태 패턴은 상태별 동작이 복잡하고 확장 가능성이 높은 시스템에서 적합하며, 상태 수가 적고 단순한 경우에는 테이블 조회 방법이 더 효과적일 수 있다.
아래는 지금 읽고 있는 책 <디자인 패턴의 아름다움>에서 상태 패턴을 설명할 때 들었던 예시 코드를 자바에서 타입스크립트로 수정한 것이다. 자바 코드를 타입스크립트로 옮기는 행위만으로도 꽤 많은 걸 이해할 수 있었다.
enum State {
SMALL,
SUPER,
CAPE,
FIRE
}
interface IMario {
getName(): State;
obtainMushRoom(stateMachine: MarioStateMachine): void;
obtainCape(stateMachine: MarioStateMachine): void;
obtainFireFlower(stateMachine: MarioStateMachine): void;
meetMonster(stateMachine: MarioStateMachine): void;
}
class SmallMario implements IMario {
private static instance: SmallMario = new SmallMario();
private constructor() {}
public static getInstance(): SmallMario {
return SmallMario.instance;
}
public getName(): State {
return State.SMALL;
}
public obtainMushRoom(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(SuperMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 100);
}
public obtainCape(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(CapeMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 200);
}
public obtainFireFlower(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(FireMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 300);
}
public meetMonster(stateMachine: MarioStateMachine): void {
// 여기서는 아무 일도 하지 않으므로 빈 함수
}
}
// SuperMario, CapeMario, FireMario 클래스 코드 생략
class SuperMario implements IMario {
private static instance: SuperMario = new SuperMario();
private constructor() {}
public static getInstance(): SuperMario {
return SuperMario.instance;
}
public getName(): State {
return State.SUPER;
}
public obtainMushRoom(stateMachine: MarioStateMachine): void {
// 이미 SuperMario 상태이므로 아무 일도 하지 않음
}
public obtainCape(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(CapeMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 200);
}
public obtainFireFlower(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(FireMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 300);
}
public meetMonster(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(SmallMario.getInstance());
stateMachine.setScore(stateMachine.getScore() - 100);
}
}
class CapeMario implements IMario {
private static instance: CapeMario = new CapeMario();
private constructor() {}
public static getInstance(): CapeMario {
return CapeMario.instance;
}
public getName(): State {
return State.CAPE;
}
public obtainMushRoom(stateMachine: MarioStateMachine): void {
// 이미 CapeMario 상태이므로 아무 일도 하지 않음
}
public obtainCape(stateMachine: MarioStateMachine): void {
// 이미 CapeMario 상태이므로 아무 일도 하지 않음
}
public obtainFireFlower(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(FireMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 300);
}
public meetMonster(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(SmallMario.getInstance());
stateMachine.setScore(stateMachine.getScore() - 200);
}
}
class FireMario implements IMario {
private static instance: FireMario = new FireMario();
private constructor() {}
public static getInstance(): FireMario {
return FireMario.instance;
}
public getName(): State {
return State.FIRE;
}
public obtainMushRoom(stateMachine: MarioStateMachine): void {
// 이미 FireMario 상태이므로 아무 일도 하지 않음
}
public obtainCape(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(CapeMario.getInstance());
stateMachine.setScore(stateMachine.getScore() + 200);
}
public obtainFireFlower(stateMachine: MarioStateMachine): void {
// 이미 FireMario 상태이므로 아무 일도 하지 않음
}
public meetMonster(stateMachine: MarioStateMachine): void {
stateMachine.setCurrentState(SmallMario.getInstance());
stateMachine.setScore(stateMachine.getScore() - 300);
}
}
class MarioStateMachine {
private score: number;
private currentState: IMario;
constructor() {
this.score = 0;
this.currentState = SmallMario.getInstance();
}
public obtainMushRoom(): void {
this.currentState.obtainMushRoom(this);
}
public obtainCape(): void {
this.currentState.obtainCape(this);
}
public obtainFireFlower(): void {
this.currentState.obtainFireFlower(this);
}
public meetMonster(): void {
this.currentState.meetMonster(this);
}
public getScore(): number {
return this.score;
}
public getCurrentState(): State {
return this.currentState.getName();
}
public setScore(score: number): void {
this.score = score;
}
public setCurrentState(currentState: IMario): void {
this.currentState = currentState;
}
}
// 사용 예제
class Application {
public static main(): void {
const stateMachine = new MarioStateMachine();
stateMachine.obtainMushRoom();
stateMachine.obtainCape();
stateMachine.obtainFireFlower();
stateMachine.meetMonster();
console.log(`Score: ${stateMachine.getScore()}`);
console.log(`Current State: ${State[stateMachine.getCurrentState()]}`);
}
}
Application.main();
블로그의 정보
Ayden's journal
Beard Weard Ayden