본문 바로가기
java/java 리팩토링

07. 전략, 상태패턴 적용하기

by 문자메일 2023. 2. 11.

 

상태 패턴

  • 클래스 내의 여러 메서드가 하나의 상태(멤버 변수)에 의존하여 switch 문이 만들어지는 경우
  • 하나의 메서드에서 각 상태에 따라 수행하는 로직이 다른 경우
  • 각 상태를 클래스로 만들고 수행하는 로직은 각 클래스에 작성한다.
  • 여러 상태가 추가되는 경우 확장이 용이하고, 상태의 전환에 대해 인스턴스를 바꾸어주면 되므로 유지보수가 쉽다.

 

전략 패턴

  • 어떤 로직에 대해 여러 알고리즘(전략, 정책)이 있을 수 있는 경우
  • 상황에 따라 전략을 교체할 수 있고, 선택이 추가될 수 있음
  • 각 전략이 해결해야 하는 동일한 메서드가 존재함
  • 메서드에 전략에 따른 다른 수행이 있음
  • 전략마다 클래스로 만들고, 각 클래스에서 해당하는 로직을 수행하도록 하는 메서드 구현

 

상태 패턴과 전략 패턴

  • 두 패턴 모두 상속과 오버라이딩을 활용한 패턴
  • 상태 패턴은 클래스의 상태(주로 변수)에 관련하여 여러 가지 수행이 있는 경우이고, 전략 패턴은 알고리즘이 여러개 존재하는 경우

 

전략 패턴과 상태 패턴의 공통점과 차이점

  • 공통점
    • 둘 다 다형성을 사용해서 Concreate class를 캡슐화 한다는 점.
    • 따라서 Context Class에서 어떤 구상 클래스가 할당되었는지 관계 없이 로직을 실행하면 된다. (변경 대처에 유연하다.)
  • 차이점 (정확한지는 잘 모르겠으나, 생각해본 바 적음)
    • Concreate Class를 외부에서 주입(파라메터로)받아서 사용하도록 구현하면 전략 패턴, 내부에서 만들 수 있으면 상태 패턴으로 치는 것으로 보임.
      • 전략 패턴 예제로 게임에서 player 객체에서 무기에 따른 공격 로직 (attack()메서드) 주입받는 것을 예시로 많이 드는데, 공격 로직은 전략 패턴으로 외부 Client에서 Player 객체에 주입받는 것이 맞는 것 같음.
        상태 패턴으로 구현한다면 객체에 무기에 따른 모든 공격 로직을 생성하는 메서드를 Player 객체에서 가지고 있어야 한다는 건데, 맞지 않아 보임.
      • 위 예시와 반대로 선풍기의 ON/OFF, 혹은 특정 모드(상태) 에 따라 로직이 달라지는 특성을 가진 객체를 구현할 때는 해당 객체에서 그 객체가 가질 수 있는 상태 클래스(로직)을 생성하는 로직을 가지고 있는 것이 캡슐화 적인 부분에서나 맞는 것으로 보임

 

AS-IS

package refactoringwithstatepattern.before;

public class Player {
	
	public static final int BEGINNER_LEVEL = 1;
	public static final int ADVANCED_LEVEL = 2;
	public static final int SUPER_LEVEL = 3;
	private int level;

	public Player() {
		level = BEGINNER_LEVEL;
		showLevelMessage();
	}
	
	public int getLevel() {
		return level;
	}
	
	public void upgradeLevel() {
		if(level == BEGINNER_LEVEL) {
			level = ADVANCED_LEVEL;
		}
		else if(level == ADVANCED_LEVEL) {
			level = SUPER_LEVEL;
		}
		else {
			System.out.println("not support level");
		}
		showLevelMessage();
	}
	
	private String showLevelMessage() {
		// TODO Auto-generated method stub
		return "현재 래벨은 " + String.valueOf(level) + "입니다.";
		
	}

	public void play(int count) {
		run();
		for(int i=0; i<count; i++) {
			jump();
		}
		turn();
	}
	
	public void run() {
		if (level == BEGINNER_LEVEL) {
			System.out.println("천천히 달립니다.");
		}
		else if(level == ADVANCED_LEVEL) {
			System.out.println("빨리 달립니다.");
		}
		else if(level == SUPER_LEVEL) {
			System.out.println("엄청 빨리 달립니다.");
		}
		else {
			System.out.println("not support level");
		}
	}

	public void jump() {
		if (level == BEGINNER_LEVEL) {
			System.out.println("Jump 할 줄 모릅니다.");
		}
		else if(level == ADVANCED_LEVEL) {
			System.out.println("높이 jump 합니다.");
		}
		else if(level == SUPER_LEVEL) {
			System.out.println("엄청 높게 jump 합니다.");
		}
		else {
			System.out.println("not support level");
		}
	}
	
	public void turn() {
		if (level == BEGINNER_LEVEL) {
			System.out.println("turn 할 줄 모릅니다.");
		}
		else if(level == ADVANCED_LEVEL) {
			System.out.println("turn 할 줄 모릅니다.");
		}
		else if(level == SUPER_LEVEL) {
			System.out.println("한 바퀴 돕니다.");
		}
		else {
			System.out.println("not support level");
		}
	}
}

 

package refactoringwithstatepattern.before;

public class MainBoard {

	public static void main(String[] args) {
		
		Player player = new Player();
		player.play(1);
		player.upgradeLevel();
		player.play(2);
		player.upgradeLevel();
		player.play(3);

	}

}

 

 

TO-BE - 템플릿 메서드 패턴(PlayerLevel), 상태 패턴, 싱글턴 패턴 적용한 결과

 

package refactoringwithstatepattern.after2;

public class Player {

	private PlayerLevel playerLevel;

	public Player() {
		playerLevel = BeginnerLevel.getInstance();
		playerLevel.showLevelMessage();
	}
	
	public PlayerLevel getLevel() {
		return playerLevel;
	}
	
	public void upgradeLevel() {
		if(playerLevel instanceof BeginnerLevel) {
			playerLevel = AdvancedLevel.getInstance();
		}
		else if(playerLevel instanceof AdvancedLevel) {
			playerLevel = SuperLevel.getInstance();
		}
		else {
			System.out.println("not support level");
		}
		playerLevel.showLevelMessage();
	}
	
	public void play(int count) {
		playerLevel.go(count);
	}
}
package refactoringwithstatepattern.after2;

public abstract class PlayerLevel {
	protected abstract void run();
	protected abstract void jump();
	protected abstract void turn();
	protected abstract void showLevelMessage();
	
	final public void go(int count) {
		run();
		for(int i=0; i<count; i++) {
			jump();
		}
		turn();
	}
}

 

package refactoringwithstatepattern.after2;

public class SuperLevel extends PlayerLevel{

	private static SuperLevel instance = new SuperLevel();
	
	private SuperLevel() {}
	
	public static SuperLevel getInstance() {
		if( instance == null ) {
			instance = new SuperLevel();
		}
		return instance;
	}
	
	@Override
	protected void run() {
		System.out.println("엄청 빨리 달립니다.");
	}

	@Override
	protected void jump() {
		System.out.println("엄청 높게 jump 합니다.");
	}

	@Override
	protected void turn() {
		System.out.println("한 바퀴 돕니다.");
	}

	@Override
	protected void showLevelMessage() {
		System.out.println("*** 고급자 래벨 입니다. ***");
	}

}

 

package refactoringwithstatepattern.after2;

public class AdvancedLevel extends PlayerLevel{

	private static AdvancedLevel instance = new AdvancedLevel();
	
	private AdvancedLevel() {}
	
	public static AdvancedLevel getInstance() {
		if( instance == null ) {
			instance = new AdvancedLevel();
		}
		return instance;
	}
	
	@Override
	protected void run() {
		System.out.println("빨리 달립니다.");
	}

	@Override
	protected void jump() {
		System.out.println("높이 jump 합니다.");
	}

	@Override
	protected void turn() {
		System.out.println("turn 할 줄 모릅니다.");
	}

	@Override
	protected void showLevelMessage() {
		System.out.println("*** 중급자 래벨 입니다. ***");
	}

}

 

package refactoringwithstatepattern.after2;

public class BeginnerLevel extends PlayerLevel{
	
	private static BeginnerLevel instance = new BeginnerLevel();
	
	private BeginnerLevel() {}
	
	public static BeginnerLevel getInstance() {
		if( instance == null ) {
			instance = new BeginnerLevel();
		}
		return instance;
	}

	@Override
	protected void run() {
		System.out.println("천천히 달립니다.");
	}

	@Override
	protected void jump() {
		System.out.println("Jump 할 줄 모릅니다.");
	}

	@Override
	protected void turn() {
		System.out.println("turn 할 줄 모릅니다.");
	}

	@Override
	protected void showLevelMessage() {
		System.out.println("*** 초급자 래벨 입니다. ***");
	}

}

 

package refactoringwithstatepattern.after2;

public class MainBoard {

	public static void main(String[] args) {
		
		Player player = new Player();
		player.play(1);
		player.upgradeLevel();
		player.play(2);
		player.upgradeLevel();
		player.play(3);

	}

}

댓글