[Unity] Strategy Pattern
Strategy Pattern
동일 계열의 알고리즘을 정의하고 각각의 알고리즘을 캡슐화하여 상호교환이 가능하도록 만드는 패턴이다. 쉽게 설명하면 객체 내에서 알고리즘이나 동작을 캡슐화 하고 이를 실질적으로 교환이 가능하게끔 만들어 준다는 것에 초점을 두면 된다. 각 객체는 동적으로 실행될 수 있는 고유한 동작을 캡슐화하고, 알고리즘을 사용하는 사용자와 상관없이 독립적으로 알고리즘을 다양하게 런타임시 변경할 수 있게 하는 것이다.
그림을 살펴보면 베이스 클래스인 Strategy가 존재하고, 이를 구체화해서 만든 파생 클래스가 ConcreteStrategy 클래스들이다. 실시간으로 Context에서 A나 B를 자유롭게 변경할 수 있도록 만들어준다.
예시
게임에서 어빌리티 시스템을 만든다고 예를 들어보자.
public class AbilityRunner : MonoBehaviuor {
public enum Ability {
RadarPulse,
AirSupport,
FirstAid
}
public Ability currentAbility;
void ActivateAbility(Ability ability) {
switch (ability) {
case Ability.RadarPulse:
Debug.Log("Activating Radar Pulse");
break;
case Ability.AirSupport:
Debug.Log("Calling in Air Support");
break;
case Ability.FirstAid:
Debug.Log("Using First Aid");
break;
}
}
}
각각의 속성마다 switch case문을 만들게 된다면 컨텐츠가 업데이트 될 때 switch case문이 계속해서 늘어나게 될 것이다. 이를 클래스로 빼고 보완해두면 아래의 코드와 같다.
public abstract class Ability : ScriptableObject {
// Ability라는 Base Class
[SerializeField] protected string m_AbilityName;
...
public virtual void Use(GameObject gameObject) {
// 공통 기능
Debug.Log("$Using ability : {m_AbilityName}");
PlaySound();
PlayParticleFX();
}
}
public class AirSupportAbility : Ability {
// Ability를 상속
public override void Use(GameObject gameObject) {
base.Use(gameObject);
// 추가 기능 구현
}
}
public class RadarPulseAbility : Ability { ... }
public class FirstAidAbility : Ability { ... }
// ---
public class AbilityRunner : MonoBehaviuor {
private Ability m_CurrentAbility;
...
public void OnAbilityButtonClicked() {
if (m_CurrentAbility != null) {
m_CurrentAbility.Use(this.gameObject);
// 실행
}
}
}
클래스를 추가하여도 기존의 Runner는 건들이지 않고 추가하면 된다.
행동이 조금씩 다를뿐 개념적으로 관련된 많은 클래스들이 존재할 때 해당 패턴은 유용하게 쓰인다. 하나의 클래스에 몰아서 사용하는 것이 아닌, 상속을 이용해 여러 클래스를 생성하고 사용하면 된다. 알고리즘의 변형이 필요할 때, 이러한 변형물들이 알고리즘의 상속 관계도 구현될 때 사용 가능하다. 만약 사용자가 몰라야 하는 알고리즘이 있을 때, 노출하지 말아야 할 복잡한 자료구조는 Strategy 클래스로 적용한다. 많은 행동들이 하나의 클래스 연산안에서 복잡한 다중 조건문의 모습을 취할 때 프로토타입에서는 상관없으나, 확장될 우려가 있으면 리팩토링해서 Strategy Pattern을 사용하면 된다.