본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

State

by 민휘 2023. 2. 24.

핵심 의도

상태 패턴을 사용하면 내부 상태가 바뀜에 따라 객체의 행동을 바꿀 수 있다. 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

 

적용 상황

상태 다이어그램을 구현할 때 사용할 수 있다. 특히 상태에 따라 행동이 바뀌는 경우에 적합하다. 상태를 캡슐화하지 않고 그대로 조건문으로 행동을 처리하면 수많은 조건문으로 인해 가독성이 떨어지고 관리가 어려워진다. 상태 패턴은 상태와 함께 관련된 행동을 캡슐화하므로, 상태 변경을 통해 행동을 변경할 수 있다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

상태 다이어그램을 생각해보면, 각각의 상태를 나타내는 책임과 상태를 가지고 관리하고 변경하는 책임이 필요하다. 상태는 여러개 존재하므로 State라는 공통 인터페이스를 따르게 하고, 상태를 가지고 변경하는 책임은 Context가 가진다. 클라이언트는 Context에게 특정 행동을 하도록 메시지를 보내고, Context는 내부적으로 상태를 변경하여 자신이 할 수 있는 행동을 바꾼다.

 

구현 포인트

State는 인터페이스일까 추상클래스일까?

둘다 가능하다. State는 상태 클래스에서 사용하는 모든 행동의 명세를 가지고 있어야 한다. (그래서 어떤 구상 클래스에서는 허용되지 않는 행동에 대해 적절한 처리를 해야한다) 만약 모든 구상 클래스에서 같은 행동을 하도록 만들고 싶다면 추상클래스에 구상 메소드를 둘 수 있다. 모든 행동을 구상 클래스에서 구현하도록 하고 싶다면 인터페이스로 두어야 한다.

 

전략 패턴과 상태 패턴

상태 패턴과 전략 패턴의 클래스 다이어그램은 동일하다. 둘다 구성과 위임으로 객체가 다른 행동과 알고리즘을 보일 수 있도록 한다. 하는 일은 유사하지만, 그 목적은 다르다. 전략 패턴은 클라이언트가 Context 객체에게 어떤 전략 객체를 사용할지 지정한다. 하지만 상태 패턴에서 클라이언트는 상태 객체를 모른다. 어떤 상태 객체가 사용될지 결정하는 것은 클라이언트와 상관 없이 Context 객체가 결정한다.

 

적용 예시

 

요구사항

다음과 같은 상태 기계를 가지는 뽑기 기계를 구현한다.

 

설계

State 인터페이스에는 뽑기 기계가 각 상태에서 보일 수 있는 모든 행동의 명세를 가지고 있다. 그리고 구상 상태 클래스에서 행동을 구현한다. 이때 상태마다 구현할 필요가 없는 행동은 그냥 실행할 수 없다는 메시지를 출력한다.

 

코드

State

public interface State {
 
	public void insertQuarter();
	public void ejectQuarter();
	public void turnCrank();
	public void dispense();
	
	public void refill();
}

SoldState

public class SoldState implements State {
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}
 
	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}
 
	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
	
	public void refill() { }
 
	public String toString() {
		return "dispensing a gumball";
	}
}

HasQuarterState

import java.util.Random;

public class HasQuarterState implements State {
	Random randomWinner = new Random(System.currentTimeMillis());
	GumballMachine gumballMachine;
 
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
  
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}
 
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	public void turnCrank() {
		System.out.println("You turned...");
		int winner = randomWinner.nextInt(10);
		if ((winner == 0) && (gumballMachine.getCount() > 1)) {
			gumballMachine.setState(gumballMachine.getWinnerState());
		} else {
			gumballMachine.setState(gumballMachine.getSoldState());
		}
	}

    public void dispense() {
        System.out.println("No gumball dispensed");
    }
    
    public void refill() { }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}

GumballMachine(Context)

public class GumballMachine {
 
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;
 
	State state = soldOutState;
	int count = 0;
 
	public GumballMachine(int numberGumballs) {
		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);

		this.count = numberGumballs;
 		if (numberGumballs > 0) {
			state = noQuarterState;
		} 
	}
 
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}

	void setState(State state) {
		this.state = state;
	}
 
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count > 0) {
			count = count - 1;
		}
	}
 
	int getCount() {
		return count;
	}
 
	void refill(int count) {
		this.count += count;
		System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
		state.refill();
	}

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\\nMighty Gumball, Inc.");
		result.append("\\nJava-enabled Standing Gumball Model #2004");
		result.append("\\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\\n");
		result.append("Machine is " + state + "\\n");
		return result.toString();
	}
}

Test(Client)

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = 
			new GumballMachine(10);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

MVC (아키텍처 패턴)  (1) 2023.02.24
Proxy  (1) 2023.02.24
Composite  (0) 2023.02.24
Iterator  (0) 2023.02.24
Template Method  (0) 2023.02.24