디자인 패턴 - State Pattern

김도현·2023년 10월 17일
0

TIL

목록 보기
50/76
post-thumbnail

State Pattern(상태 패턴)

State Pattern(상태 패턴)은 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 상태를 조건문으로 검사해서 행위를 달리하는 것이 아닌, 상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴을 말한다.

객체 지향 프로그래밍에서의 클래스는 꼭 사물 / 생물만을 표현하는 고체 형태의 데이터만 표현 할 수있는게 아니다. 경우에 따라서 무형태의 행위 / 동작도 클래스로 묶어 표현할 수 있다.

그래서 상태를 클래스로 표현하면 클래스를 교체해서 '상태의 변화'를 표현할 수 있고, 객체 내부 상태 변경에 따라 객체의 행동을 상태에 특화된 행동들로 분리해 낼 수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.

상태이란?
객체가 가질 수 있는 어떤 조건이나 상황을 의미한다.
예를 들어 티비가 켜져 있는 상태라면 음량 버튼을 누르면 음량이 증가하거나 감소한다. 하지만 티비가 꺼져 있는 상태라면 음량버튼을 아무리 눌러도티비의 음량은 바뀌지 않는다. 즉, 티비 전원의 상태에 따라 메소드 행동이 바뀌는 것이다. 이 처럼 객체가 특정 상태에 따라 행위를 달리하는 상황에서 사용되는 최적의 패턴이 State Patter이다.

구조

  1. State 인터페이스 : 상태를 추상화한 고수준 모듈
  2. ConcreteState : 구체적인 각각의 상태를 클래스로 표현. State 역할로 결정되는 인터페이스(API)를 구체적으로 구현한다. 다음 상태가 결정되면 Context에 상태 변경을 요청하는 역할도 한다.

각 상태들, On, Off, Saving 상태를 클래스로 정의하고, 이들을 하나의 인터페이스로 묶습니다. 그리고나서 Laptop이 상태 인터페이스의 메서드를 호출하면 각 상태 클래스에서 정의된 행위가 수행되는 방식입니다.

코드

  1. 먼저 전원 상태를 캡슐화한 인터페이스를 선언합니다.
public interface PowerState {
    public void powerPush();
}
  1. 다음으로 PowerState 인터페이스를 구현한 각 상태 클래스를 정의합니다.
public class On implements PowerState{
    public void powerPush(){
        System.out.println("전원 off");
    }
}
public class Off implements PowerState {
    public void powerPush(){
        System.out.println("절전 모드");
    }
}
public class Saving implements PowerState {
    public void powerPush(){
        System.out.println("전원 on");
    }
}
  1. 이어서 Laptop클래스 작성
public class Laptop {
    private PowerState powerState;

    public Laptop(){
        this.powerState = new Off();
    }

    public void setPowerState(PowerState powerState){
        this.powerState = powerState;
    }

    public void powerPush(){
        powerState.powerPush();
    }
}
  1. 마지막으로 Laptop 객체를 사용하는 Client 클래스를 정의합니다.
public class Client {
    public static void main(String args[]){
        Laptop laptop = new Laptop();
        On on = new On();
        Off off = new Off();
        Saving saving = new Saving();

        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
        laptop.setPowerState(saving);
        laptop.powerPush();
        laptop.setPowerState(off);
        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
    }
}

특징

상태 패턴 사용 시기

  • 객체의 행동(메서드)가 상태(state)에 따라 각기 다른 동작을 할때.
  • 상태 및 전환에 걸쳐 대규모 조건 분기 코드와 중복 코드가 많을 경우
  • 조건문의 각 분기를 별도의 클래스에 넣는것이 상태 패턴의 핵심
  • 런타임단에서 객체의 상태를 유동적으로 변경해야 할때

상태 패턴 장점

  • 상태(State)에 따른 동작을 개별 클래스로 옮겨서 관리 할 수 있다.
  • 상태(State)와 관련된 모든 동작을 각각의 상태 클래스에 분산시킴으로써, 코드 복잡도를 줄일 수 있다.
  • 단일 책임 원칙을 준수할 수 있다. (특정 상태와 관련된 코드를 별도의 클래스로 구성)
  • 개방 폐쇄 원칙을 준수 할 수 있다. (기존 State 클래스나 컨텍스트를 변경하지 않고 새 State를 도입할 수 있다)
  • 하나의 상태 객체만 사용하여 상태 변경을 하므로 일관성 없는 상태 주입을 방지하는데 도움이 된다

