[JAVA/디자인패턴] State Pattern, 상태 패턴

2023. 11. 29. 07:17
반응형

 

 

State Pattern

 

https://upload.wikimedia.org/wikipedia/commons/e/e8/State_Design_Pattern_UML_Class_Diagram.svg

 

 

State Pattern(상태 패턴)은 객체의 내부 상태가 변경될 때 객체의 행동을 변경할 수 있도록 하는 디자인 패턴.

객체의 행동이 그 객체의 상태에 따라 다르게 변하게 하기 위해 사용된다. 주로 상태 전이가 복잡하고 상태에 따른 행동이 많은 경우에 적합할 수 있다.

 

  1. Context : 상태를 가지고 있는 객체로,  상태에 따라 다른 행동을 수행한다. Context는 State 인터페이스를 구현한다.
    객체가 가질 수 있는 State들을 멤버변수로 가지며, 현재 State를 유지한다.
  2. State : 상태에 대한 인터페이스를 정의한다. Context의 상태에 따라 다른 동작을 수행하기 위한 메서드를 선언한다.
  3. ConcreteState: State 인터페이스를 구현하여 객체의 실제 상태에 대한 동작을 정의한다.

 

 


State Pattern 예제

 

간단한 산술 연산이 가능한 계산기 프로그램을 스테 이트 패턴을 이용해서 구현

정수 범위 내에 있는 숫자는 계산 가능 사칙 연산과 나머지 연산(+, -, *, /, %)

상명대학교 조용주 교수님 <고급객체지향프로그래밍> 과제

상태에서 다른 상태로의 이동은 설명 내용에 해당되 는 입력이 들어올 때 일어나며, 사용자는 숫자(들), 산 술 연산자, 숫자(들), '=' 순서로 정확하게 입력한다고 가정할 것

 

Start :시작 상태, 피연산자 2개를 저장하는 변수는 0으 로 초기화

Operand1 :첫 번째 피연산자 입력 상태

Operator : 연산자 입력 상태. Operator 상태에서 다시 연산 자가 입력되면 기존 연산자를 덮어 씀(overwrite)

Operand2 :두 번째 피연산자 입력 상태, '='가 입력되면 결과값을 계산해서 저장한 후 Start 상태로 이동

 

 

 

 


State 정의

 

public interface State {
    default void processDigit(int digit) {
        System.out.println("invalid operation");
    }

    default void processArithmeticOperator(char ch) {
        System.out.println("invalid operation");
    }

    default void processEqualOperator() {
        System.out.println("invalid operation");
    }
}

 

Context의 상태에 따라 수행할 수 있는 동작들을 State 인터페이스에 선언해둔다.

Digit인 경우, +,-와 같은 산술 연산자인 경우, = 연산자인 경우에 수행될 수 있으므로 해당 메서드들을 선언해두고, ConcreteState에서 각각 다른 동작이 이루어지도록 구현한다.

 

ConcreteState로는 문제와 같이 Start, Operand1, Operator, Operand2로 Context는 총 4가지의 State를 가지게 될 것이다.

 

 

 

 

 

 

 


Context 작성

 

public class Calculator {
    /* 현재 상태 */
    State state;

    /* 가질 수 있는 State */
    State start;
    State operand1;
    State operator;
    State operand2;
    
    private String first; //첫 번째 피연산자
    private String second; //두 번째 피연산자
    private char op; //연산자
    String result; //결과값 저장

    public Calculator(){
        this.start = new Start(this);
        this.operand1 = new Operand1(this);
        this.operand2 = new Operand2(this);
        this.operator= new Operator(this);
        this.state = this.start;
    }

    /* 현재 State 변경 */
    public void setState(State state){
        System.out.println(this.state.toString() + " -> " + state.toString());
        this.state = state;
    }
    
    /* State Getter */
    public State getStart() {
        return start;
    }
    public State getOperand1() {
        return operand1;
    }
    public State getOperator() {
        return operator;
    }
    public State getOperand2() {
        return operand2;
    }



    
    /* State에 따른 동작 수행 */
    public void process(char ch) {
        //연산자에 대한 State가 행동해야 할 메서드
        if(ch == '+' || ch == '-' || ch == '*' || ch == '/'){
            this.state.processArithmeticOperator(ch);
        }
        //Equal 연산자에 대한 State가 행동해야 할 메서드
        else if(ch == '='){
            this.state.processEqualOperator();
        }
        //Digit에 대한 State가 행동해야 할 메서드
        else if(Character.isDigit(ch)){
            int i = Character.getNumericValue(ch);
            this.state.processDigit(i);
        }
        else{
            System.out.println("Invalid Input");
        }
    }
    
    // 첫 번째 피연산자의 수 추가입력 (1,2,3,4 -> 1234)
    public void addFirst(int i){
        this.first = this.first.concat(Integer.toString(i));
    }

