Design Pattern | State Pattern (스테이트 패턴)


State 패턴이란?

  • State라는 영어 단어는 상태라는 의미이다.
  • State 패턴은 상태를 클래스로 표현하고 클래스를 전환하여 “상태 변경"을 나타내는 방법이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

State 패턴의 예제 프로그램

낮, 밤의 상태에 따라 버튼의 동작을 바꾸는 금고 관리 프로그램이다.

Class Diagram
State Pattern Class Diagram

1. Context 인터페이스

금고의 상태 변화를 관리하여 보안 센터와의 연락을 하는 인터페이스이다.

Context.java

package com.devkuma.designpattern.behavioral.state;

public interface Context {
    // 시간 설정
    void setClock(int hour);

    // 상태 변화
    void changeState(State state);

    // 보안센터 보안요원 호출
    void callSecurityCenter(String msg);

    // 보안 센터 기록
    void recordLog(String msg);
}

2. SafeFrame 클래스

Context 인터페이스를 구현하는 클래스이다. 버튼이나 화면 표시 등의 UI를 가진다.

SafeFrame.java

package com.devkuma.designpattern.behavioral.state;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SafeFrame extends Frame implements ActionListener, Context {

    private TextField textClock = new TextField(60);
    private TextArea textScreen = new TextArea(10, 60);
    private Button buttonUse = new Button("금고 사용");
    private Button buttonAlarm = new Button("비상벨");
    private Button buttonPhone = new Button("일반 통화");
    private Button buttonExit = new Button("종료");

    private State state = DayState.getInstance();

    public SafeFrame(String title) {
        super(title);

        setBackground(Color.lightGray);
        setLayout(new BorderLayout());
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);
        add(textScreen, BorderLayout.CENTER);
        textScreen.setEditable(false);

        Panel panel = new Panel();
        panel.add(buttonUse);
        panel.add(buttonAlarm);
        panel.add(buttonPhone);
        panel.add(buttonExit);
        add(panel, BorderLayout.SOUTH);

        pack();
        setVisible(true);

        buttonUse.addActionListener(this);
        buttonAlarm.addActionListener(this);
        buttonPhone.addActionListener(this);
        buttonExit.addActionListener(this);
    }

    // 버튼을 누르는 이벤트가 발생하면 여기 메소드가 실행된다.
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        if (e.getSource() == buttonUse) {
            // 금고 사용 버튼
            state.doUse(this);
        } else if (e.getSource() == buttonAlarm) {
            // 비상벨 버튼
            state.doAlarm(this);
        } else if (e.getSource() == buttonPhone) {
            // 일반 통화 버튼
            state.doPhone(this);
        } else if (e.getSource() == buttonExit) {
            // 종료 버튼
            System.exit(0);
        } else {
            System.out.println("?");
        }
    }

    // 시간 설정
    public void setClock(int hour) {
        String clockstring = "현재 시간은 ";
        if (hour < 10) {
            clockstring += "0" + hour + ":00";
        } else {
            clockstring += hour + ":00";
        }
        System.out.println(clockstring);
        textClock.setText(clockstring);
        state.doClock(this, hour);
    }

    // 상태 변화
    public void changeState(State state) {
        System.out.println(this.state + "에서 " + state + "로 상태가 변경되었습니다.");
        this.state = state;
    }

    // 경비센터 경비원 호출
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }

    // 보안 센터 기록
    public void recordLog(String msg) {
        textScreen.append("record... " + msg + "\n");
    }
}

3. State 인터페이스

금고의 상태를 나타내는 인터페이스이다.

State.java

package com.devkuma.designpattern.behavioral.state;

public interface State {
    // 시간 설정
    void doClock(Context context, int hour);

    // 금고 사용
    void doUse(Context context);

    // 비상벨
    void doAlarm(Context context);

    // 일반 통화
    void doPhone(Context context);
}

4. DayState 클래스

State 인터페이스를 구현하는 클래스이다. 낮 상태를 나타낸다.

DayState.java

package com.devkuma.designpattern.behavioral.state;

public class DayState implements State {
    private static DayState singleton = new DayState();

    private DayState() {
    }

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour < 9 || 17 <= hour) {
            context.changeState(NightState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.recordLog("금고사용(낮)");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("비상 벨(낮)");
    }

    @Override
    public void doPhone(Context context) {
        context.callSecurityCenter("일반 통화(낮)");
    }

    @Override
    public String toString() {
        return "[낮]";
    }
}

5. NightState 클래스

State 인터페이스를 구현하는 클래스이다. 야간 상태를 나타낸다.

NightState.java

package com.devkuma.designpattern.behavioral.state;

public class NightState implements State {
    private static NightState singleton = new NightState();

    private NightState() {
    }

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.callSecurityCenter("비상: 야간 금고 사용!");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("비상 벨(야간)");
    }

    @Override
    public void doPhone(Context context) {
        context.recordLog("야간 통화 녹음");
    }

    @Override
    public String toString() {
        return "[야간]";
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.state;

public class Main {

    public static void main(String[] args) {
        SafeFrame frame = new SafeFrame("State Sample");
        while (true) {
            for (int hour = 0; hour < 24; hour++) {
                frame.setClock(hour);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

7. 실행 결과

State Pattern Class Result

State 패턴의 장점

분할하고 통합하라는 방침은 프로그래밍에 자주 등장한다.
State 패턴에서는 상태를 클래스로 표현한다. 개별의 구체적인 상태를 별개의 클래스로서 표현하여 분할을 실행하고 있다. DayState를 구현하고 있는 동안 프로그래머는 다른 클래스(NightState)를 의식할 필요는 없어진다. 예제 프로그램에서는 상태가 2개 밖에 없지만, 상태가 더 늘어났을 경우에 State 패턴은 강점을 더 발휘한다.