JUINTINATION

스테이트(State) 패턴 본문

JAVA객체지향디자인패턴

스테이트(State) 패턴

DEOKJAE KWON 2023. 7. 6. 11:43
반응형

스테이트 패턴이란?

시스템의 각 상태를 클래스로 분리하고 각 클래스에서 수행하는 행위들을 메서드로 구현하여 어떤 행위를 수행할 때 상태에 행위를 수행하도록 위임하는 패턴이다.

스테이트 패턴과 스트래티지 패턴

  • 공통점
    • 행위를 클래스로 캡슐화
    • 연관 관계를 이용해 행위를 구현한 클래스에 실제 작업을 위임
    • 실행 중 행위 변경 가능
  • 차이점
    • 스트래티지 패턴
      • 기능 수행 전략을 캡슐화
      • Client가 Context에 사용할 Strategy 지정
    • 스테이트 패턴
      • 객체의 상태를 캡슐화
      • Client가 Context의 State를 지정해주지 않음

위와 같은 상태 머신 다이어그램을 갖는 형광등이 있다고 하자. 해석해보면 다음과 같다.

  • 형광등은 기본적으로 OFF 상태에서 시작한다.
  • OFF 상태에서 사용자가 on 버튼을 누르면 ON 상태로 진입한다.
  • OFF 상태에서 사용자가 off 버튼을 누르면 아무일도 일어나지 않는다.
  • ON 상태에서 사용자가 off 버튼을 누르면 OFF 상태로 진입한다.
  • ON 상태에서 사용자가 on 버튼을 누르면 아무일도 일어나지 않는다.

코드로 표현하면 다음과 같이 작성할 수 있겠다.

class Light {
    private final static int ON = 1, OFF = 0;
    private int state;
    public Light() {
        state = OFF;
    }
    public void on_button_pushed() {
        if (state == ON) {
            System.out.println("아무일도 일어나지 않음");
        } else {
            System.out.println("Light ON");
            state = ON;
        }
    }
    public void off_button_pushed() {
        if (state == OFF) {
            System.out.println("아무일도 일어나지 않음");
        } else {
            System.out.println("Light OFF");
            state = OFF;
        }
    }
}

형광등에 취침등 상태를 추가한 상태 머신 다이어그램이다. ON 상태에서 on 버튼을 누르면 SLEEPING 상태로 진입하고 SLEEPING 상태에서 on 버튼을 누르면 ON 상태로 진입한다. 이렇게 되면 다음과 같이 코드를 수정해야 한다.

class Light {
    private final static int SLEEPING = 2, ON = 1, OFF = 0;
    private int state;
    public Light() {
        state = OFF;
    }
    public void on_button_pushed() {
        // state가 ON일 때 on 버튼을 눌르면 SLEEPING 상태로 진입
        if (state == ON) {
            System.out.println("SLEEPING");
            state = SLEEPING;
        } else {
            System.out.println("Light ON");
            state = ON;
        }
    }
    public void off_button_pushed() {
        if (state == OFF) {
            System.out.println("아무일도 일어나지 않음");
        } else {
            System.out.println("Light OFF");
            state = OFF;
        }
    }
}
  • 위 코드의 문제점
    • 형광등에 취침등 상태 등 다른 상태를 추가하려면 기존 코드를 수정해야 한다.
      • 유지 보수의 어려움
      • OCP 위반

이를 해결하기 위해 다음 클래스 다이어그램과 같이 상태를 캡슐화한다.

interface State {
    void on_button_pushed(Light light);
    void off_button_pusehd(Light light);
}

class ON implements State {
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("SLEEPING");
        light.setState(new SLEEPING());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("OFF");
        light.setState(new OFF());
    }
}

class OFF implements State {
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("ON");
        light.setState(new ON());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("아무일도 일어나지 않음");
    }
}

class SLEEPING implements State {
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("ON");
        light.setState(new ON());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("OFF");
        light.setState(new OFF());
    }
}

class Light {
    private State state;
    public Light() {
        setState(new OFF());
    }
    public void on_button_pushed() {
        state.on_button_pushed(this);
    }
    public void off_button_pushed() {
        state.off_button_pusehd(this);
    }
    public void setState(State state) {
        this.state = state;
    }
}

또한 상태를 바꿀 때마다 객체를 새로 만들 필요는 없기 때문에 싱글턴 패턴을 적용하면 다음과 같이 코드를 수정할 수 있다.

interface State {
    void on_button_pushed(Light light);
    void off_button_pusehd(Light light);
}

class ON implements State {
    private static ON instance = new ON();
    private ON() {}
    public static ON getInstance() {
        return instance;
    }
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("SLEEPING");
        light.setState(SLEEPING.getInstance());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("OFF");
        light.setState(OFF.getInstance());
    }
}

class OFF implements State {
    private static OFF instance = new OFF();
    private OFF() {}
    public static OFF getInstance() {
        return instance;
    }
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("ON");
        light.setState(ON.getInstance());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("아무일도 일어나지 않음");
    }
}

class SLEEPING implements State {
    private static SLEEPING instance = new SLEEPING();
    private SLEEPING() {}
    public static SLEEPING getInstance() {
        return instance;
    }
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("ON");
        light.setState(ON.getInstance());
    }
    @Override
    public void off_button_pusehd(Light light) {
        System.out.println("OFF");
        light.setState(OFF.getInstance());
    }
}

