티스토리 뷰
State Pattern
상태(State)란?
하나의 오브젝트가 시점에 따라 특정 상태에 있어야 한다. 처음에 가지게 되는 초기 상태 또는 상황에 따라 여러 상태 중 하나의 상태를 가질 수 있다.
한 상태에서 다른 상태로 전환하는 것을 전이(Transition) 이라고 한다. 예를들어 게임 캐릭터의 경우 걷기, 뛰기, 멈추기, 공격하기, 방어하기 등이 있고, 가전제품의 경우 on, off, sleep 등이 있을 수 있다.
이러한 다양한 상태를 if 문으로 상태를 통제하는 방식은 추가적인 상태가 생기거나 구현의 변경이 발생했을 때 여러곳의 코드가 변경될 수 있다는 문제가 있다.
이를 해결하기 위해 상태를 한 곳에서 관리하기 위한 패턴이 상태 패턴이다.
설명
두개의 인자와 한개의 연산자를 받고 "="을 입력 받으면 계산을 하는 Calculator를 구현한다. 이때 계산기의
상태로는 첫번째 인자 없음, 연산자 없음, 두번째 인자 없음, 계산("=") 인자 없음으로 총 4개의 상태가 존재하게 된다.
또한, 각 상태 별로 숫자 입력, 연산자 입력, 종료 입력의 3가지 전이(Transition)가 발생할 수 있다.
따라서, 모든 상태들을 하나의 상태 인터페이스를 상속하게 하여 각 상태 별로 전이에 대해 적절히 구현하도록 한다.
다이어그램
- 모든 상태들은 STATE 인터페이스를 implements 하도록 한다.
- 공통 기능인 quitInput()은 default 메소드로 만들었다.
- 상태 간 상태가 변경 되는 Transition을 인터페이스에 작성하고 이를 각자 구현한다.
Calculator.class 코드
public class Calculator {
int operand1; // 첫 번째 피 연산자값 저장
int operand2; // 두 번째 피 연산자값 저장
char operator; // 사칙 연산자 저장
BufferedReader br;
STATE noOperandOne;
STATE noOperandTwo;
STATE noOperator;
STATE calculate;
STATE state;
public Calculator() {
this.noOperandOne = new noOperandOne(this);
this.noOperandTwo = new noOperandTwo(this);
this.noOperator = new noOperator(this);
this.calculate = new calculate(this);
this.state = noOperandOne;
br = new BufferedReader(new InputStreamReader(System.in));
}
public void setState(STATE state) {
this.state = state;
}
void printOutResult() {
switch (operator) {
case '+' -> System.out.printf("%d + %d = %d\n", operand1, operand2, operand1 + operand2);
case '-' -> System.out.printf("%d - %d = %d\n", operand1, operand2, operand1 - operand2);
case '*' -> System.out.printf("%d * %d = %d\n", operand1, operand2, operand1 * operand2);
case '/' -> System.out.printf("%d / %d = %d\n", operand1, operand2, operand1 / operand2);
}
}
boolean run() throws IOException {
System.out.println("정수 또는 +, -, *, /, = 기호 중 한 개를 입력하세요. (종료 : q)");
String input = br.readLine();
char ch;
if (input.equals("")) {
ch = '=';
input = "=";
} else ch = input.charAt(0);
if (ch == 'q' || ch == 'Q') {
state.quitInput();
return false;
} else if (ch >= '0' && ch <= '9') {
state.decimalInput(input);
} else {
state.operatorInput(input);
}
return true;
}
}
- 현재 state를 if문 등을 통해 확인하지 않고 입력 값에 따라 decimalInput(), operatorInput(),
quitInput()을 수행하도록 한다. - STATE 인터페이스를 implements 한 각 상태 구현 코드에서 주어진 전이 값에 따라 적절한 수행을 한다.
STATE interface 코드
public interface STATE {
void decimalInput(String value);
void operatorInput(String oper);
default void quitInput(){
System.out.println("연산 종료");
}
}
- 모든 상태들은 STATE 인터페이스를 상속받아서 관리한다.
- 상태들은 자신의 현 상태에 따라 전이를 적절히 수행하게 implement 한다.
noOperandOne.class 코드
public class noOperandOne implements STATE {
Calculator calculator;
public noOperandOne(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void decimalInput(String value) {
calculator.operand1 = Integer.parseInt(value);
System.out.println("첫번째 인자 : " + calculator.operand1);
calculator.setState(calculator.noOperator);
}
@Override
public void operatorInput(String oper) {
System.out.println("숫자를 입력하세요.");
}
}
- 첫번째 인자값을 받아야 하는 상태
noOperator.class 코드
public class noOperator implements STATE{
Calculator calculator;
public noOperator (Calculator calculator) {
this.calculator = calculator;
}
@Override
public void decimalInput(String value) {
System.out.println("연산 기호를 입력하세요.");
}
@Override
public void operatorInput(String oper) {
char tmp = oper.charAt(0);
if (tmp == '+' || tmp == '-' || tmp == '*' || tmp == '/') {
calculator.operator = oper.charAt(0);
System.out.println("연산자 : " + calculator.operator);
calculator.setState(calculator.noOperandTwo);
} else {
System.out.println("잘못된 연산 기호입니다. 다시 입력하세요.");
}
}
}
- 연산기호를 받아야 하는 상태
noOperandTwo.class 코드
public class noOperandTwo implements STATE {
Calculator calculator;
public noOperandTwo(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void decimalInput(String value) {
calculator.operand2 = Integer.parseInt(value);
System.out.println("두번째 인자 : " + calculator.operand2);
calculator.setState(calculator.calculate);
}
@Override
public void operatorInput(String oper) {
System.out.println("숫자를 입력하세요.");
}
}
- 두번째 인자값을 받아야 하는 상태
calculate.class 코드
public class calculate implements STATE{
Calculator calculator;
public calculate (Calculator calculator) {
this.calculator = calculator;
}
@Override
public void decimalInput(String value) {
System.out.println("잘못된 입력입니다. '='를 입력하세요.");
}
@Override
public void operatorInput(String oper) {
char c = oper.charAt(0);
if (c == '=') {
calculator.printOutResult();
calculator.setState(calculator.noOperandOne);
} else {
System.out.println("잘못된 입력입니다. '='를 입력하세요.");
}
}
}
- 결과를 내기 위해 '='를 받아야 하는 상태
결과
- StatePattern을 사용함으로서 상태애 대해 일일히 if 조건문을 통해 현재 상태를 확인하지 않고도 전이에 대해 수행을 하도록
할 수 있다. - 추가적인 상태가 발생하더라도 코드의 변경을 최소화 할 수 있다.
반응형
'CS > GoF의 Design Pattern 정리' 카테고리의 다른 글
디자인 패턴 정리 - Template Method Pattern (0) | 2021.11.27 |
---|---|
디자인 패턴 정리 - Command Pattern (0) | 2021.11.19 |
디자인 패턴 정리 - Adapter Pattern, Facade Pattern (0) | 2021.11.19 |
디자인 패턴 정리 - DAO Pattern Refactoring with Generic (0) | 2021.11.19 |
디자인 패턴 정리 - DAO Pattern (0) | 2021.11.19 |
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday