티스토리 뷰

JAVA/디자인 패턴

상태(State) 패턴

kkoon9 2022. 4. 18. 00:06

객체 지향 프로그래밍에서는 프로그램 할 대상을 ‘클래스’로 표현한다.

당연하게도 어떤 것을 클래스로 표현할지는 설계자가 생각해야 한다.

클래스에 대응하는 구체적인 ‘사물'이 현실에서 존재할수도 존재하지 않을수도 있다.

경우에 따라서는 ‘이런 것이 클래스가 될 수 있나?’라고 놀랄 정도의 것을 클래스로 하는 경우도 있다.

 

현실세계에서 우리는 다양한 사물의 ‘상태'에 대해서 생각한다.

그러나 상태를 ‘사물’로 생각할 일이 없기 때문에, 상태를 클래스로 표현한다는 것도 쉽게 이해가 안 될 수 있다.

이번 포스팅에서는 ‘상태'를 클래스로 표현하는 방법에 대해 다룬다.

상태를 클래스로 표현하면 클래스를 교체해서 ‘상태의 변화'를 표현할 수 있고, 새로운 상태를 추가해야 될 때 무엇을 프로그램하면 좋을지 분명해진다.

 

다음 링크는 상태 패턴의 예제 코드다.

 

GitHub - kkoon9/Java-Design-Pattern: Java 언어로 배우는 디자인 패턴 입문 예제 코드

Java 언어로 배우는 디자인 패턴 입문 예제 코드. Contribute to kkoon9/Java-Design-Pattern development by creating an account on GitHub.

github.com

위 프로그램은 금고경비 시스템이다.

  • 금고가 1개 있다.
  • 금고는 경비센터와 접속되어 있다.
  • 금고에는 비상벨과 일반통화용 전화가 접속되어 있다.
  • 금고에는 시계가 설치되어 있어 현재의 시간을 감시하고 있다.
  • 주간은 09:00 ~ 16:59, 야간은 17:00 ~ 23:59 및 0:00 ~8:59
  • 금고는 주간에만 사용할 수 있다.
  • 주간에 금고를 사용하면 경비센터에 사용기록이 남는다.
  • 야간에 금고를 사용하면 경비센터에 비상사태로 통보가 된다.
  • 비상벨은 언제나 사용할 수 있다.
  • 비상벨을 사용하면 경비센터에 비상벨 통보가 된다.
  • 일반통화용의 전화는 언제나 사용할 수 있다. (그러나 야간은 녹음만 가능)
  • 주간에 전화를 사용하면 경비센터가 호출된다.
  • 야간에 전화를 사용하면 경비센터의 자동응답기가 호출된다.

상태 패턴을 사용하지 않는다면 주간과 야간의 상태가 각 메서드 안의 변수로 if문에 등장한다.

그리고 각 메서드 안에서 현재의 상태를 조사한다.

 

하지만 위 예제 코드에서는 주간과 야간의 상태를 클래스로 표현하고 있다.

상태가 클래스로 표현되기 때문에 그 안의 메서드에는 상태 검사를 위한 if문이 등장하지 않는다.

상태 패턴의 등장인물

State(상태)의 역할 - State

상태를 나타낸다.

상태가 변할 때마다 다른 동작을 하는 인터페이스(API)를 결정한다.

이 인터페이스(API)는 상태에 의존한 동작을 하는 메서드의 집합이 된다.

ConcreteState(구체적인 상태)의 역할 - DayState, NightState

각각의 상태를 표현한다.

State 역할로 결정되는 인터페이스(API)를 구체적으로 구현한다.

Context(상황, 전후관계, 문맥)의 역할 - Context

현재의 상태를 나타내는 ConcreteState 역할을 가진다.

또한, 상태 패턴의 이용자에게 필요한 인터페이스(API)를 결정한다.

분할 통치

이것은 복잡하고 규모가 큰 프로그램을 취급할 경우의 방침이다.

규모가 크고 복잡한 문제는 그대로 해결하려고 해서는 안 된다.

우선 문제를 해결할 수 있을 정도로 작은 문제로 나눈다.

상태가 많을 때에는 State 패턴의 장점이 발휘된다.

상태 패턴을 사용하지 않으면 상태가 많아질 때마다 조건문이 다양해질 것이다.

