Ch.10 상태 패턴

객체의 상태 바꾸기

전략 패턴과 상태 패턴은 쌍둥이다

전략 패턴은 바꿔 쓸 수 있는 알고리즘을 내세워 큰 성공을 거둔 반면,

상태 패턴은 내부 상태를 바꿈으로써 객체가 행동을 바꿀 수 있도록 도와주는 길을 택했다.

The Code

뽑기 기계

다음과 같은 상태를 갖는 츄잉껌(gumball) 뽑기 기계가 있다.

분석

상태들은 총 4개가 있다.

  • SOLD_OUT (츄잉껌 매진)

  • NO_QUARTER (동전 없음)

  • HAS_QUARTER (동전 있음)

  • SOLD (츄잉껌 판매)

여기서 상태값을 정의하기 위해 상수값(e.g., static final int SOLD_OUT = 1)으로 정의할 수 있을 것이다.

처음 뽑기 기계를 상자에서 꺼내 설치하는 시점에선 알맹이가 하나도 없을 테니 현재 상태는 SOLD_OUT으로 하자.

이 시스템에서 일어날 수 있는 모든 행동은 다음과 같다.

  • 동전 투입

  • 동전 반환

  • 손잡이 돌림

  • 츄잉껌 내보냄

위의 행동 각각은 뽑기 기계의 인터페이스라고 할 수 있으며, 이런 행동을 실행할 때마다 상태가 바뀌는 것을 알 수 있다.

이제 상태 기계 역할을 하는 클래스를 만들면 된다. 간단하게 현재 상태 받아 상태를 if문으로 비교해 상태를 바꾸고 결과를 출력할 수 있을 것이다.

요구사항의 변화

각 행동 인터페이스마다 현재 상태를 읽어 다음 상태로 바꿔주는 메서드를 조건문을 이용해 어찌저찌 잘 구현했다.

그런데 이번엔 요구사항이 바뀌어 10분의 1 확률로 보너스 츄잉껌을 받을 수 있도록, 즉, 10번에 1번 꼴로 손잡이를 돌릴 때 츄잉껌이 2개가 나오도록 코드를 고쳐야 한다.

여태까진 상태가 많지 않아서 조건문과 인터페이스 추가로 잘 해결했지만, 상태가 늘어나면서 조건문이 비대해지면서 행동도 비대해지게 된다. 따라서 리팩터링이 필요하다.

  1. 모든 행동에 관한 메소드가 들어있는 State 인터페이스를 정의한다.

  2. 기계의 모든 상태를 대상으로 상태 클래스를 구현한다. 기계가 어떤 상태에 있다면, 그 상태에 해당하는 상태 클래스가 모든 작업을 책임져야 한다.

  3. 마지막으로 조건문 코드를 전부 없애고 상태 클래스에 모든 작업을 위임한다.

이제 우리는 상태 패턴이라는 새로운 패턴을 구현하게 된다.

State Pattern

상태 패턴(State Pattern)을 사용하면 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

  • 구상 상태 클래스는 원하는 만큼 만들 수 있다.

  • 클라이언트는 상태 객체와 직접 연락하지 않고, Context 쪽에서 변경해야 한다.

State v.s. Strategy Pattern

사실 다이어그램은 똑같다. 하지만 상태 패턴과 전략 패턴의 용도는 다르다.

State Pattern

  • 상태 패턴에서는 상태 객체에 일련의 행동이 캡슐화된다.

  • 상황에 따라 Context 객체에서 여러 상태 객체 중 한 객체에게 모든 행동을 맡기게 된다.

  • 그 객체의 내부 상태에 따라 현재 상태를 나타내는 객체가 바뀌게 되고, 그 결과로 Context 객체의 행동도 자연스럽게 바뀐다.

  • 클라이언트는 상태 객체를 몰라도 된다.

  • 전략 패턴은 일반적으로 클라이언트가 Context 객체에게 어떤 전략 객체를 사용할지를 지정해 준다.

  • 전략 패턴은 주로 실행 시에 전략 객체를 변경할 수 있는 유연성을 제공하는 용도로 쓰인다.

  • 보통 가장 적합한 전략 객체를 선택해서 사용하게 된다. 예를 들어 1장에서는 특정한 방식으로 날아다니도록 설정된 오리와 날지 못하도록 설정된 오리가 등장했다. 이럴 때 각각 필요한 전략 객체를 지정해서 사용했다.

일반적으로 전략 패턴은 서브클래스를 만드는 방법을 대신해서 유연성을 극대화하는 용도로 쓰인다.

상속을 사용해서 클래스의 행동을 정의하다 보면 행동을 변경해야 할 때 마음대로 변경하기가 힘들다.

하지만 전략 패턴을 사용하면 합성으로 행동을 정의하는 객체를 런타임에서 유연하게 바꿀 수 있다.

Context 객체에 수많은 조건문을 넣는 대신 상태 패턴을 사용한다고 생각하면 된다. 행동을 상태 객체 내에 캡슐화하면 Context 내의 상태 객체를 바꾸는 것만으로도 Context 객체의 행동을 바꿀 수 있다.

My Thoughts

예전에 컴파일러 수업에서 Finite State Machine을 이용해 simplified C를 구현했던 기억이 있다. 그때 enum을 남발해 개발했던 코드를 생각하고 반성하며 마무리해본다... 😅

Last updated