상태 패턴 단점

  • 상태 별로 클래스를 생성하므로, 관리해야할 클래스 수 증가
  • 상태 클래스 갯수가 많고 상태 규칙이 자주 변경된다면, Context의 상태 변경 코드가 복잡해지게 될 수 있다.
  • 객체에 적용할 상태가 몇가지 밖에 없거나 거의 상태 변경이 이루어지지 않는 경우 패턴을 적용하는 것이 과도할 수 있다.

State(상태) vs Strategy(전략)

패턴 유사점

  • 전략 패턴과 상태 패턴은 클래스 다이어그램이 거의 동일하고 코드 사용법도 비슷하다.
  • 둘다 난잡한 조건 분기를 극복하기 위해 전략 / 상태 형태를 객체화
  • 둘다 합성(Composition)을 통해 상속의 한계를 극복
  • 둘다 객체의 일련의 행동이 캡슐화되어 객체 지향 원칙을 준수한다.
  • State는 Strategy의 확장으로 간주될 수도 있다.

패턴 차이점

  • 전략 패턴과 상태 패턴의 구조는 거의 같지만 어떤 목적을 위해 사용되는가에 따라 차이가 있다.
  • 전략 패턴은 알고리즘을 객체화 하여 클라이언트에서 유연적으로 전략을 제공 / 교체를 한다.
    상태 패턴은 객체의 상태를 객체화하여 클라이언트와 상태 클래스 내부에서 다른 상태로 교체를 한다.
  • 전략 패턴의 전략 객체는 그 전략만의 알고리즘 동작을 정의 및 수행한다. (만일 전략을 상태화 하면 클래스 폭발이 일어날 수 있다)
    상태 패턴의 상태 객체는 상태가 적용되는 대상 객체가 할수있는 일련의 모든 행동들을 정의 및 수행한다.
  • 전략 패턴의 전략 객체는 입력값에 따라 전략 형태가 다양하게 될 수 있으니 인스턴스로 구성한다.
    상태 패턴의 상태 객체는 정의된 상태를 서로 스위칭 하기에 메모리 절약을 위해 싱글톤으로 구성한다.

State Machin

State Machin 이란?

State Pattern을 활용하여 만든 방식이며 주로 컴파일러, 임베디드, 게임 캐릭터(몬스터) AI에 자주 사용된다.

State Machin은 흔히들 여러 이름으로 불린다.
State Machine(상태 기계), Finite State Machine(유한 상태 기계), Finite Automation(유한 오토마톤), Finite Automata(무한 오토마타), 등등..

State Machin은 Finite, 유한 개의 State로 구성된 Finite State Machine과 Infinite, 무한 개의 State로 구성되는 Infinite State Machine으로 나뉜다.

  • 유한 상태 기계는 자신이 취할 수 있는 유한한 갯수의 상태들을 가진다.
  • 그리고 그 중에서 반드시 하나의 상태만 취한다.
  • 현재 상태는 특정 조건이 되면 다른 상태로 변할 수 있다.
  • 유한 상태 기계는 가능한 상태들의 집합과 각 상태들의 전이 조건으로 전의 될 수 있다.
  • 상태들의 노드와 그 노드들을 연결하는 조건의 엣지로 표현할 수 있다.(그래프)

사용 이유

1. 반드시 설계한대로만 플로우가 흐른다.

  • 위 예시에서는 절대로 Init State에서 Processing State로 바로 갈 수가 없다. 고로 안정성이 높아진다.
  • 또한 크리티컬 에러가 발생하여 디바이스나 서버가 죽을 시에는 개발자가 대충 어느 지점에서 크리티컬 에러가 발생했는지 로그를 보지 않고도 짐작이 가능하여 디버깅이 빨라진다.

2. 정해진 이벤트를 통해 상태가 천이된다.

  • 설계자는 모든 플로우를 주관하여 설계할 수 있고, 구현자는 설계 문서만 보더라도 설계와 똑같이 구현이 가능해진다.
  • 설계 자체도 단순해지며, 문서화에 걸리는 시간이 줄어든다.
  • 멀티스레딩/멀티프로세싱 환경이나 기타 외란(disturbance)에도 오염이 되지 않으며 (미리 정해진 이벤트 규약이기에) 안정된 실행 환경을 보장한다.

3. 전체적인 흐름이 유기적이며 명확하다.

  • 나중에 들어온 후임자가 보더라도 코드는 명확하고 한 눈에 들어온다.
  • 설령 코드 구현체 자체가 복잡하더라도 설계 문서 한 장만 보더라도 곧바로 이해가 가능해진다.

참조 사이트

Inpa Dev
victolee
공대생의 차고
Tecpble
refactoring.guru

0개의 댓글