CS/디자인패턴

[디자인패턴] State 패턴 (상태 패턴)

mabb 2023. 6. 24. 18:59
반응형

▶디자인 패턴 ( Design Pattern)

:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four) 라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 

▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.

 

---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶State 패턴

"상태를 클래스로 표현"
"복잡한 상태에 의한 처리들을 각각의 상태로 분리하여 정복"
"상태에 따른 처리는 각각의 State에서"
"상태전환"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스

▶State 패턴에 대한 이해

-State는 상태를 의미한다

-상태를 클래스로 표현하는 방법이다.

-특정 기능이 상태에 따라 달라질 때 사용한다.

-State패턴을 사용하지 않으면 각각의 기능을 수행하는 메서드 내부에 상태에 따라 다르게 동작하는
분기를 만들게 된다. 상태가 추가될 경우 각각의 기능을 다 수정해야 한다. 이는 확장에 용이하지 않다.

-State패턴을 이용하면 각각의 구체적인 상태를 나타내는 클래스는 해당 상태에서 수행할 기능만 구현하면 된다. 

-State 패턴을 사용할 맥락이 되는 클래스를 Context라 칭한다.

-Context는 State의 필드를 가진다

-Context가 가지고 있는 State 필드에 대입되는 구체적 State가 전환되는 것을 <상태전환>이라 칭한다.

-상태전환을 각각의 구체적 State에서 하는 것과 Context에서 하는 것에는 장단점이 있다.

-상태전환을 위해 Mediator 패턴을 적용하는 것이 좋을 수 있다.
  
-State패턴을 사용하면 메소드 안에 묻혀있는 상태에 대한 분기를 외부로 표현하게 된다.

-State는 상태에 의존한 처리를 하는 메서드의 집합이다.

-각각의 State는 싱글톤 패턴으로 생성하여 메모리 낭비를 방지한다. 

-Divide and Conquer 방침에 부합한다. 각각의 상태를 분리하여 각각의 상태가 처리할 내용을 작성한다.

 

▶왜 사용하는가

샹태에 의한 처리를 위해 사용한다. 각각의 상태를 클래스로 표현한다. Context에서는 State에 해당하는 필드를 가지고 기능 수행을 위임하는데, 이 필드가 상태에 따라 교체되면  자연스럽게 각각의 상태에 따른 동작을 하게 된다. 메서드 내부에서 상태에 따라 분기를 할 경우 새로운 상태의 추가나 기능의 추가등 확장에 용이하지 못하다.

 

▶클래스다이어그램

State패턴의 클래스다이어그램

 

▶각 클래스(인터페이스)의 역할

▷State 역할 - State.java

상태들의 인터페이스이다. 상태에 따라 달라지는 메소드들의 집합을 정한다.

▷Concrete State 역할

상태에 따라 달라지는 처리를 구체적으로 구현한다. 예제에서는 본인의 상태가 아닌 경우 다른 상태로 전환하는 상태전환도 수행하고 있으며 Context에 정의된 기능을 토대로 구체적인 기능을 작성하였다.

NightState.java

야간 상태일 때 구체적인 처리 방법을 정한다.

DayState.java

주간 상태일 때 구체적인 처리 방법을 정한다.

▷Context 역할 

State패턴을 사용하는 전체적인 맥락, 문맥이 되는 클래스이다
상태전환 및 구체적인 상태들에서 사용할 수 있는 기능들을 정의한다.

▷Concrete Context 역할 - SafeFrame.java 

상태전환 및 구체적인 상태들에서 사용할 수 있는 기능들을 구체적으로 처리한다.

▷Main 역할

Context 역할을 실행시키는 역할을 한다.

 

 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고 하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/)

예제에서는 시간에 의존하는 금고의 기능 처리에 대해 다루고 있다. 주간과 야간이라는 두 가지 상태의 변화에 따라 금고의 기능은 다르게 동작한다. 상태 변화는 각각의 ConcreteState에서 수행하고 있다. 

 

▷State 역할 - State.java

package main.java.designpattern.state;

public interface State {
    public abstract void doClock(Context context, int hour);
    public abstract void doUse(Context context);
    public abstract void doAlarm(Context context);
    public abstract void doPhone(Context context);
}

 

▷Concrete State 역할

NightState.java

package main.java.designpattern.state;

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

    private NightState(){}
    public static State getInstance(){
        return night;
    }
    @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.recordiLog("야간통화 녹음");
    }
    @Override
    public String toString(){
        return "[야간] ";
    }
}

 

DayState.java

package main.java.designpattern.state;

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

    public static State getInstance(){
        return day;
    }

    @Override
    public void doClock(Context context, int hour) {
        if(9 > hour || hour >= 17){
            context.changeState(NightState.getInstance());
        }
    }
    @Override
    public void doUse(Context context) {
        context.recordiLog("금고사용(주간)");
    }
    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("비상벨(주간)");
    }
    @Override
    public void doPhone(Context context) {
        context.callSecurityCenter("일반통화(주간)");
    }
    public String toString(){
        return "[주간] ";
    }
}

 

▷Context 역할

package main.java.designpattern.state;

public interface Context {
    public abstract void setClock(int hour);
    public abstract void changeState(State state);
    public abstract void callSecurityCenter(String msg);
    public abstract void recordiLog(String msg);
}

 

▷Concrete Context 역할 - SafeFrame.java 

package main.java.designpattern.state;

import java.awt.*;
import java.awt.event.*;

public class SafeFrame extends Frame implements Context, ActionListener{
    private TextField textClock = new TextField(60);
    private TextArea textScreen = new TextArea(10,60);
    private Button buttonUse = new Button("Use");
    private Button buttonAlarm = new Button("Alarm");
    private Button buttonPhone = new Button("Call");
    private Button buttonExit = new Button("Exit");
    private State state = DayState.getInstance();

    public SafeFrame(String title){
        super(title);
        setBackground(Color.green);
        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();
        show();

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


    @Override
    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);
    }

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

    @Override
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }

    @Override
    public void recordiLog(String msg) {
        textScreen.append("record" + msg + "\n");
    }

    @Override
    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("?");
        }
    }
}

 

 

▷Main 역할

package main.java.designpattern.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 (Exception e){
                }
            }
        }
    }
}

 

---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형