상태 머신 다이어그램
실세계의 많은 시스템은 다양한 상태가 있고, 상태에 따라 다른 행위를 수행한다. UML에서도 이러한 상태와 상태 변화를 모델링 하는 도구로 상태 머신 다이어그램이 있다.
선풍기의 상태와 상태 변화를 간단하게 모델링하면 위와 같을 것이다. 상태 머신 다이어그램에서는 모서리가 둥근 사각형을 통해 상태를 나타내고, 상태 사이에 화살표를 두어 상태 전이를 나타낸다. 검정 동그라미는 시작을 나타내며, 시작 시에는 시작 이벤트만 명시하거나 아무것도 명시하지 않는다.
추가적으로 선풍기가 ON 혹은 WORKING일 때 off 스위치를 누르면 OFF 상태로 전이 되는데, 이를 위처럼 복합 상태를 이용해 더 간단히 표현할 수 있다.
스테이트 패턴
실세계의 많은 개체는 처한 상태에 따라 다른 일을 수행한다. 이를 코드로 표현하는 방법은 작업을 수행할 때마다 매번 상태를 검사하고 그에 따라 다른 작업을 수행하게 만드는 것이다. 이런 코드를 작성한다면 이해하기 어렵거나 수정하기 어려워 진다.
스테이트 패턴은 상태에 따라 달라지는 어떤 행위를 수행할 때, 상태에 행위를 수행하도록 위임한다. 각 상태를 클래스로 분리하여 캡슐화하고 인터페이스를 통해 구체적인 상태를 몰라도 행위를 수행할 수 있게 한다.
•
State : 시스템의 모든 상태에 대한 공통적인 인터페이스를 제공
•
State1..3 : 공통적인 인터페이스를 자신의 방식대로 구현하여 작업을 수행, 대부분의 경우 다음 상태를 결정해 Context에 요청
•
Context : 시스템의 상태를 나타내는 State와 실제 시스템의 상태를 구성하는 여러 변수를 가지고 작업 수행을 요청하는 주체
스테이트 패턴에서 State 행위를 위임한 경우, State의 행위로 인해 상태가 변경될 수 있기 때문에 Context 객체에 다음 상태를 넘겨줄 수 있어야한다.
형광등 만들기
프로그램에서 상태란 객체가 존재하는 동안(라이프 타임동안) 객체가 가질 수 있는 조건이나 상황을 표현한다. 객체에 특정 메서드가 호출되거나 이벤트가 발생하여 상태 전이가 발생할 수 있다. 형광등의 예시를 통해 이해해보자.
이 상태 머신 다이어그램을 코드로 바꾸는 작업은 그리 어렵지 않다.
public class Light {
private static int ON = 0;
private static int OFF = 1;
private int state;
public Light() {
state = OFF;
}
public void onButtonPushed() {
if (state == OFF) {
state = ON;
}
}
public void offButtonPushed() {
if (state == ON) {
state = OFF;
}
}
}
Java
복사
문제점
하지만 위와 같이 작성하면, 상태가 추가되었을 때 변경이 어렵다. 예를 들어 취침등 모드가 추가된다고 가정해보자.
private static int SLEEPING = 2;
public void onButtonPushed() {
if (state == OFF) {
state = ON;
} else if (state == ON) {
state = SLEEPING;
} else if (state == SLEEPING) {
state = ON;
}
}
public void offButtonPushed() {
if (state == ON || state == SLEEPING) {
state = OFF;
}
}
Java
복사
이와 같이 복잡한 조건문이 추가되고, 모든 메서드에 수정이 발생하기 때문에 OCP 원칙을 위반한다.
해결책
이 역시 무엇이 변하는지를 찾고, 이를 캡슐화하는 것으로 해결하는 것이 중요하다. 위에서는 각 상태가 변하는 것이기 때문에, 상태를 클래스로 분리하여 캡슐화하고 상태에 의존적인 행위들도 상태 클래스에 같이 두어 해결할 수 있다.
이는 전략 패턴과도 구조가 동일한데, Light 클래스에서 구체적인 상태 클래스가 아닌 State 인터페이스만 참조하여 상태와 무관하게 코드를 작성하는 것이다.
public class Light {
private State state;
public Light() {
state = new Off();
}
public void onButtonPushed() {
state.onButtonPushed(this);
}
public void offButtonPushed() {
state.offButtonPushed(this);
}
public void changeState(final State state) {
this.state = state;
}
}
Java
복사
public interface State {
void onButtonPushed(final Light light);
void offButtonPushed(final Light light);
}
Java
복사
public class On implements State {
@Override
public void onButtonPushed(final Light light) {
// do nothing
}
@Override
public void offButtonPushed(final Light light) {
light.changeState(new Off());
}
}
Java
복사
public class Off implements State {
@Override
public void onButtonPushed(final Light light) {
light.changeState(new On());
}
@Override
public void offButtonPushed(final Light light) {
// do nothing
}
}
Java
복사
Light 클래스 내부의 State를 통해 현재의 상태를 객체 참조 형태로 관리한다. 상태의 관리는 State 객체에 위임하고, Light 클래스는 구체적인 상태를 의존하지 않는다.
위 코드에서 추가적으로 On과 Off 클래스에 싱글톤 패턴을 적용하면 더 좋은 상태 클래스를 설계할 수 있다.