본문 바로가기
Book

[독서 기록] 헤드퍼스트 디자인 패턴 10장 | 상태 패턴 - 객체의 상태 바꾸기

by Renechoi 2022. 11. 15.
 
헤드 퍼스트 디자인 패턴
이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나의 패턴에 하나의 이야기를 담았습니다. 틀에 박히지 않아 지루할 틈이 없는 구성과 친구와 이야기하듯 편안한 대화체로 이야기를 풀어냅니다. 이야기 속에 다양한 방법으로 해결할 수 있는 질문과 90개 이상의 연습문제를 담았습니다. 마치 게임 퀘스트를 해결하듯 문제를 하나하나 해결하다 보면 학습한 내용이 머릿속에 강렬하게 남습니다. 이유 2. 원스톱으로 배우는 14가지 GoF 핵심 디자인 패턴과 9가지 객체지향 디자인 원칙! 현장에서 자주 사용되는 옵저버, 어댑터, MVC 패턴 등 14가지 GoF 객체지향 패턴을 중점으로 패턴의 정의, 사용 시기, 사용처, 사용 이유, 즉시 디자인에 적용하는 방법을 알려줍니다. 이와 더불어 객체지향 프로그래밍에 광범위하게 적용할 수 있는 OCP, 할리우드 원칙 등 9가지 객체지향 디자인 원칙과 패턴으로 생각하는 방법도 알려줍니다. 이유 3. 시대의 변화에 맞춘 개정과 한국 독자만을 위한 특별판! 자바 8과 자바 16 이상에서 무리 없이 동작할 수 있도록 예제 코드를 수정했으며, 부가적인 설명과 Q&A 질문을 추가했습니다. 또한 16여 년 만의 개정을 기념해 오직 한국 독자만을 위한 새로운 삽화를 사용하고 한글 친화적인 구성했습니다. 원서를 읽을 때보다 더욱 편안하게 디자인 패턴을 학습할 수 있습니다. ▶ 이 책을 읽어야 하는 당신! ● 소프트웨어 출시는 완벽 그 자체! “어?~ 코드 수정하려고 다시 보니까 난리…” → 유지보수만 생각하면 그저 눈물인 주니어 (자바) 개발자 ● 코딩 실력은 장판파의 장비! “어?~ 팩토리 메소드 패턴을 이렇게 적용했던가?” → 디자인 패턴을 다시 한번 살펴보고 싶은 시니어 (자바) 개발자 ● 혼자 공부해서 다진 프로그래밍 언어 실력! “어?~ 근데 패턴이 뭐야?” → 개발 현장의 소프트웨어 디자인 방법이 궁금한 개발자 지망생
저자
에릭 프리먼, 엘리자베스 롭슨, 케이시 시에라, 버트 베이츠
출판
한빛미디어
출판일
2022.03.16

 

헤드퍼스트 디자인 패턴 10장 | 상태 패턴 - 객체의 상태 바꾸기 

(에릭 프리먼 외 4인, 서환수 옮김, 한빛미디어)

 


 

 

4개의 상태

- 동전없음

- 동전있음

- 알맹이매진

- 알맹이판매 

 

인스턴스 변수를 만들고 각 상태의 값을 정의 

 

final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;

int state = SOLD_OUT;		=> 현재 상태를 저장하는 인스턴스 변수

동전투입, 동전반환, 손잡이 돌림, 알맹이 내보냄

=> 여기에 있는 행동들은 뽑기 기계의 인터페이스라고 할 수 있습니다. 

 

public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();

public void refill();

예를 들어, '동전 투입' 행동은 다음과 같은 메소드로 처리할 수 있죠. 

 

public void insertQuarter() {
   if (state == HAS_QUARTER) {
      System.out.println("You can't insert another quarter");
   } else if (state == NO_QUARTER) {
      state = HAS_QUARTER;
      System.out.println("You inserted a quarter");
   } else if (state == SOLD_OUT) {
      System.out.println("You can't insert a quarter, the machine is sold out");
   } else if (state == SOLD) {
           System.out.println("Please wait, we're already giving you a gumball");
   }
}

 

 

새로운 기능 추가 요청시 확장이 어려운 코드 

 

 

 

새로운 디자인 구상하기

 