class Light {
    private State state;
    public Light() {
        setState(OFF.getInstance());
    }
    public void on_button_pushed() {
        state.on_button_pushed(this);
    }
    public void off_button_pushed() {
        state.off_button_pusehd(this);
    }
    public void setState(State state) {
        this.state = state;
    }
}

스테이트 패턴 컬레보레이션

 

State : 시스템의 모든 상태에 공통의 인터페이스, 기존 상태 클래스를 대신해 교체 사용 가능

State1, State2, ... : Context 객체가 요청한 작업을 각 상태에 맞게 실행하는 클래스, 대부분의 경우 다음 상태를 결정해 Context에 setState 메서드를 요청하는 역할도 수행

Context : State를 이용하는 클래스

 

 


스테이트 패턴을 이용한 간단한 예제

다음은 도서관에 보관된 책의 대출과 예약에 관한 설명이다.

  • 책은 처음에 누구나 대출할 수 있다.
  • 책이 도서관에서 체크아웃 되면 대출 중 상태로 바뀐다.
  • 책이 대출 중인 상태에 있을 때만 예약할 수 있다.
  • 예약 중인 상태의 책이 반환되면 책은 예약자에게 대출해주기 위해 일정 기간 보관한다.
  • 책이 보관 중인 상태에 있을 때 예약을 취소하거나 보관 기간이 지나면 누구나 책을 대출할 수 있다.
  • 대출 중인 책이 반환되면 다시 누구나 대출할 수 있다.
  • 책이 대출 중이거나 보관 중일 때는 예약을 취소할 수 있다.

클래스 다이어그램

상태 머신 다이어그램

소스코드

interface State {
    void checkout(Book book);
    void reserve(Book book);
    void cancel(Book book);
    void timeout(Book book);
    void turnIn(Book book);
}

class Available implements State {

    private static Available available = new Available();

    private Available() {}

    public static Available getAvailable() {
        return available;
    }

    @Override
    public void checkout(Book book) {
        System.out.println("Available.checkout");
        book.setState(OnLoan.getOnLoan());
    }

    public void reserve(Book book) {} // 대출중인 상태가 아니므로 예약 불가

    public void cancel(Book book) {} // 예약중인 상태가 아니므로 취소 불가

    public void timeout(Book book) {} // 보관중인 상태가 아니므로 시간 초과 불가

    public void turnIn(Book book) {} // 대출중인 상태가 아니므로 반납 불가
}

class OnLoan implements State {

    private static OnLoan onLoan = new OnLoan();

    private OnLoan() {}

    public static OnLoan getOnLoan() {
        return onLoan;
    }

    public void checkout(Book book) {} // 대출중인 상태이므로 대출 불가

    @Override
    public void reserve(Book book) {
        System.out.println("OnLoan.reserve");
        book.setState(Reserved.getReserved());
    }

    public void cancel(Book book) {} // 예약중인 상태가 아니므로 취소 불가

    public void timeout(Book book) {} // 보관중인 상태가 아니므로 시간 초과 불가

    @Override
    public void turnIn(Book book) {
        System.out.println("OnLoan.turnIn");
        book.setState(Available.getAvailable());
    }
}

class Reserved implements State {

    private static Reserved reserved = new Reserved();

    private Reserved() {}

    public static Reserved getReserved() {
        return reserved;
    }

    public void checkout(Book book) {} // 에약된 상태이므로 대출 불가

    public void reserve(Book book) {} // 예약된 상태이므로 예약 불가

    @Override
    public void cancel(Book book) {
        book.setState(OnLoan.getOnLoan());
    }

    public void timeout(Book book) {} // 보관중인 상태가 아니므로 시간 초과 불가

    @Override
    public void turnIn(Book book) {
        book.setState(Kept.getKept());
    }
}

class Kept implements State {

    private static Kept kept = new Kept();

    private Kept() {}

    public static Kept getKept() {
        return kept;
    }

    @Override
    public void checkout(Book book) {
        System.out.println("Kept.checkout");
        book.setState(OnLoan.getOnLoan());
    }

    public void reserve(Book book) {} // 보관중인 상태이므로 예약 불가

    @Override
    public void cancel(Book book) {
        System.out.println("Kept.cancel");
        book.setState(Available.getAvailable());
    }

    @Override
    public void timeout(Book book) {
        System.out.println("Kept.timeout");
        book.setState(Available.getAvailable());
    }

    public void turnIn(Book book) {} // 보관중인 상태이므로 반납 불가
}

class Book {
    private State state;

    public Book(State state) {
        this.state = state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void checkout() {
        state.checkout(this);
    }

    public void reserve() {
        state.reserve(this);
    }

    public void cancel() {
        state.cancel(this);
    }

    public void timeout() {
        state.timeout(this);
    }

    public void turnIn() {
        state.turnIn(this);
    }
}
728x90

'JAVA객체지향디자인패턴' 카테고리의 다른 글

옵서버(Observer) 패턴  (0) 2023.07.06
커맨드(Command) 패턴  (0) 2023.07.06
스트래티지(Strategy) 패턴  (0) 2023.07.06
싱글턴(Singleton) 패턴  (0) 2023.07.05
디자인패턴 개요  (0) 2023.07.03
Comments