디자인 패턴 - 상태 패턴(State Pattern)

SeungTaek·2021년 12월 24일
0
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!

📒 상태 패턴

  • 전략 패턴과 유사한 구조를 가진다.
  • 추상화한 인터페이스와 해당 인터페이스를 구현한 클래스(상태 객체)를 만들고, 컨텍스트(context)는 상태 객체에 처리를 위임하는 방식으로 구현된다.

코드를 통해 상태 패턴을 알아보자.


📌 상태 패턴 사용 전

public class VendingMachine {
    public static enum State {NOCOIN, SELECTABLE}

    private State state = State.NOCOIN;

    public void insertCoin(int coin) {
        switch (state) {
            case NOCOIN:
                // 로직
            case SELECTABLE:
                // 로직
        }
    }
  
   public void changeState(State state){
       this.state=state;
   }
}
  • 자판기는 현재 상태(NOCOIN, SELECTABLE)에 따라 insertCoin(coin)의 로직이 달라진다.
  • 만약 상태가 추가된다면 변경을 어떻게 해야할까?
    1. enum State에 새로운 상태를 추가한다.
    2. insertCoint(coin) 로직에 switch case를 추가한다.

맙소사.. 상태를 추가하거나 로직을 변경할 때마다 코드를 변경해야 하다니?
심지어 switch 문이 길어진다고?



📌 상태 패턴 사용 후

public class VendingMachine {
    private State state;

    public VendingMachine() {
        state = new NoCoinState();
    }

    public void insertCoin(int coin) {
        state.increaseCoin(coin, this); // 상태 객체에게 코드 구현을 위임
    }

    public void changeState(State state) {
        this.state=state;
    }
}
public interface State {
    public void increaseCoin(int coin, VendingMachine vm);
}


public class NoCoinState implements State {
    @Override
    public void increaseCoin(int coin, VendingMachine vm) {
        //로직 ...
        vm.changeState(new SelectableState());
    }
}

만약 자판기에 청소 상태 구현을 위해 State가 추가되었더라면?

CleaningState 클래스를 추가하기만 하면 된다. 그럼 VendingMachine 클래스의 코드는 그대로 유지된다.

또한 상태에 따른 로직의 변경이 일어나더라도 해당 상태의 클래스만 변경해주면 된다. VendingMachine 클래스는 건들 필요없다.

물론 단점도 존재한다. 클래스 개수가 증가하기 때문에 상태 구조를 모른다면 유지보수가 어려울 수 있다.



📒 상태 변경은 누가 하는가?

위 코드를 다시 보자

public class NoCoinState implements State {
    @Override
    public void increaseCoin(int coin, VendingMachine vm) {
        //로직 ...
        vm.changeState(new SelectableState()); //컨텍스트의 상태 변경
        // 만약 컨텍스트 자체에서 상태 변경을 한다면 VendingMachine을 인자로 받지 않아도 된다.
    }
}

상태 객체가 VendingMachine을 인자로 받아 상태 변경을 하고 있다.

컨텍스트의 상태를 변경할 때, 컨텍스트의 다른 값에 접근해야 할 때도 있다.

따라서 컨텍스트의 메서드를 public으로 추가해야 한다. (예를 들면 동전 개수에 접근하는 getCoin() 메서드 추가)

하지만 컨텍스트 자체가 상태를 변경한다면? 메서드를 추가해야 하는것은 마찬가지지만 priavte으로 선언 가능하다. (간단한 필드 접근이라면 메서드도 필요 없다.)

이처럼 컨텍스트의 상태 변경을 누가 할지는 주어진 상황에 알맞게 정해 주어야 한다.

컨텍스트 자체에서 상태 변경이 유리한 경우는 다음과 같다.

  • 상태 개수가 적은 경우
  • 상태 변경 규칙이 거의 바뀌지 않은 경우

반면 상태 객체에서 컨텍스트의 상태를 변경하는 경우 컨텍스트에 영향을 주지 않으면서 상태를 추가하거나 상태 변경 규칙을 바꿀 수 있다는 장점이 있다.



📒 전략 패턴과의 차이점

전략 패턴과 아주 유사하다.

차이점은 객체의 상태 속성을 변환하는데 상태 패턴은 스스로 변환 할 수 있지만, 전략 패턴은 외부에서 새로운 상태의 주입이 필요하다.

또한 전략 패턴은 하나의 특정 작업만 처리하는 반면 상태 패턴은 컨텍스트 개체가 수행하는 대부분의 모든 것에 대한 기본 구현을 제공한다.

만약 VendingMachine 클래스를 전략 패턴으로 작성했다면 다음과 같을 것이다.

public class VendingMachine {
    private State state;

    public VendingMachine(State state) { //생성 시 상태 결정. 변경 불가
        this.state = state;
    }

    public void insertCoin(int coin) {
        state.increaseCoin(coin, this); // 상태 객체에게 코드 구현을 위임하는건 동일
    }
}

또는

public class VendingMachine {
    public void insertCoin(int coin, State state) { // 메서드 사용시 계속 파라미터로 넘겨주기
        state.increaseCoin(coin, this); // 상태 객체에게 코드 구현을 위임하는건 동일
    }
}


reference

개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴(최범균)

profile
I Think So!

0개의 댓글