기존 코드를 그대로 활용하는 대신 상태 객체들을 별도의 코드에 넣고, 어떤 행동이 일어나면 현재 상태 객체에서 필요한 작업을 처리하게 하는 거죠. 

 

1) 우선 뽑기 기계와 관련된 모든 행동에 관한 메소드가 들어있는 State 인터페이스를 정의해야 합니다.

 

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

 

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

 

- 428p 

 


State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

State state;

 

정적 변수를 사용하던 기존의 코드를 새로 만든 클래스를 사용하는 방식으로 수정합니다. 기존 클래스에는 정수를 사용했지만 이번에는 클래스를 사용한다는 점을 제외하면 크게 달라지진 않았습니다. 

 

=> 상태 객체를 생성하고 대입하는 작업은 생성자가 처리합니다.

=> 이제 정수가 아니라 상태 객체가 저장됩니다. 

 

 

- 432p 

 

 

 

public class NoQuarterState implements State {	
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {	=> 생성자로부터 뽑기 기계의 레퍼런스가 전달됩니다. 이 레퍼런스를 인스턴스 변수에 저장합니다.
        this.gumballMachine = gumballMachine;
    }
 
   public void insertQuarter() {
      System.out.println("You inserted a quarter");			=> 누군가 동전을 넣으면 동전이 투입되었다는 메시지를 출력하고 기계의 상태를 hasquaterstate로 전환합니다. 
      gumballMachine.setState(gumballMachine.getHasQuarterState());
   }

- 431p

 

 

 


public class GumballMachine {
 
   State soldOutState;
   State noQuarterState;
   State hasQuarterState;
   State soldState;
 
   State state;
   int count = 0;
 
   public GumballMachine(int numberGumballs) {
      soldOutState = new SoldOutState(this);
      noQuarterState = new NoQuarterState(this);
      hasQuarterState = new HasQuarterState(this);
      soldState = new SoldState(this);

      this.count = numberGumballs;
      if (numberGumballs > 0) {
         state = noQuarterState;
      } else {
         state = soldOutState;
      }
   }
 
   public void insertQuarter() {
      state.insertQuarter();
   }
 
   public void ejectQuarter() {
      state.ejectQuarter();
   }
 
   public void turnCrank() {
      state.turnCrank();
      state.dispense();
   }

=> 생성자 알맹이의 초기 개수를 인자로 받아서 인스턴스 변수에 저장합니다.

=> 그리고 state 인스턴스도 각각 하나씩 생성합니다. 

 

=> 알맹이 개수가 0개보다 많으면 state를 NoQuarterState로 설정합니다. 

 

=> 메소드 구현 -> 현재 상태가 작업을 처리하게 만듬 

 

- 433p 

 

 

뽑기 기계에는 상태 클래스의 인스턴스를 들어있도록 함

기계의 현재 상태는 NoQuarter, HasQuarter, Sold, Soldout 클래스들 중 하나

 

- 437p 

 

 

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

 - 440p 

 

 

 

보너스 알맹이 당첨 기능 추가하기

 

WinnerState를 추가하고 생성자 내에서 그 상태 객체를 초기화하는 코드만 추가하면 됩니다. 

 

WinnerState 구현 

 

 

public void dispense() {
   gumballMachine.releaseBall();
   if (gumballMachine.getCount() == 0) {
      gumballMachine.setState(gumballMachine.getSoldOutState());
   } else {
      gumballMachine.releaseBall();
      System.out.println("YOU'RE A WINNER! You got two gumballs for your quarter");
      if (gumballMachine.getCount() > 0) {
         gumballMachine.setState(gumballMachine.getNoQuarterState());
      } else {
               System.out.println("Oops, out of gumballs!");
         gumballMachine.setState(gumballMachine.getSoldOutState());
      }
   }
}

 

=> 알맹이 2개를 내보내고 NoQuarterState 또는 soldOutState로 가기 

 

=> 알맹이가 하나 더 있으면 내보내기 

 

- 443p 

 

 

 

 

데모 버전 돌려보기 

 

public static void main(String[] args) {
   GumballMachine gumballMachine = 
      new GumballMachine(10);

   System.out.println(gumballMachine);

   gumballMachine.insertQuarter();
   gumballMachine.turnCrank();
   gumballMachine.insertQuarter();
   gumballMachine.turnCrank();

   System.out.println(gumballMachine);

 

- 445p

 

 

 

반응형