상태 state 패턴
- 디자인 패턴중 상태 패턴에 대해 정리한다.
상태패턴이란
- 행동 패턴(Behavioral Pattern)으로 분류
- 객체지향방식으로 유한 상태 기계를 구현한것입니다.
- 객체의 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴
- 객체는 마치 자신의 클래스를 바꾸는 것처럼 보입니다.
- 사용이유
- 상태 전이를 위한 조건 로직이 지나치게 복잡한 경우 이를 해소하는 것
- 상태 전이 로직이란 객체의 상태와 이들 간의 전이 방법을 제어하는 것
- 각 상태에 대응하는 별도의 클래스를 만들고 상태 전이 로직을 그 클래스들로 옮기는 작업
- 자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고, 상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴을 말합니다.
- 상태 전이를 위한 조건 로직이 지나치게 복잡한 경우 이를 해소하는 것
- 하나의 예시로 사람의 돈이 얻는것에 따라 상태가 변화되는 예시입니다.
- 상태가 Terrible→bad→soso→happy→perfect로 기분이라는 상태의 변화가 있습니다.
- Terrible은 제일 불쾌, perfect는 제일 행복의 순입니다.
- 상태가 변화되는것으로 돈을 벌다(earnmoney)와 돈을 잃다(losemoney)가 있다
- 상태가 Terrible→bad→soso→happy→perfect로 기분이라는 상태의 변화가 있습니다.
단순 if로 상태를 관리한다면?
void earnMoney() {
if (currentState == SoSo) {
currentState = Happy;
} else if (currentState == Happy) {
currentState = Perfect;
...
} else if (currentState == Bad) {
...
}
- 이러한 기분이라는 상태를 관리하는데 있어 많은 조건문이 들어가고 추가를 하는데 복잡해질 것 입니다.
예시
state라는 기분을 관리하는 하나 만들어줍니다.
public interface State {
State winLottery(); //로또 당첨
State earnMoney();//돈을 벌다 -> 기분 상승
State loseMoney();//돈을 잃다 -> 기분 하락
State loseWinLottery();
void printCurrentEmotion();//현재 기분 출력
}
사람이라는 실제 상태가 적용되는 class입니다.
public class Person {
private State state;
public Person(State state) {
this.state = state;//상태를 받습니다.
}
public void winLottery() {// 로또가 되었을대
state = state.winLottery();//상태에서 로또가됐다는 메소드를 실행하고
state.printCurrentEmotion();//현재 상태를 출력합니다.
}
public void earnMoney() {
state = state.earnMoney();
state.printCurrentEmotion();
}
public void loseMoney() {
state = state.loseMoney();
state.printCurrentEmotion();
}
public void printCurrentEmotion() {
state.printCurrentEmotion();
}
}
- 따로 상태의 조건이 없이 해당 상태가 되었을대 state의 메소드를 통해 상태가 변화되도록만 되어있습니다.
- state의 earnMoney, loseMoney등은 어떻게 되었는지 확인해봅시다.
sate 인터페이스의 추상 클래스
public abstract class Emotion implements State {
@Override // 로또 당첨은 기분이 최상이 됩니다.
public State winLottery() {
return new Perfect();
}
@Override // 당첨된 로또를 잃어버리면 기분이 최악이 됩니다.
public State loseWinLottery() {
return new Terrible();
}
}
- 먼저 State에서 로또의 당첨과 잃어버리는것은 무조건 최상의 기분, 최악의 기분으로 나뉘기 때문에 추상클래스에서 한번 거쳐서 해당 메소드를 구현했습니다.
SoSo라는 그저 그런 상태의 클래스입니다.
public class SoSo extends Emotion {
@Override // 돈을 벌어서 한 단계 기분이 좋아집니다.
public State earnMoney() {
return new Happy();
}
@Override // 돈을 잃어서 한 단계 기분이 나빠집니다.
public State loseMoney() {
return new Bad();
}
@Override
public void printCurrentEmotion() {
System.out.println("그저그래요.");
}
}
- state→Emotion을 거쳐서 SoSo라는 그저그런 상태입니다.
- earnMoney로 SoSo→Happy로 변화를 시키고, loseMoney로 SoSo→Bad로 변화가 일어나고 있습니다.
- 이렇듯 해당 객체에서 현재 기분이라는 상태가 변화되도록 하는것이 state패턴입니다.
그외의 기분이라는 상태 클래스들입니다.
public class Perfect extends Emotion {
@Override // 기분이 변경되지 않습니다.
public State earnMoney() {
// 기분이 더 좋아질 수 없기 때문에 예외를 발생시키는 방식을 사용할 수도 있다.
return this;
}
@Override // 돈을 잃어서 한 단계 기분이 나빠집니다.
public State loseMoney() {
return new Happy();
}
@Override
public void printCurrentEmotion() {
System.out.println("최고입니다.");
}
}
public class Happy extends Emotion {
@Override // 돈을 벌어서 한 단계 기분이 좋아집니다.
public State earnMoney() {
// 예) 기분이 너무 좋아서 주변 지인들과 파티를 하는 기능 처리
return new Perfect();
}
@Override // 돈을 잃어서 한 단계 기분이 나빠집니다.
public State loseMoney() {
return new SoSo();
}
@Override
public void printCurrentEmotion() {
System.out.println("행복합니다.");
}
}
public class Bad extends Emotion {
@Override // 돈을 벌어서 한 단계 기분이 좋아집니다.
public State earnMoney() {
return new SoSo();
}
@Override // 돈을 잃어서 한 단계 기분이 나빠집니다.
public State loseMoney() {
// 예) 기분이 너무 나빠지며 친한 친구와 술을 먹는 기능 처리
return new Terrible();
}
@Override
public void printCurrentEmotion() {
System.out.println("별로입니다.");
}
}
public class Terrible extends Emotion {
@Override // 돈을 벌어서 한 단계 기분이 좋아집니다.
public State earnMoney() {
return new Bad();
}
@Override // 기분이 변경되지 않습니다.
public State loseMoney() {
// 기분이 더 나빠질 수 없기 때문에 예외를 발생시키는 방식을 사용할 수도 있다.
return this;
}
@Override
public void printCurrentEmotion() {
System.out.println("최악입니다.");
}
}
- 기분이라는 상태는 terrible<bad<soso<happy<Perfect순입니다.
- 돈을 벌면 기분이라는 상태가 변화되는것을 각 class내에서 정하고 있습니다.
해당 상태 변화를 확인해 봅시다.
public class Main {
public static void main(String[] args) {
Person person = new Person(new SoSo());
person.printCurrentEmotion();//그저그래요
person.earnMoney();//행복합니다
person.winLottery();//최고입니다.
person.loseMoney();//행복합니다.
person.loseMoney();//그저그래요
person.loseMoney();//별로입니다
person.loseMoney();//최악입니다
}
}
- 이처럼 state라는 person에서 메소드 호출에 따라 상태가 변화되는것을 확인 할 수 있습니다.
만약 새로운 상태를 추가하고 싶다면?
public class Sleepy extends Emotion {
@Override
public State earnMoney() {
return this;
}
@Override
public State loseMoney() {
return this;
}
@Override
public void printCurrentEmotion() {
System.out.println("졸려.");
}
}
- 위처럼 새로운 상태로 sleepy라는 상태를 추가 한다면 해당 상태 클래스를 정의 하고, 사용하기만 하면 된다.
public static void main(String[] args) {
Person person = new Person(new Sleepy());
person.printCurrentEmotion(); // 졸려.
person.earnMoney(); //졸려.
person.winLottery(); // 최고입니다.
person.loseMoney(); //행복합니다.
}
결론
- 상태 패턴을 이용하면 상태 패턴 인터페이스의 파생 클래스로서 각각의 상태를 구현함
- 패턴의 부모클래스에 의해 정의되는 메서드를 호출하여 상태 변화를 구현함으로써 상태 기계를 구현
- 상태 패턴의 장점은 새로운 상태가 추가되더라도 콘텍스트 코드가 받는 영향은 최소화
- 클래스를 추가하더라도 기존의 메서드의 코드는 그대로 유지
- 상태에 따른 동작을 구현한 코드가 상태별로 구분되기 때문에 상태별 동작을 수정하기 쉽다.
참고
- https://johngrib.github.io/wiki/pattern/state/
- https://tecoble.techcourse.co.kr/post/2021-04-26-state-pattern/
- https://velog.io/@jinmin2216/디자인-패턴-스테이트상태-패턴-State-Pattern
- https://victorydntmd.tistory.com/294