전략 패턴 (Strategy Pattern)

티원·2025년 12월 18일

디자인패턴

목록 보기
14/16

상태 패턴과 비슷하지만, 전략패턴은 전략(상태)를 외부에서 전환한다

왜 쓰는가?
switch-case문으로 추가하게되면 아래에 case문이 계속 추가되면서 코드 길어지고 유지보수가 힘들어져서
또한 분기가 계속 증가할 경우에도 전략패턴은 상속을 하니까 하나만 써서 해결할 수 있다.

아래의 코드를 전략 패턴으로 바꾸었다

public enum eMonsterAction
{
    Idle=1, AttackReady,Attack
}
public class Monster :Monobehavior
{
	eMonsterAction actionType;
	private start()
	{
		//무조건 공격상태라고 가정
		actionType = eMonsterAction.Attack;
	}
	private IEnumerator MonsterActDo()
	{
		//어떤 행동인지 찾아서 그 행동을 수행한다
		switch (actionType)
		{
			case eMonsterAction.Idle:
				MonsterIdleState(this,action); 
				break;
			case eMonsterAction.AttackReady:
				MonsterAttackReadyState(this,action);
				break;
			case eMonsterAction.Attack:
				MonsterAttackState(this, action);
				break;
		}
	}
}

0. 고려해야할점

  • 전략패턴 -> 메서드 (행동)들만 넣자
  • 부모에 필드는 넣지말고 필요하다면 자식에 따로따로 넣는 것이 좋을듯

1. 추상클래스, 혹은 인터페이스 부모를 만들어준다

현재 case문을 보면 상태가 3개가 있는데 이것들은 하나의 매개변수를 받는 이름만 다른 메서드를 가지고있다

public abstract class MonsterStrategy
{
    public abstract void MonsterActDo(Monster monster);
}

2. 이제 자식을 만들어준다

현재 case문을 보면 상태가 3개가 있기때문에 이것을 전부. 이것을 클래스로 바꿔준다

public class MonsterIdleStrategy : MonsterStrategy
{
    public override void MonsterActDo(Monster monster)
    {
        //현재 아무것도안함. 추후 sprite이미지, sound, effect만 변경되면 될듯 
    }
}
public class MonsterAttackReadyStrategy : MonsterStrategy
{
    public override void MonsterActDo(Monster monster)
    {
        //현재 아무것도안함. 추후 sprite이미지, sound, effect만 변경되면 될듯 
    }
}
public class MonsterAttackStrategy : MonsterStrategy
{
    public override void MonsterActDo(Monster monster)
    {
        Debug.Log("몬스터 공격 상태");
    }
}

3. 어떤 전략패턴을 쓸건지 결정해주는 switch문이나 딕셔너리를 만든다

둘중 아무거나 써도 상관없다. 익숙해질때까지는 3-1 방식이 나을듯

3-1 switch-case문 방식

스위치-케이스문 안에서 전략패턴을 결정해서 사용

public enum eMonsterAction
{
    Idle=1, AttackReady,Attack
}
public class Monster :Monobehavior
{
	eMonsterAction actionType;
	MonsterStrategy monsterStrategy;
	private start()
	{
		//무조건 공격상태라고 가정
		actionType = eMonsterAction.Attack;
	}
	private IEnumerator MonsterActDo()
	{
		//어떤 행동인지 찾아서 그 행동을 수행한다
		switch (actionType)
		{
			case eMonsterAction.Idle:
				monsterStrategy = new MonsterIdleStrategy(this,);
				break;
			case eMonsterAction.AttackReady:
				monsterStrategy = new MonsterAttackReadyStrategy();
				break;
			case eMonsterAction.Attack:
				monsterStrategy = new MonsterAttackStrategy();
				break;
		}
		monsterStrategy.MonsterActDo(this); //전략에 따라 다른 메서드 실행
	}
}

3-2 딕셔너리 방식

딕셔너리로 매번 new로 클래스를 생성해주지않아서 GC부담 줄이는 방식

public enum eMonsterAction
{
    Idle=1, AttackReady,Attack
}
public class Monster :Monobehavior
{
	eMonsterAction actionType;
	MonsterStrategy monsterStrategy;
	Dictionary<eMonsterAction, MonsterStrategy> monsterStrategyDic;
	private start()
	{
		//무조건 공격상태라고 가정
		actionType = eMonsterAction.Attack;
		MakeMonsterStrategyDic();
	}
	public void MakeMonsterStrategyDic()
	{
		//foreach문 돌리며 모든 enum에 대해 딕셔너리키를 통해 전략을 설정
		foreach(eMonsterAction type in Enum.GetValues(typeof(eMonsterAction)))
		{
			switch(type)
			{
				case eMonsterAction.Idle:
					monsterStrategyDic[type] = new MonsterIdleStrategy();
					break;
				case eMonsterAction.AttackReady:
					monsterStrategyDic[type] = new MonsterAttackReadyStrategy();
					break;
				case eMonsterAction.Attack:
					monsterStrategyDic[type] = new MonsterAttackStrategy();
					break;
			}
		}
	}
	private IEnumerator MonsterActDo()
	{
		//어떤 행동인지 찾아서 그 행동을 수행한다
		monsterStrategyDic[actionType].MonsterActDo(this);
	}
}

0개의 댓글