방문자 패턴이란?
이름에서도 알 수 있듯이 주요 로직의 흐름이 방문을 통해 이루어진다.
어떠한 방문자가 데이터 각 요소에 방문을 함으로써 특정 요소의 작업을 할 수 있게 된다.
따라서 방문자에 해당하는 컴포넌트와 방문할 장소 그리고 이를 사용할 클라이언트로 크게 나뉜다.
IVisitor 인터페이스를 상속받은 Visitor가 IVisitable을 상속받은 ConcreteVisitable에 방문을 하는 구조라고 생각하면 된다. 그리고 Client는 이 구조를 사용하게 된다.
실제로 방문자 패턴을 구현해 보면 구조가 간단하지 않기 때문에 UML 다이어그램 구조를 기억한 채로 사용하는 편이 좋다.
왜 사용하는 것일까?
유니티 엔진에서 다양한 스킬 혹은 아이템으로 버프를 받는 상황을 생각해 보자.
아이템이나 스킬로 인한 버프를 리스트에 담아 관리를 해야 할까?
이러한 방식은 클래스 간의 강한 결합을 유도할 수 있고, 자칫하면 스파게티 코드가 될 가능성이 존재한다.
(아이템이나 스킬 등 버프를 주는 방식이 다양하다면 더욱 그럴 것이다.)
이 경우에 SOLID원칙을 지키며 스크립트를 작성해 나간다면 유지 보수를 단단하게 할 수 있다.
방문자 패턴은 단일 책임 원칙과 개방/폐쇄 원칙을 고수하면서 확장해 나갈 수 있는 디자인 패턴 중 하나이다. 클라이언트 자체는 ConcreteVisitor의 구체적인 내용을 알 필요 없이 사용할 수 있다.
다만 코드의 양이 방대해질 수 있기 때문에 방문자 패턴이 적절하게 쓰임이 있는 곳에만 사용해야 한다.
장점
단일 책임 원칙 : Visitable과 Visitor가 각각의 행동에 대한 책임만을 진다는 점에서 단일 책임 원칙 따름
개방/폐쇄 원칙 : Visitor 혹은 Visitable의 컴포넌트를 추가함으로써 확장에 열려있고 수정에는 닫힌 구조
단점
복잡성 : 일반적인 참조로 이루어진 구조에 비해 많은 스크립트와 상속과 참조를 가진 구조를 가지므로 상대적으로 복잡
어떻게 사용할 수 있을까?
위에서 예시로 든 버프를 관리하는 코드를 생각해 보자.
스크립트는 구체 클래스 Visitable을 위한 인터페이스 IVisitable로 시작을 한다.
public interface IVisitable
{
void Accept(IVisitor visitor);
}
IVisitable 인터페이스는 방문자가 방문 가능한 클래스(Visitable)를 상속하기 위한 것이다.
아래는 IVisitable 인터페이스를 상속받은 구체 클래스의 예시이다.
첫 번째는 공격력 버프이다. 데미지와 관련된 버프를 받는다.
public class DamageBuff : MonoBehaviour, IVisitable
{
public int damage = 0;
public void Accept(IVisitor visitor)
{
Debug.Log("Damage increased!");
visitor.Visit(this);
}
}
두 번째는 방어력 버프이다.
public class DefenseBuff : MonoBehaviour, IVisitable
{
public int defense = 0;
public void Accept(IVisitor visitor)
{
Debug.Log("Defense Increased!");
visitor.Visit(this);
}
}
방문자(Visitor) 클래스는 이 두 개의 구체 클래스를 매개변수로 받는 함수를 가진다. 그러기 위해서 먼저 IVisitable 인터페이스를 작성한다.
아래는 IVisitor 인터페이스이다.
public interface IVisitor
{
void Visit(DamageBuff visitable);
void Visit(DefenseBuff visitable);
}
이를 상속받은 Visitor클래스는 Scriptable Object로 작성한다. 이는 여러 개의 프리팹으로 만들어 필요한 개수만큼 사용할 수 있음을 나타내기 위해서이다.
아래는 IVisitor 인터페이스를 상속받은 Enchant라는 클래스이며, 방문자 클래스로써 버프를 주는 역할을 한다.
[CreateAssetMenu(fileName = "Enchant", menuName = "DesignPattern/VisitorPattern/Enchant")]
public class Enchant : ScriptableObject, IVisitor
{
public int damageIncrease;
public int defenseIncrease;
public void Visit(DamageBuff visitable)
{
visitable.damage += damageIncrease;
}
public void Visit(DefenseBuff visitable)
{
visitable.defense += defenseIncrease;
}
}
Scriptable Object를 여러개 만들어서 예제에 활용하고자 하였다.
추가로 유니티 월드 상에 배치되어 플레이어와 충돌할 경우 플레이어에게 이벤트를 전달할 PickUp 클래스도 작성한다.
이 클래스는 OnTriggerEnter을 통해 플레이어와 충돌 시 Enchant를 플레이어에게 전달하는 역할을 한다.
public class PickUp : MonoBehaviour
{
public Enchant enchant;
private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent(out IVisitable player))
{
player.Accept(enchant);
}
Destroy(gameObject);
}
}
마지막으로 플레이어 클래스이다. 플레이어에겐 공격력과 방어력 버프가 있고 초기값은 0이다.
public class Player : MonoBehaviour, IVisitable
{
List<IVisitable> buffList = new();
public void Accept(IVisitor visitor)
{
foreach (var buff in buffList)
{
buff.Accept(visitor);
}
}
private void Awake()
{
var damageBuff = gameObject.AddComponent<DamageBuff>();
var defenseBuff = gameObject.AddComponent<DefenseBuff>();
buffList.Add(damageBuff);
buffList.Add(defenseBuff);
}
}
이제 버프가 플레이어와 충돌되면 버프가 오르는 것을 확인할 때이다.
큐브가 플레이어이고 스피어는 모두 Enchant를 가진 PickUp들이다.
방문자 패턴으로 구현된 아이템들이 모두 잘 적용되는 것을 확인할 수 있다.
'유니티 > 디자인 패턴' 카테고리의 다른 글
[Unity] 이벤트 버스 패턴 (Event Bus Pattern) (0) | 2024.04.12 |
---|---|
[Unity] 메멘토 패턴 (Memento Pattern) (0) | 2024.04.12 |
[Unity] 서비스 로케이터 패턴 (Service Locator Pattern) (0) | 2024.04.09 |
[Unity] 널 객체 패턴 (Null Object Pattern) (1) | 2023.10.25 |
[Unity] 커맨드 패턴 (Command Pattern) (1) | 2023.10.22 |