본문 바로가기

디자인 패턴/디자인 패턴

[디자인패턴] 스테이트 패턴

책을 공부하며 새로운 문제를 만들고 구현한것을 기록한 게시글입니다.

(이해한 내용을 바탕으로 작성했기 때문에, 내용이 정확하지않고 틀린부분이 있을수있으며, 예시가 패턴을 사용하기에 알맞지 않을수도있습니다)


스테이트 패턴

객체의 상태에따라 객체의 행동이 변경된다. 객체를 사용하는 클라이언트 입장에서는 객체가 하는 행동이 완전히 바뀜으로써 자신이 호출하는 객체의 클래스가 바뀌는듯한 느낌을 받는다.

 

스트레티지 패턴과 상당히 유사한 패턴이다.(책에서도 이렇게 설명한다.)

스트레티지 패턴과의 차이점이라면, 우선, 사용 용도가 다르다는점이다. 스테이트 패턴은 상태에 따라서 행동을 동적으로 바꿔주기위해 사용하는 반면, 스트레티지패턴은 사용중 알고리즘군을 변경시켜주기위해 사용한다.

쉽게 생각해서 클라이언트가 객체의 행동을 변경시킨다면 이는 스테이트 패턴이 아니다. (이 외의 상황이 항상 스테이트 패턴은 아니라는것에 주의하자) 


문제

커피머신이 하나있다.

커피머신을 사용해서 커피를 만들수있는데, 커피머신은 똑같은 종류의 커피는 계속 만들수있으나 다른 종류의 커피를 만들기위해선 반드시 세척해야한다. 

 

커피머신으로 만들수있는 커피의 목록은 다음과 같다.

Americano

CafeLatte

ColdBrew

 

만들커피가 입력으로 주어졌을때, 커피머신의 진행상황을 출력하는 프로그램을 만들어보자.


구현

구현중 스트레티지 패턴과 스테이트패턴이 함께 나오므로 잘 구분하도록하자

 

우선, 각 커피가 하는 행동을 캡슐화 해보자.

커피머신은 drip()메소드가 호출되면 상황에 맞는 커피를 추출한다고 가정하고, drip행동을 총괄하는 인터페이스 Drip을 만들자.

package prob10_CoffeeMachine.Coffee;

public interface Drip {
    public void drip();
}

이제 Drip을 구현하는 커피 클래스들을 만들어보자.

package prob10_CoffeeMachine.Coffee;

public class Americano implements Drip{
    public void drip(){
        System.out.println("Americano!");
    }
}
package prob10_CoffeeMachine.Coffee;

public class CafeLatte implements Drip{
    public void drip(){
        System.out.println("CafeLatte!");
    }
}
package prob10_CoffeeMachine.Coffee;

public class ColdBrew implements Drip{
    public void drip(){
        System.out.println("ColdBrew!");
    }
}

이렇게 디자인함으로써,  이후에 커피가 추가되더라도 코드을 최소화하며 프로그램을 확장시킬수있다. 

여기까지는 스트레티지 패턴이였다. 

만약 우리가 아메리카노에서 카페라떼로 커피종류를 변경하고싶다면, 커피머신을 닦아야한다. 즉, 커피머신의 상태에따라 행동을 변경해줘야하는것이다.

현재상태에 따른 커피머신의 행동을 주관하는 인터페이스 CoffeeMachineState를 만들자.

package prob10_CoffeeMachine;

public interface CoffeeMachineState {
    public void cleaner(); // 커피머신을 세척하는 메소드이다.
    public boolean drip(); // 커피머신의 상태를 주관하는 클래스가 커피추출에 영향을 미칠것이다.
}

커피머신의 행동에는 머신을 세척하는 cleaner와 커피를 추출하는drip이 있을것이다.

 

이제, CoffeeMachineState를 구현하는 CleanState, DirtyState클래스를 만들자

package prob10_CoffeeMachine;

public class DirtyState implements CoffeeMachineState{
    
    private CoffeeMachine coffeeMachine;
    
    public DirtyState(CoffeeMachine coffeeMachine){
        this.coffeeMachine = coffeeMachine;
    }
    
    public void cleaner(){
        System.out.println("커피머신을 닦는중입니다..!");
        coffeeMachine.setState(new CleanState(this.coffeeMachine)); // coffeMachine의 상태를 Clean으로 변경한다.
    }
    
    public boolean drip(){
        System.out.println("커피머신이 더럽습니다..!"); // 더러운 상태에서는 커피를 만들수없다.
        return false;
    }
    
}

package prob10_CoffeeMachine;

public class CleanState implements CoffeeMachineState{

    private CoffeeMachine coffeeMachine;

    public CleanState(CoffeeMachine coffeeMachine){
        this.coffeeMachine = coffeeMachine;
    }
    
    public void cleaner(){
        System.out.println("커피머신이 이미 깨끗합니다."); // 커피머신이 깨끗한 상태에서는 닦을필요가없다.
    }
    
    public boolean drip(){
        return true; // 커피머신이 깨끗하므로, drip을 허용한다.
    }
    
}

coffemachine에는 커피를 추출하는 drip메소드가 있고, 이는 상태에따라서 호출될수도 안될수도있다.

커피머신의 코드를 보자.

package prob10_CoffeeMachine;

import prob10_CoffeeMachine.Coffee.*;

public class CoffeeMachine {
    
    private CoffeeMachineState state;
    private Drip drip;
    
    public CoffeeMachine(){
        state = new CleanState(this);
        drip = new Americano();
    }
    
    public void setState(CoffeeMachineState state){
        this.state = state;
    }
    
    public void changeCoffee(Drip drip){
        if(drip.getClass() == this.drip.getClass()) return; // 같은커피라면 바꾸지않는다.
        this.drip = drip;
        setState(new DirtyState(this));
    }
    
    public void drip(){
        if(this.state.drip()) drip.drip(); // 깨끗하다면 추출한다
        else this.state.cleaner(); // 더럽다면 닦는다.
    }
    
}

이제 메인스레드를 만들어서 실행시켜보자. 

package prob10_CoffeeMachine;

import java.io.*;
import prob10_CoffeeMachine.Coffee.*;

public class Main {
    
    public static void main(String[] args){
        CoffeeMachine coffeeMachine = new CoffeeMachine();
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            try{
                String coffee = bf.readLine();
                Drip drip = smallFactory(coffee);
                if(drip == null) continue;
                coffeeMachine.changeCoffee(drip);
                coffeeMachine.drip();
            }catch (IOException ioe){}
        }
    }
    
    public static Drip smallFactory(String str){
        if(str.equals("Americano")) return new Americano();
        if(str.equals("CafeLatte")) return new CafeLatte();
        if(str.equals("ColdBrew")) return new ColdBrew();
        return null;
    }
    
}

실행시켜보면, 잘 동작하는것을 볼수있다.

 


소스코드

https://github.com/devxb/DesignPatterns/tree/main/DesignPatterns/prob10_CoffeeMachine

 

devxb/DesignPatterns

디자인 패턴 🤔. Contribute to devxb/DesignPatterns development by creating an account on GitHub.

github.com