    // 두 번째 피연산자의 수 추가입력 (1,2,3,4 -> 1234)
    public void addSecond(int i){
        this.second = this.second.concat(Integer.toString(i));
    }

    // 연산자 설정
    public void setOp(char op){
        this.op = op;
    }

    // 결과 설정
    public void setResult(){
        double n1 = Double.parseDouble(this.first);
        double n2 = Double.parseDouble(this.second);
        double res = 0;
        switch(op){
            case '+':
                res = n1 + n2;
                break;
            case '-':
                res = n1 - n2;
                break;
            case '*':
                res = n1 * n2;
                break;
            case '/':
                res = n1 / n2;
                break;
            default:
                break;
        }
        this.result = String.format("%s %c %s = %.1f", this.first, this.op, this.second, res);
        System.out.println(result);

    }

    //계산기 초기화
    public void init(){
        this.first = "";
        this.second = "";
    }


}

 

  • state: 현재 상태를 나타내는 변수
  • start, operand1, operator, operand2: 가질 수 있는 상태를 나타내는 변수로, 각각 ConcreteState의 인스턴스를 가진다.
  • setState(State state): 현재 상태를 변경한다.
  • process(char ch): 입력된 문자에 대한 처리를 현재 상태에 따라 State에게 위임

 

 

 

 

 


ConcreteState 구현

 

public class Start implements State{

    Calculator calculator;
    public Start(Calculator calc){
        this.calculator = calc;
    }

    public void processDigit(int digit) {
        this.calculator.init();
        this.calculator.setState(calculator.getOperand1());
        this.calculator.process(Integer.toString(digit).charAt(0));
    }
    
    public String toString(){
        return "Start State";
    }
    
}
  1.  

State를 구현한 ConcreteState 중 하나인 시작을 나타내는 상태인 Start이다. State에는 Context가 멤버 변수로 포함되고 해당 Context가 지금 상태에서 무슨 행위를 하게 되는지를 구현한다.

해당 상태에서는 첫 Digit이 입력으로 들어올 때, Calculator를 초기화하고 Operand1 상태로 변경하면서 입력값을 첫 번째 피연산자에 추가하는 시작 상태에서의 행위를 작성하였다.

 

 

public class Operand1 implements State{

    Calculator calculator;
    public Operand1(Calculator calc){
        this.calculator = calc;
    }

    public void processDigit(int digit) {
        this.calculator.addFirst(digit);
    }

    public void processArithmeticOperator(char ch) {
        this.calculator.setState(calculator.getOperator());
        this.calculator.process(ch);
    }
    
    public String toString(){
        return "Operand1 State";
    }

}

첫 번째 피연산자를 작성하고 있는 상태이다. 

Digit이 입력으로 들어올 때, 입력값을 첫 번째 피연산자에 추가하는 행위를 하며,

연산자가 입력으로 들어올 때, 상태를 연산자 입력 State로 변경하고 연산자를 입력하도록 한다.

 

 

public class Operator implements State {

    Calculator calculator;
    public Operator(Calculator calc){
        this.calculator = calc;
    }

    public void processArithmeticOperator(char ch) {
        this.calculator.setOp(ch);
        this.calculator.setState(calculator.getOperand2());
    }
    
    public String toString(){
        return "Operator State";
    }
    

}

연산자를 작성하고 있는 상태이다.

연산자가 입력으로 들어올 때, 연산자를 추가하고 상태를 두 번째 입력 State로 변경하도록 한다.

 

 

 

public class Operand2 implements State{

    Calculator calculator;
    public Operand2(Calculator calc){
        this.calculator = calc;
    }

    public void processDigit(int digit) {
        this.calculator.addSecond(digit);
    }


    public void processEqualOperator() {
        this.calculator.setResult();
        this.calculator.setState(this.calculator.getStart());
    }
    
   public String toString(){
        return "Operand2 State";
    }
    

}

두 번째 피연산자를 작성하고 있는 상태이다.

Digit이 입력으로 들어올 때, 입력값을 두 번째 피연산자에 추가하는 행위를 하며,

=가 입력으로 들어올 때, 결과를 계산하고 첫 시작상태로 초기화시킨다.

 

 

 

 


메인함수와 결과 확인

 

// 입력된 문자 ch가 0~9까지의 숫자를 나타내는 문자인 경우, ch - '0'으로 0~9까지의 정수로 변환 가능
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Calculator calc = new Calculator();
        char ch = 'A';
        while (ch != 'q' && ch != 'Q') {    // 종료 문자가 아니면 반복
            ch = sc.next().charAt(0);      // 키보드에서 한 글자 입력 받기
            calc.process(ch);
        }
    }
}

 

 

 

 

 

반응형

BELATED ARTICLES

more