본문 바로가기
CS

[디자인패턴] 상태 패턴 (State pattern) 의 개념 및 구조 요약정리

by Warehaus 2022. 8. 17.

 

의도

객체 자신의 내부 상태에 따라 행위를 변경하도록 한다. 객체가 클래스를 바꾸는 것 처럼 보일 수 있다.

 

 

Use When

- 객체의 행위가 상태에 영향을 받는 경우

- 객체의 행위가 상태에 따라 변경되며 이 상태가 런타임에 바뀌는 경우

- 복잡한 조건이 객체의 행위와 상태에 얽혀 있는 경우

- 상태 간 전이 동작이 분리되어야 하는 경우

 

구조도 및 구조설명

 

출처: 위키피디아

 

 

- State : 여러가지 세부 상태를 대표하는 추상 클래스

- Context : 상태에 따른 행위를 실행하는 객체

- ConcreteState : 세부 행위를 정의하는 상태객체

 

상태 패턴 구조도는 위 그림과 같습니다.  상태라는 개념은 하나의 클래스로 추상화 되어 있으며, 자식클래스로 세부 상태를 구현하게 됩니다. ConcreateState 에서는 각 상태에서 수행되어야 할 행위들을 구현하며, 상태의 변경 및 행위의 호출은 Context 객체 내에서 일어나게 됩니다. ( 위 구조도에서 state.handle() 에 해당 )

 

Context는 미리 각 세부상태를 인스턴스화 하여 객체로 만들어 두는데, 이 때 개별 세부 상태 객체들이 Context reference 를 갖게 합니다.  ConcreteState가 Context 참조정보를 가져야 하는 이유는, 각 세부상태의 행위( handle() ) 수행 시 다음 상태로 변경하는 동작이 필요할 수 있기 때문입니다.

 

풀어서 설명하면, Context객체에서 state.handle() 실행 시 state 변수에 설정 된 ConcreteState 객체의 handle()이 호출되는데, 해당 메서드에서 필요한 동작을 수행 한 뒤, 따른 ConcreteState로 상태전이가 필요한 경우 handle()내에서 state 변경이 필요하게 됩니다. 이때, ConcreteState에 Context 객체의 참조정보가 할당되어 있다면,  아래처럼 상태변경이 가능하게 됩니다.

 

public void handle() {
    // do handle operations
    
    // set next state
    context.state = context.anotherConcreteState;
}

 

샘플코드

public class StatePatternCar {

	public static interface CarState {
		public void speedUp(int targetSpeed);
		public void speedDown(int targetSpeed);
		public void enigineFaultDetected();
		public void engineRepaired();
		public void stop() ;
		
	}
	
	public static class LimpState implements CarState {

		Car context;
		// 생성자에서 Context 를 설정. 별도 setContext 메서드를 만들어도 무관
		public LimpState (Car context){
			this.context = context;
		}
		
		@Override
		public void speedUp(int targetSpeed) {
			// Nothing to do
		}

		@Override
		public void speedDown(int targetSpeed) {
			// Nothing to do
			
		}

		@Override
		public void enigineFaultDetected() {
			// Nothing to do
		}

		@Override
		public void engineRepaired() {
			context.state = new NormalState(context);
		}

		@Override
		public void stop() {
			context.speed = 0;
		}
		
	}
	
	public static class NormalState implements CarState{
		Car context;
		
		public NormalState (Car context){
			this.context = context;
		}
		
		@Override
		public void speedUp(int targetSpeed) {
			// TODO Auto-generated method stub

			if (targetSpeed > context.speed)
				context.speed = targetSpeed;
			else {
				if (targetSpeed > context.speed && targetSpeed < context.LIMP_MODE_MAX_SPEED)
					context.speed = targetSpeed;
				else
					context.speed = context.LIMP_MODE_MAX_SPEED;
			}
		}

		@Override
		public void speedDown(int targetSpeed) {
			if (context.speed > targetSpeed) {
				context.speed = targetSpeed;
				return;
			}
		}

		@Override
		public void enigineFaultDetected() {
			// TODO Auto-generated method stub
			context.state = new LimpState(context);
			context.speed = context.LIMP_MODE_MAX_SPEED;
		}

		@Override
		public void engineRepaired() {
			// Nothing to do
		}

		@Override
		public void stop() {
			context.speed = 0;
		}
	}
	
	public static class Car {

		private static final int LIMP_MODE_MAX_SPEED = 100;
		private int speed;
		private CarState state;


		public Car() {
			state = new NormalState(this);
			speed = 0;
		}

		public void speedUp(int targetSpeed) {
			state.speedUp(targetSpeed);
			
		}

		public void speedDown(int targetSpeed) {
			state.speedDown(targetSpeed);
		}

		public void enigineFaultDetected() {
			state.enigineFaultDetected();
		}

		public void engineRepaired() {
			state.engineRepaired();
		}

		public void setSpeed(int speed) {
			this.speed = speed;
		}

		public int getSpeed() {
			return speed;
		}

		@Override
		public String toString() {
			return "Car [speed=" + speed + ", state=" + ((state instanceof NormalState) ? " Normal " : " Limp ") + "]";
		}
		
		
	}

	public static void main(String[] args) {
		Car car = new Car();
		car.speedUp(150); 
		System.out.println(car);
		car.speedDown(130); 
		System.out.println(car);
		car.enigineFaultDetected(); 
		System.out.println(car);
		car.speedUp(100); 
		System.out.println(car);
		car.speedDown(30); 
		System.out.println(car);
		car.speedUp(50);
		System.out.println(car);
		car.speedUp(100); 
		System.out.println(car);
		car.engineRepaired(); 
		System.out.println(car);
		car.speedUp(100); System.out.println(car);
	}

}

 

 

상태 패턴의 장점

상태패턴 사용의 장점은 다음과 같습니다.

 

- 관련 행위를 상태 안에 넣을 수 있다.

- Switch 같은 분기를 통한 상태행위를 수행하는 것이 아닌, 하나의 상태 객체에 필요한 상태 로직을 넣을 수 있다.