- 상태 패턴(State Pattern) 소개
- 상태 패턴의 구체적인 구현(State Pattern)
- 상태 패턴의 응용 사례(State Pattern)
- 상태 패턴의 확장과 변형(State Pattern)
- 상태 패턴의 장단점과 최적의 사용 사례(State Pattern)
이번 글에서는 상태 패턴(State Pattern)이 실무에서 어떻게 응용되는지, 다양한 사례를 통해 알아보겠습니다. 상태 패턴은 객체의 상태에 따라 동작을 변경하는 특성 때문에 다양한 시나리오에서 유용하게 사용됩니다. 특히 게임 개발, 웹 애플리케이션의 사용자 인터페이스(UI) 관리, 그리고 금융 시스템에서의 트랜잭션 처리 등에서 자주 활용됩니다. 상태 패턴의 응용 사례를 통해 패턴의 유용성을 깊이 이해해 보도록 하겠습니다.
1. 상태 패턴의 기본 개념 복습
상태 패턴은 객체가 내부 상태에 따라 다른 동작을 수행하도록 하는 디자인 패턴입니다. 이 패턴의 주요 목표는 조건문을 사용하는 대신, 각 상태를 별도의 객체로 관리하여 상태 변화에 따라 유연하게 행동을 변경하는 것입니다. 상태 패턴은 특히 복잡한 상태 전환 로직이 있는 시스템에서 효과적입니다.
상태 패턴의 주요 요소:
- Context(문맥): 상태 객체를 포함하고, 상태에 따른 동작을 외부로부터 요청받아 처리하는 역할을 합니다.
- State(상태 인터페이스): 상태별로 구현해야 할 공통 행동을 정의하는 인터페이스입니다.
- ConcreteState(구체적인 상태 클래스): 상태 인터페이스를 구현하여 각 상태에 맞는 행동을 정의하는 클래스입니다.
이 기본 개념을 토대로 실무에서 상태 패턴이 어떻게 사용되는지 살펴보겠습니다.
2. 게임 개발에서의 상태 패턴 활용
게임 개발에서는 상태 패턴이 매우 자주 사용됩니다. 특히 캐릭터의 상태(Idle, Running, Attacking 등)를 관리하는 데 유용합니다. 캐릭터가 어떤 상태에 있는지에 따라 동작이 달라져야 하므로, 상태 패턴을 통해 이러한 상태 변화를 깔끔하게 관리할 수 있습니다.
2.1 캐릭터의 상태 관리 예제
아래 예제는 게임 캐릭터의 상태를 관리하는 코드입니다. 캐릭터는 Idle
(대기), Running
(달리기), Attacking
(공격) 상태를 가질 수 있으며, 각 상태에 따라 다른 행동을 수행합니다.
interface CharacterState {
handleInput(character: GameCharacter, input: string): void;
}
class IdleState implements CharacterState {
handleInput(character: GameCharacter, input: string): void {
if (input === "run") {
console.log("캐릭터가 달리기 상태로 전환됩니다.");
character.setState(new RunningState());
} else if (input === "attack") {
console.log("캐릭터가 공격 상태로 전환됩니다.");
character.setState(new AttackingState());
} else {
console.log("캐릭터는 대기 상태입니다.");
}
}
}
class RunningState implements CharacterState {
handleInput(character: GameCharacter, input: string): void {
if (input === "stop") {
console.log("캐릭터가 대기 상태로 전환됩니다.");
character.setState(new IdleState());
} else if (input === "attack") {
console.log("캐릭터가 공격 상태로 전환됩니다.");
character.setState(new AttackingState());
} else {
console.log("캐릭터는 달리고 있습니다.");
}
}
}
class AttackingState implements CharacterState {
handleInput(character: GameCharacter, input: string): void {
if (input === "stop") {
console.log("캐릭터가 대기 상태로 전환됩니다.");
character.setState(new IdleState());
} else if (input === "run") {
console.log("캐릭터가 달리기 상태로 전환됩니다.");
character.setState(new RunningState());
} else {
console.log("캐릭터는 공격하고 있습니다.");
}
}
}
class GameCharacter {
private state: CharacterState;
constructor() {
this.state = new IdleState(); // 초기 상태는 Idle(대기) 상태
}
setState(state: CharacterState): void {
this.state = state;
}
handleInput(input: string): void {
this.state.handleInput(this, input);
}
}
위 코드는 캐릭터가 Idle
, Running
, Attacking
상태 중 하나에 있을 때 어떤 행동을 해야 하는지를 정의합니다. 상태 패턴을 사용하면 상태별 동작을 각각의 클래스에서 정의하므로, 상태가 늘어나도 코드가 복잡해지지 않고 확장하기 쉽습니다.
2.2 상태 패턴을 적용한 게임 캐릭터의 행동
이제 상태 패턴을 적용한 캐릭터가 어떻게 동작하는지 살펴보겠습니다.
const character = new GameCharacter();
character.handleInput("run"); // 캐릭터가 달리기 상태로 전환됩니다.
character.handleInput("attack"); // 캐릭터가 공격 상태로 전환됩니다.
character.handleInput("stop"); // 캐릭터가 대기 상태로 전환됩니다.
결과:
캐릭터가 달리기 상태로 전환됩니다.
캐릭터가 공격 상태로 전환됩니다.
캐릭터가 대기 상태로 전환됩니다.
상태 패턴을 통해 캐릭터의 상태 변화를 간결하게 관리할 수 있음을 알 수 있습니다. 게임 개발에서 캐릭터나 NPC(Non-Playable Character)의 상태 변화를 관리할 때 상태 패턴은 매우 유용합니다.
3. 웹 애플리케이션에서의 상태 패턴 응용
상태 패턴은 웹 애플리케이션의 사용자 인터페이스(UI) 상태 관리에서도 자주 사용됩니다. 웹 애플리케이션에서는 사용자의 상호작용에 따라 UI 상태가 변할 수 있으며, 이러한 상태 변화를 관리하는 데 상태 패턴을 사용할 수 있습니다.
3.1 로그인 상태 관리 예제
로그인 상태를 관리하는 웹 애플리케이션을 예로 들어보겠습니다. 사용자는 로그아웃
, 로그인 중
, 로그인됨
상태를 가지며, 상태에 따라 UI가 달라집니다.
interface LoginState {
login(context: LoginContext): void;
logout(context: LoginContext): void;
}
class LoggedOutState implements LoginState {
login(context: LoginContext): void {
console.log("사용자가 로그인 중입니다.");
context.setState(new LoggingInState());
}
logout(context: LoginContext): void {
console.log("사용자는 이미 로그아웃 상태입니다.");
}
}
class LoggingInState implements LoginState {
login(context: LoginContext): void {
console.log("이미 로그인 처리 중입니다.");
}
logout(context: LoginContext): void {
console.log(
"로그인 처리 중 로그아웃 요청을 받았습니다. 로그아웃 상태로 전환됩니다."
);
context.setState(new LoggedOutState());
}
}
class LoggedInState implements LoginState {
login(context: LoginContext): void {
console.log("이미 로그인된 상태입니다.");
}
logout(context: LoginContext): void {
console.log("사용자가 로그아웃되었습니다.");
context.setState(new LoggedOutState());
}
}
class LoginContext {
private state: LoginState;
constructor() {
this.state = new LoggedOutState(); // 초기 상태는 로그아웃 상태
}
setState(state: LoginState): void {
this.state = state;
}
login(): void {
this.state.login(this);
}
logout(): void {
this.state.logout(this);
}
}
위 코드는 로그인 상태를 관리하는 시스템을 상태 패턴으로 구현한 예입니다. 사용자는 로그인 상태에 따라 login()
또는 logout()
동작을 할 수 있으며, 상태에 따라 적절한 동작이 실행됩니다.
3.2 로그인 상태 관리 시나리오
상태 패턴을 적용한 로그인 상태 관리의 예는 다음과 같습니다.
const loginContext = new LoginContext();
loginContext.login(); // 사용자가 로그인 중입니다.
loginContext.logout(); // 로그인 처리 중 로그아웃 요청을 받았습니다. 로그아웃 상태로 전환됩니다.
loginContext.login(); // 사용자가 로그인 중입니다.
loginContext.login(); // 이미 로그인 처리 중입니다.
loginContext.logout(); // 사용자는 이미 로그아웃 상태입니다.
결과:
사용자가 로그인 중입니다.
로그인 처리 중 로그아웃 요청을 받았습니다. 로그아웃 상태로 전환됩니다.
사용자가 로그인 중입니다.
이미 로그인 처리 중입니다.
사용자는 이미 로그아웃 상태입니다.
이 예제에서는 상태 패턴을 사용하여 로그인과 로그아웃 상태를 관리하고, 각 상태에 따라 적절한 동작을 처리하도록 만들었습니다. 상태 패턴을 사용하면 UI 상태가 변할 때마다 이를 상태 객체로 분리하여 관리할 수 있어 유지보수와 확장성이 높아집니다.
4. 금융 시스템에서의 상태 패턴 응용
금융 시스템에서 트랜잭션 상태를 관리하는 것도 상태 패턴의 중요한 응용 사례 중 하나입니다. 예를 들어, 결제 시스템에서는 트랜잭션이 여러 상태를 거치게 되며, 이 상태에 따라 결제 승인, 실패, 취소 등의 처리가 이루어집니다.
4.1 결제 트랜잭션 상태 관리 예제
아래 코드는 결제 트랜잭션의 상태를 관리하는 예입니다. 트랜잭션은 Created
, Pending
, Completed
, Cancelled
상태를 가지며, 상태에 따라 다른 동작을 수행합니다.
interface TransactionState {
process(context: TransactionContext): void;
cancel(context: TransactionContext): void;
}
class CreatedState implements TransactionState {
process(context: TransactionContext): void {
console.log("트랜잭션이 승인 대기 중입니다.");
context.setState(new PendingState());
}
cancel(context: TransactionContext): void {
console.log("트랜잭션이 취소되었습니다.");
context.setState(new CancelledState());
}
}
class PendingState implements TransactionState {
process(context: TransactionContext): void {
console.log("트랜잭션이 완료되었습니다.");
context.setState(new CompletedState());
}
cancel(context: TransactionContext): void {
console.log("트랜잭션이 처리 중이므로 취소할 수 없습니다.");
}
}
class CompletedState implements TransactionState {
process(context: TransactionContext): void {
console.log("이미 완료된 트랜잭션입니다.");
}
cancel(context: TransactionContext): void {
console.log("이미 완료된 트랜잭션은 취소할 수 없습니다.");
}
}
class CancelledState implements TransactionState {
process(context: TransactionContext): void {
console.log("취소된 트랜잭션입니다. 처리할 수 없습니다.");
}
cancel(context: TransactionContext): void {
console.log("이미 취소된 트랜잭션입니다.");
}
}
class TransactionContext {
private state: TransactionState;
constructor() {
this.state = new CreatedState(); // 초기 상태는 생성된 상태
}
setState(state: TransactionState): void {
this.state = state;
}
process(): void {
this.state.process(this);
}
cancel(): void {
this.state.cancel(this);
}
}
4.2 결제 트랜잭션 상태 전환 시나리오
const transaction = new TransactionContext();
transaction.process(); // 트랜잭션이 승인 대기 중입니다.
transaction.process(); // 트랜잭션이 완료되었습니다.
transaction.cancel(); // 이미 완료된 트랜잭션은 취소할 수 없습니다.
결과:
트랜잭션이 승인 대기 중입니다.
트랜잭션이 완료되었습니다.
이미 완료된 트랜잭션은 취소할 수 없습니다.
트랜잭션 상태 관리에서 상태 패턴을 사용하면 각 상태별로 트랜잭션 처리 로직을 분리하여 관리할 수 있습니다. 트랜잭션이 어떤 상태에 있는지에 따라 처리 방식이 달라지며, 상태 패턴을 통해 이를 깔끔하게 관리할 수 있습니다.
5. 상태 패턴의 응용 장점
상태 패턴을 다양한 실무 사례에 적용하면서 얻을 수 있는 주요 장점은 다음과 같습니다.
- 복잡한 상태 전환 로직의 간결화: 상태별로 로직을 분리하여 조건문을 최소화하고, 상태 전환이 명확해집니다.
- 확장성: 새로운 상태를 쉽게 추가할 수 있으며, 기존 로직에 최소한의 영향만 미칩니다.
- 상태에 따른 행동의 분리: 상태와 행동이 명확하게 분리되므로, 유지보수가 용이하고 가독성이 높아집니다.
- 상태 전환의 명시적 관리: 상태 전환이 각 상태 객체에서 명시적으로 이루어지므로, 상태 관리가 더 직관적이고 투명해집니다.
6. 결론
이번 글에서는 상태 패턴이 실무에서 어떻게 사용되는지, 다양한 응용 사례를 통해 살펴보았습니다. 게임 캐릭터의 상태 관리, 웹 애플리케이션의 로그인 상태 관리, 금융 시스템에서의 트랜잭션 상태 관리 등에서 상태 패턴이 유용하게 적용될 수 있습니다. 이러한 사례들을 통해 상태 패턴의 장점과 실무에서의 유용성을 확인할 수 있었습니다.
'디자인패턴' 카테고리의 다른 글
상태 패턴의 확장과 변형(State Pattern) (0) | 2024.10.17 |
---|---|
상태 패턴(State Pattern) 소개 (0) | 2024.10.17 |
AOP와 DI 구분 (0) | 2024.09.24 |
퍼사드 패턴(Facade Pattern) (0) | 2014.06.25 |
Adapter Pattern(어답터 패턴) (0) | 2014.06.24 |