상태 패턴

  • 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다.

1. 최첨단 뽑기 기계

  • 뽑기 기계 회사에서 의뢰가 들어왔습니다.

  • 원하는 상태 다이어그램을 보내주었고, 우리는 그에 맞춰 코드를 제작하면 될 겁니다.

2. 10분의 1확률 게임기능 더하기

  • 제품을 성공적으로 납품했습니다. 다행히 뽑기 회사는 제품에 만족헀다고 합니다. 하지만, 추가 요청이 들어왔어요. 10분의 1의 확률로 알맹이가 2개가 나와야 된다고 합니다.

  • 그런데 winner라는 상태값을 하나 추가해서 수정하려고 하니, 기존의 코드를 너무 많이 고쳐야 합니다. 다른 기능 추가 요청이 더 들어온다면 그때부턴 더 힘들어지겠죠.

  • 아, 또 추가로 리필 기능이 필요하다고 합니다. 그 기능도 추가를 해야 해요!

  • 현재 코드가 가지고 있는 문제점

    • OCP를 위반합니다.(변경에는 닫혀있고, 확장에는 열려있어야 한다.)

    • 이런 디자인은 객체지향 디자인이라고 하기 힘듭니다.

    • 상태 전환이 복잡한 조건문 속에 숨어있어서 디버깅하기 힘듭니다.

    • 바뀌는 부분을 전혀 캡슐화하지 않습니다.

    • 새로운 기능을 추가하는 과정에서 기존 코드에 없던 새로운 버그가 생길 가능성이 높습니다.

3. 상태 패턴으로 리팩토링하기

  • 상태별 행동을 별도의 클래스에 넣어두고, 각자 자기 할 일을 구현하도록 하면 어떨까? 새로운 상태(winner)가 늘어난다면 그냥 클래스를 새로 추가하면 될 수 있도록 말이죠.

  • 각 상태클래스에서 사용할 인터페이스를 만듭니다.

  • 10% 당첨률을 추가한 WinnerState와 refill() 기능을 추가했습니다.

  • SoldState에서 해결하지 않고 WinnerState를 추가한 것은 SRP에 위배되기 때문입니다. 절대적으로 꼭 지켜야할 법칙은 아니지만, 특별 행사 기간이 끝나거나 당첨 확률이 달라지거나 하는 변수에 대응하기 위해서는 나누는 게 더 좋습니다.

  • 상태 패턴을 적용해서 수정된 코드

    • 각 상태의 행동을 별개의 클래스로 국지화했습니다.

    • 관리하기 힘든 if문들을 없앴습니다.

    • 각 상태는 변경에 닫혀 있게 했고, GumballMachine 클래스는 새로운 상태를 추가하는 확장에는 열려 있도록 고쳤습니다.(OCP)

    • 오너가 처음 제시했던 다이어그램에 훨씬 가까우면서 더 이해하기 좋은 코드 베이스와 클래스 구조를 만들었습니다.

4. 상태 패턴

  • 상태를 별도의 클래스로 캡슐화하고, 현재 상태를 나타내는 객체에게 행동을 위임합니다. 그래서 상태가 바뀔 때 행동이 달라지게 됩니다.

  • 마치 객체가 바뀌는 것 같은 결과를 얻을 수 있는 것인데, 실제로 그런 것이 아니라 구성(Composition)으로 여러 상태 객체를 바꿔가면서 사용하는 거겠죠.

  • 상태가 늘어남에 따라 클래스의 개수는 늘어납니다. 유연성을 향상시키려고 지불하는 비용이라고 생각하면 됩니다.

  • 상태 패턴과 전략 패턴의 다이어그램은 똑같습니다. 하지만 사용 용도가 다르죠.

  • State Pattern(상태 패턴)

    • 미리 몇 가지 상태를 가지고 작업하고, 미리 정해진 상태 전환 규칙에 따라(State 인터페이스) 알아서 자기 상태를 변경한다.

    • 상태 객체에 일련의 행동이 캡슐화 됩니다. Context(GumballMachine) 객체의 상태에 따라 현재 상태를 나타내는 객체가 바뀌게 되고, 행동도 바뀝니다.

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

    • Context 내에 수많은 if 문을 넣는 대신에 상태 별로 캡슐화해서 상태를 바꿔준다고 생각하면 됩니다.

  • Strategy Pattern(전략 패턴)

    • 어떤 클래스를 인스턴스를 만들고, 그 인스턴스에게 어떤 행동을 구현하는 전략 객체를 건네준다.

    • 클라이언트가 Context 객체에게 어떤 전략 객체를 사용할지를 지정합니다.

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

    • 상속을 사용해서 클래스의 행동을 정의한다면, 유연성이 떨어지기 때문에 구성을 사용해서 실행 중에 유연하게 행동을 바꾸는 용도로 쓰입니다.

5. 생각해보기

Reference

https://www.hanbit.co.kr/store/books/look.php?p_code=B6113501223

Last updated