상태에 의존한 처리

SafeFrame 클래스의 setClock는 state에 위임한다.

즉, 시간의 설정을 ‘현재의 상태에 의존한 처리'로 취급하고 있다.

이것은 doClock 메서드에만 한정되지 않는다.

State 인터페이스로 선언되는 메서드는 모두 ‘상태에 의존한 처리'이고 ‘상태에 따라 동작이 달라지는 처리'다.

상태 패턴에서는 ‘상태에 의존한 처리'를 다음 두 가지 사항으로 정리할 수 있다.

  • 추상 메서드로서 선언하고 인터페이스로 한다.
  • 구상 메서드로서 구현하고 각각의 클래스로 한다.

이것이 상태 패턴의 ‘상태에 의존한 처리'의 표현 방법이다.

🤔
상태전환은 누가 관리하는 게 맞을까?

 

상태전환은 누가 해야 하는지는 주의해야 한다.

예제 코드에서는 Context 역할의 SafeFrame 클래스가 상태전환을 실제로 수행하는 changeState를 구현한다.

그러나 이 메서드를 호출하는 건 ConcreteState 역할의 클래스들이다.

즉, ‘상태전환'을 ‘상태에 의존한 동작'으로 간주하고 있다.

이 방법은 장단점이 존재한다.

[장점]

‘다른 상태로 전환하는 것은 언제인가' 하는 정보가 하나의 클래스 내에 정리되어 있는 점

[단점]

‘하나의 ConcreteState 역할이 다른 ConcreteState 역할을 알아야 한다'는 점

즉, 클래스 사이의 의존관계를 깊게 한다.

자기 모순에 빠지지 않는다.

상태 패턴을 사용하지 않고 시스템의 상태가 복수의 변수 값의 집합으로 표현되어 있다고 생각해보자.

이 때, 변수 값의 사이에 자기 모순이나 불균형이 없어야 한다.

예제 코드에서는 state 필드의 값이 시스템의 상태를 확실하게 결정하기 때문에 자기 모순에 빠지지 않는다.

새로운 상태를 추가하는 것은 간단하다

상태전환의 부분은 다른 ConcreteState 역할과의 접점이 되기 때문에 주의해야 한다.

완성된 상태 패턴에 새로운 ‘상태의존의 처리'를 추가하는 것은 곤란하다.

그것은 State 역할의 인터페이스에 메서드를 추가한다는 것을 의미하며, 모든 ConcreteState 역할에 처리를 추가하는 일이 되기 때문이다.

물론, 무조건 추가되어야 할 상황이면 오히려 좋다.

State 인터페이스에 새로운 메서드가 추가했다고 가정했을 때, 그 메서드의 구현을 잊었다면 컴파일 에러가 발생하기 때문이다.

두 얼굴을 가진 인스턴스

SafeFrame 클래스 안에 등장하는 this는 무엇일까?

물론, 둘 모두 SafeFrame 클래스의 인스턴스이다.

 

addActionListener 메서드에 전달될 때, ‘ActionListener 인터페이스를 구현하고 있는 클래스의 인스턴스'로 취급된다.

이것은 addActionListener 메서드의 인수가 ActionListener형이기 때문이다.

addActionListener 메서드 안에서는 ‘ActionListener 인터페이스에서 규정된 메서드의 범위'에서 인수가 이용된다.

인수로 전달된 것이 SafeFrame의 인스턴스인지 아닌지는 중요하지 않다.

 

doUse 메서드에 전달될 때, ‘동일한 인스턴스가 Context 인터페이스를 구현하고 있는 클래스의 인스턴스'로 취급된다.

이것은 doUse 메서드의 인수가 Context형이기 때문이다.

doUse 메서드 안에서는 ‘Context 인터페이스에서 규정된 메서드의 범위'에서 인수가 이용된다.

관련 패턴

  • Singleton 패턴
  • Flyweight 패턴

'JAVA > 디자인 패턴' 카테고리의 다른 글

프록시(Proxy) 패턴  (0) 2022.04.18
플라이웨이트(Flyweight) 패턴  (0) 2022.04.18
메멘토(Memento) 패턴  (0) 2022.04.03
옵저버(Observer) 패턴  (0) 2022.04.02
미디에이터(Mediator) 패턴  (0) 2022.04.02
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함