[Design pattern] 3-6. 상태 패턴 (State pattern)


Study/Design Pattern  2021. 11. 17. 20:02

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 상태 패턴(State pattern)에 대한 글입니다.


먼저 상태 패턴이라는 것은 클래스의 상태에 따라 처리되는 결과 값이 다른 형태로 만드는 것을 말합니다.

어떻게 보면 전략 패턴과 약간 비슷한 구조가 될 수 있는데...

차이를 두자면 전략 패턴은 전략 인스턴스에 의해 외부의 값이 변환되어 실행되는 것이고 상태 패턴이라는 것은 상태 인스턴스에 의해 내부의 값의 처리가 바뀌는 것을 뜻합니다.

사실 디자인 패턴이라는 것은 실무에서 딱 이건 무슨 패턴이다 정의된 것은 없고.. 그 내용만 이해하여 사용하면 되는 것입니다.

출처 - https://en.wikipedia.org/wiki/State_pattern
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 상태 패턴 인터페이스
class IState {
public:
  // 함수 추상화
  virtual void print(const char* str) = 0;
  virtual ~IState() {};
};
// AState 상태 패턴 클래스, IState 인터페이스를 상속
class AState : public IState {
public:
  // 함수 재정의
  void print(const char* str) {
    // 콘솔 출력
    cout << "AState - " << str << endl;
  }
};
// BState 상태 패턴 클래스, IState 인터페이스를 상속
class BState : public IState {
public:
  // 함수 재정의
  void print(const char* str) {
    // 콘솔 출력
    cout << "BState - " << str << endl;
  }
};
// 상태 패턴을 사용할 클래스
class Context {
private:
  // 상태 패턴 맴버 변수
  IState* state;
public:
  // 생성자, 초기 상태를 설정한다.
  Context(IState* state) {
    // 상태 설정 함수 호출
    this->setState(state);
  }
  // 상태 설정 함수
  void setState(IState* state) {
    // 맴버 변수 설정
    this->state = state;
  }
  // 실행
  void run() {
    // 상태 패턴의 print 함수를 호출한다.
    this->state->print("Hello world");
  }
};
// 실행 함수
int main()
{
  // 상태 패턴 인스턴스 생성
  AState astate;
  BState bstate;
  // Context 인스턴스 생성, 최초 AState 인스턴스의 상태를 설정한다.
  Context context(&astate);
  // Context 인스턴스의 run함수 실행
  context.run();
  // 상태를 BState로 설정한다.
  context.setState(&bstate);
  // Context 인스턴스의 run함수 실행
  context.run();

  return 0;
}

전략 패턴에서는 아마 run 함수에서 무엇가의 값을 받았을 것입니다. 즉, run 함수에서 값을 받고 그 전략 패턴에 의해 다른 값을 리턴하는 것이고, 상태 패턴은 run의 함수에서는 받은 것은 없지만 상태 인스턴스에 의해 내부의 처리가 바뀐 것을 뜻합니다.

제가 알고 있는 전략 패턴과 상태 패턴의 차이는 이것입니다. 사실 저도, 실무에서 적용할 때 전략 패턴, 상태 패턴 구분해서 사용하지는 않습니다. 그냥 사양에 맞게 맞는 패턴 쓰는 거지 뭐...

