본문 바로가기
카테고리 없음

자바 상태(State) 패턴

by 방구석개발자 2022. 11. 8.
반응형

설명

상태를 객체로 만들어서 각 상태 별 동작을 처리하는 패턴입니다.

클라이언트 코드가 직접 호출하는 Context class에서 상태별로 다른 State 구현객체를 사용하여 상태별로 다른 처리가 가능하도록 합니다.

 

구성요소

💡 - State Interface
      - State Concrete Class : State Instance (implement each State Concrete Class)
      - Context Class
  • 상태 Interface : 기능만 명세화
  • 상태 구현클래스 (Concrete Class) : 각 상태별 기능을 구현
  • 컨텍스트 클래스 : 클라이언트 코드에서 직접 호출하는 클래스
    • 로직구현코드가 없고, 상태객체를 호출하여 기능을 수행 → 클라이언트 코드에게 결과를 반환한다.
    • 필드로 상태 객체 를 가진다.
    • 클라이언트 코드(호출하는쪽)가 기능요청 → 상태객체를 활용하여 기능을 처리한다.

왜 사용해야 하는지

  • 조건문을 사용하는 코드를 덜쓰려고
  • 새론운 상태에 대한 처리가 추가되었을 때, 새로운 상태객체를 구현해서 처리할 수 있기때문에 편함 (안그럼 조건문마다 분기추가해서 코드변경해야됨, 기존 로직을 건드릴 가능성 존재)

예시

티머니 충전기능을 개발해보겠습니다.

티머니의 상태를 인터페이스로 만들었습니다.

public interface State {

    void chargeTmoney(int balance, int charging);
    void useTmoney(int balance);
    void transfer(int balance);
}

그다음 각각 사용가능한티머니, 사용불가능한 티머니 객체를 만듭니다.

public class UnUsableTmoney implements State {
    Tmoney tmoney;

    public UnUsableTmoney(Tmoney tmoney) {
        this.tmoney = tmoney;
    }

    /**
     * 충전 후 잔액 0원 초과
     * 사용불가 -> 사용가능
     */
    @Override
    public void chargeTmoney(int balance, int charging) {
        int bal = balance + charging;
        System.out.println("================ 사용불가능한 카드 [충전중...] ================");
        System.out.println(String.valueOf(charging) + "원 충전합니다");
        System.out.println("잔액 : " + String.valueOf(bal));

        this.tmoney.setBalance(bal);
        this.tmoney.setState(bal > 0 ? tmoney.usable :  tmoney.unUsable);
    }

    /**
     * 잔액 0원 : 사용 불가능
     */
    @Override
    public void useTmoney(int balance) {
        System.out.println("================ 사용불가능한 카드 [사용시도] ================");
        System.out.println("잔액이 부족합니다.");
        this.tmoney.setBalance(balance);
        this.tmoney.setState(tmoney.unUsable);
    }
    /** 중략 **/
}

public class UsableTmoney implements State {

    Tmoney tmoney;

    public UsableTmoney(Tmoney tmoney) {
        this.tmoney = tmoney;
    }

    /**
     * 사용가능 -> 사용가능
     */
    @Override
    public void chargeTmoney(int balance, int charging) {
        int bal = balance + charging;
        System.out.println("================ 사용가능한 카드 [충전중......] ================");
        System.out.println(String.valueOf(charging) + "원 충전합니다");
        System.out.println("잔액 : " + String.valueOf(bal));
        this.tmoney = new Tmoney(bal);
        this.tmoney.setState(tmoney.usable);
    }

    /**
     * 사용 후 잔액 0원 : 사용불가
     */
    @Override
    public void useTmoney(int balance) {
        int bal = balance - 1000;
        System.out.println("================ 사용가능한 카드 [사용] ================");
        System.out.println("1000 원 사용합니다");
        System.out.println("잔액 : " + String.valueOf(bal));

        this.tmoney.setBalance(bal);
        this.tmoney.setState(bal > 0 ? tmoney.usable : tmoney.unUsable);
    }
    /** 중략 **/
}

그다음 티머니 객체에서 상태값들을 위임받아서 사용합니다.

public class Tmoney {

    UsableTmoney usable;
    UnUsableTmoney unUsable;

    State state;

    int balance = 0;

    public Tmoney(int balance) {
        this.balance = balance;
        this.usable = new UsableTmoney(this);
        this.unUsable = new UnUsableTmoney(this);
        this.state = this.balance > 0 ? this.usable : this.unUsable;
    }

    /**
     * 상태를 바꾼다.
     * State의 구현체면 상태객체로 사용가능
     */
    void setState(State state) {
        this.state = state;
    }

    public void setBalance(int bal) {
        this.balance = bal;
    }

    /**
     * 버스를 탄다
     * 잔액이 있으면 : 사용가능, 잔액차감
     * 잔액이 없으면 : 사용불가, 충전필요
     */
    public void takeBus() {
        if(this.balance > 0) {
            this.usable.useTmoney(this.balance);
        } else {
            this.unUsable.useTmoney(this.balance);
        }
    }

    /**
     * 충전을 한다
     * 충전 후 잔액에 따라 상태변경
     * todo. 어떤걸 호출하던, 충전 후 잔액 > 0 이어야만 useable
     */
    public void charging(int charging) {
        if(this.balance > 0) {
            usable.chargeTmoney(this.balance,charging);
        } else {
            unUsable.chargeTmoney(this.balance,charging);
        }
    }


    /**
     * 환승을 한다
     */
    public void transferTo(){
        state.transfer(this.balance);
        this.state = this.balance > 0 ? usable : unUsable;
    }
}

이제 클라이언트에서 실행하면 됩니다.

public class UsingTmoney {

    public static void main(String[] args) {
        Tmoney tmoney = new Tmoney(2000); // 2번 사용가능

        tmoney.takeBus();
        tmoney.transferTo();
        tmoney.takeBus();
        tmoney.transferTo();
        tmoney.takeBus();

        tmoney.takeBus();

        tmoney.charging(1000);
        tmoney.takeBus();


    }
}

 

design-patterns-ex/src/patterns/structural/state/tmoney at master · YINAKIM/design-patterns-ex

 

GitHub - YINAKIM/design-patterns-ex: 디자인패턴 스터디

디자인패턴 스터디. Contribute to YINAKIM/design-patterns-ex development by creating an account on GitHub.

github.com

 

티머니 충전상태

상태변화 동작 조건 실행 결과
돈X → 돈O 티머니충전 충전금액 없음이면 충전금액 증가 충전금액 사용가능
돈O → 돈O 티머니충전 충전금액 사용가능 충전금액 증가 충전금액 사용가능
돈X → 돈X 티머니 사용 충전금액 없음이면 사용불가(충전필요) 충전금액 없음 유지
돈O → 돈O 티머니 사용 충전금액 사용가능 티머니사용 , 충전금 차감 충전금 있으면 사용가능(금액사용)
충전금 없으면 사용불가(충전필요)
💡 추가요청사항 환승할인 하는 경우 : 티머니 사용 → 충전금 차감 0원
      현재 상태에 따라, 기능이 다르게 동작하는 패턴 (코드에서 조건문 사용하게 됨)

참고

https://refactoring.guru/ko/design-patterns/state

 

상태 패턴

/ 디자인 패턴들 / 행동 패턴 상태 패턴 다음 이름으로도 불립니다: State 의도 상태 패턴은 객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니

refactoring.guru

 

반응형

댓글