2 분 소요

상태 패턴

정의

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

-GoF design pattern

객체 내부의 상태가 바뀔 때, 객체의 동작을 변경할 수 있도록 한다.

구조

img1

Context

여러 상태가 존재하는 시스템을 말한다. 시스템은 상태를 나타내는 State를 가지고 있으며 클라이언`로부터 요청을 받으면 State에 실행을 위임한다.

State

상태를 추상화한 인터페이스이다.

ConcreteStatus는 이를 구현하여 구체적인 상태를 나타내게 된다.

ConcreteStatus

구체적인 상태를 나타내는 클래스이다.

맴버 변수로 Context를 가지고 있으며 연산에 따라 Context의 상태를 변경한다.

필요성

아래와 같은 상태가 존재하는 뽑기 기계를 생각해보자.

img1

뽑기 기계는 상태( 동전 투입 여부, 알맹이 개수)에 따라 동작 방식이 달라진다.

이를 구현하기 위해서는 분기 처리를 해줘야 한다.

만약 여기서 새로운 요구사항이 생기면 어떻게 될까?

예를 들어서 5번에 한 번마다 알맹이가 2개가 나오게 변경한다고 생각해보자.

그러면 우리는 5번째인지 확인하기 위해 조건문을 모든 메소드에 추가해야 한다.

매번 요구사항이 추가될 때마다 기존 코드를 변경해야 하는 것은 유지보수 측면에서 좋지 않다. 이를 해결하는 방법 중 하나가 상태 패턴이다.

상태 패턴을 사용하게 되면 상태에 따른 동작을 클래스로 관리할 수 있게 되고 코드의 복잡도도 줄일 수 있게 된다.

-> 상태 패턴의 필요성에 대해서 봤으니 이제 상태 패턴의 장단점에 대해서 알아보자.

특징

장점

  1. 상태에 따른 객체의 동작을 클래스로 관리할 수 있게 된다.
  2. 상태에 관련된 동작을 각 클래스로 분산시켜 코드의 복잡도를 줄일 수 있다.
  3. 새로운 요구사항이 생겨서 변경이 발생하더라도 기존의 코드를 그대로 유지할 수 있다. (OCP원칙)

단점

  1. 상태별로 클래스로 관리해야 하기 때문에 클래스의 수가 증가한다.
  2. 객체의 상태가 몇 개 없거나 상태가 거의 변경되지 않는 경우에 상태 패턴을 적용하는 것은 오버엔지니어링일 수 있다.

예제

위에 필요성에서 설명했던 예제인 뽑기 기계를 가지고 상태 패턴을 이해해보자.

먼저 뽑기 기계의 상태에 대해서 생각해보자. 아래와 같이 4가지 상태가 존재할 것이다.

SOLD_OUT

머신의 알맹이가 다 떨어진 상태를 의미한다.

NO_COIN

동전이 투입되지 않은 상태를 의미한다.

HAS_COIN

동전이 투입된 상태를 의미한다.

SOLD

손잡이를 돌려서 알맹이가 나오고 있는 상태를 의미한다.

그리고 이런 상태에 4가지 행위가 있다.

  1. 뽑기 기계에 코인을 넣는 행위
  2. 뽑기 기계에서 알맹이를 뽑는 행위
  3. 뽑기 기계에서 동전을 반환하는 행위

-> 이제 이를 상태 패턴 없이 코드로 작성해보겠다.

Machine Class

img1img1img1

상태에 따른 동작을 분기 처리를 통해서 구현했다.

그리고 각 상태(SOLD_OUT, SOLD, HAS_COIN, NO_COIN)를 상태 변수로 처리했다.

하지만 이 코드에는 여러 단점이 존재한다.

  1. 상태 전환을 하기 위해 조건 분기를 해야 한다. 이는 가독성에 좋지 않다.
  2. 상태에 따른 추가 요구사항이 생기면 메소드를 다 수정해야 한다. OCP를 위반하게 된다.

그렇다면 이제 상태 패턴을 적용하여 이러한 단점을 보안해보자.

여기서는 상태 패턴 + 싱글톤 패턴을 같이 적용한 코드를 볼 것이다.

싱글톤 패턴을 적용한 이유는 상태를 변경할 때마다 새로운 객체를 생성하게 되면 메모리 낭비가 발생하기 때문이다.

상태 패턴 + 싱글톤 패턴 적용

Machine (Context)

img1img1

Machine 클래스는 맴버 변수로 상태를 가지고 있다. 그리고 동전 넣기, 동전 반환, 손잡이 돌리기, 뽑기라는 4가지 행위를 가진다.

각각의 행위는 State에 위임되며 현재 Machine의 상태에 따라 다르게 동작하게 된다.

그리고 동작이 일어나고 상황에 맞게 State가 변경될 수도 있다.

State (State)

img1

상태를 추상화한 인터페이스이다.

NoCoinState (ConcreteState)

img1img1

동전이 없는 상태를 나타내는 ConcreteState이다.

싱글톤 패턴을 적용하였는데 만약 싱글톤 패턴을 적용하지 않았다면 insertCoin(Machine machine) 에서 머신의 상태를 변경할 때, 새로운 객체를 생성해서 넘겼어야 했을 것이다.

이렇게 되면 상태가 변할때마다 객체가 생성되기 때문에 효율적이지 않다.

물론 GC가 주기적으로 정리해주긴 하지만 이러한 쓰레기가 늘어나게 되면 제거 과정에서 Stop-the-world가 발생하여 프로그램이 느려질 수 있다.

HasCoinState (ConcreteState)

img1img1

동전이 들어있는 상태를 나타내는 ConcreteState이다.

동전이 들어있을 때는 동전 반환, 손잡이 돌리기를 수행하면 상태가 변하게 된다.

SoldOutState (ConcreteState)

img1img1

알맹이가 떨어진 상태를 나타내는 ConcreteState이다.

SoldState (ConcreteState)

img1img1

알맹이가 나가고 있는 상태를 나타내는 ConcreteState이다.

정리

상태 패턴은 상태를 객체화하여 객체의 행동을 상태에 특화된 행위들로 분리할 수 있다.

상태에 따라 행위가 달라진다면 상태 패턴을 고려해보면 좋을 것 같다.

참고