import java.util.HashMap;
import java.util.Map;
// 상태 패턴의 인터페이스
interface IState {
  // 함수 추상화
  void print(String str);
}
// AState의 상태 패턴 클래스, IState 인터페이스를 상속
class AState implements IState {
  // 함수 재정의
  public void print(String str) {
    // 콘솔 출력
    System.out.println("AState - " + str);
  }
}
// BState의 상태 패턴 클래스, IState 인터페이스를 상속
class BState implements IState {
  // 함수 재정의
  public void print(String str) {
    // 콘솔 출력
    System.out.println("BState - " + str);
  }
}
// Process 클래스
class Process {
  // flyweight 패턴의 맵
  private Map<Class<? extends IState>, IState> flyweight = new HashMap<>();
  // 상태 패턴의 맴버 변수
  private IState state = null;
  // 생성자
  public Process(Class<? extends IState> clz) {
    // 상태 설정
    setState(clz);
  }
  // flyweight 패턴의 상태 패턴 인스턴스 취득 함수
  private IState getState(Class<? extends IState> clz) {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.containsKey(clz)) {
      try {
        // 없으면 인스턴스를 생성해서 입력한다.
        flyweight.put(clz, clz.getDeclaredConstructor().newInstance());
      } catch (Exception e) {
        // Exception 처리를 RuntimeException 처리로 변환
        throw new RuntimeException(e);
      }
    }
    // 상태 패턴 인스턴스 취득
    return flyweight.get(clz);
  }
  // 상태 패턴 타입 입력
  public void setState(Class<? extends IState> clz) {
    // flyweight 맵에서 인스턴스 취득
    this.state = getState(clz);
  }
  // 출력 함수
  public void run() {
    // 상태 패턴의 print 함수를 호출한다.
    this.state.print("Hello world");
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Process 인스턴스 생성, 상태 패턴 타입 설정
    Process process = new Process(AState.class);
    // run 함수 호출
    process.run();
    // 상태 패턴 타입 설정
    process.setState(BState.class);
    // run 함수 호출
    process.run();
  }
}

상태 패턴도 전략 패턴과 같은 구조로 flyweight 패턴을 적용해서 상태 패턴 인스턴스 생성을 최소화 하였습니다. 그리고 인스턴스 재사용률을 높여서 시스템의 성능을 올릴 수 있습니다.

using System;
using System.Collections.Generic;
// 상태 패턴의 인터페이스
interface IState
{
  // 함수 추상화
  void Print(String str);
}
// AState의 상태 패턴 클래스, IState 인터페이스를 상속
class AState : IState
{
  // 함수 재정의
  public void Print(String str)
  {
    // 콘솔 출력
    Console.WriteLine("AState - " + str);
  }
}
// BState의 전략 패턴 클래스, IState 인터페이스를 상속
class BState : IState
{
  // 함수 재정의
  public void Print(String str)
  {
    // 콘솔 출력
    Console.WriteLine("BState - " + str);
  }
}
// Process 클래스
class Process
{
  // flyweight 패턴의 딕셔너리
  private Dictionary<Type, IState> flyweight = new Dictionary<Type, IState>();
  // 상태 패턴의 맴버 변수
  private IState state = null;
  // 생성자
  public Process(Type clz)
  {
    // 상태 설정
    SetState(clz);
  }
  // flyweight 패턴의 상태 패턴 인스턴스 취득 함수
  private IState GetState(Type clz)
  {
    // flyweight 딕셔너리에 클래스 타입이 있는지 확인
    if (!flyweight.ContainsKey(clz))
    {
      // 없으면 인스턴스를 생성해서 입력한다.
      flyweight.Add(clz, Activator.CreateInstance(clz) as IState);
    }
    // 상태 패턴 인스턴스 취득
    return flyweight[clz];
  }
  // 상태 패턴 타입 입력
  public void SetState(Type clz)
  {
    // flyweight 딕셔너리에서 인스턴스 취득
    this.state = GetState(clz);
  }
  // 출력 함수
  public void Run()
  {
    // 상태 패턴의 Print 함수를 호출한다.
    this.state.Print("Hello world");
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // Process 인스턴스 생성, 상태 패턴 설정
    Process process = new Process(typeof(AState));
    // Run 함수 호출
    process.Run();
    // 상태 패턴 설정
    process.SetState(typeof(BState));
    // Run 함수 호출
    process.Run();
    // 아무 키나 누르면 종료
    Console.WriteLine("Press Any key...");
    Console.ReadLine();
  }
}

상태 패턴도 전략 패턴과 비슷한 목적으로 사용됩니다. 클래스의 결합도를 낮추고 재사용성을 높이는 호과를 위해서 사용합니다.

단지 그 차이점이 전략 패턴은 외부의 값이 패턴에 의해 다른 결과를 내는 것이고, 상태 패턴은 패턴에 의해 내부의 값이 다른 결과를 내는 것입니다.


여기까지 디자인 패턴의 상태 패턴(State pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.