Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/디자인패턴

[Unity] 유니티 C#) Visitor 패턴

새벽_글쓴이 2024. 12. 6. 21:55
반응형

방문자 패턴

알고리즘을 객체 구조에서 분리시키는 디자인 패턴

주로 복잡한 객체 구조에서 각 객체에 대해 서로 다른 작업을 수행해야 할 때 사용된다

 

기본 구현 방법

1. Visitor 인터페이스

public interface IVisitor
{
    void Visit(Enemy enemy);
    void Visit(Item item);
    void Visit(NPC npc);
}

 

2. 방문 가능한 요소들의 인터페이스

public interface IElement
{
    void Accept(IVisitor visitor);
}

 

3. 구체적인 게임 요소들

public class Enemy : MonoBehaviour, IElement
{
    public int health;
    public int damage;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Item : MonoBehaviour, IElement
{
    public string itemName;
    public int value;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class NPC : MonoBehaviour, IElement
{
    public string npcName;
    public string dialogue;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

 

4. 구체적인 Visitor 클래스들

public class SaveGameVisitor : IVisitor
{
    public void Visit(Enemy enemy)
    {
        // 적 데이터 저장
        SaveData("enemy", new Dictionary<string, object>
        {
            {"health", enemy.health},
            {"damage", enemy.damage},
            {"position", enemy.transform.position}
        });
    }

    public void Visit(Item item)
    {
        // 아이템 데이터 저장
        SaveData("item", new Dictionary<string, object>
        {
            {"name", item.itemName},
            {"value", item.value},
            {"position", item.transform.position}
        });
    }

    public void Visit(NPC npc)
    {
        // NPC 데이터 저장
        SaveData("npc", new Dictionary<string, object>
        {
            {"name", npc.npcName},
            {"dialogue", npc.dialogue},
            {"position", npc.transform.position}
        });
    }

    private void SaveData(string type, Dictionary<string, object> data)
    {
        // 실제 저장 로직
        Debug.Log($"Saving {type} data: {string.Join(", ", data)}");
    }
}

public class DebugVisitor : IVisitor
{
    public void Visit(Enemy enemy)
    {
        Debug.Log($"Enemy - Health: {enemy.health}, Damage: {enemy.damage}");
    }

    public void Visit(Item item)
    {
        Debug.Log($"Item - Name: {item.itemName}, Value: {item.value}");
    }

    public void Visit(NPC npc)
    {
        Debug.Log($"NPC - Name: {npc.npcName}, Dialogue: {npc.dialogue}");
    }
}

 

5. 게임 매니저에서 사용

public class GameManager : MonoBehaviour
{
    private IVisitor saveVisitor;
    private IVisitor debugVisitor;

    void Start()
    {
        saveVisitor = new SaveGameVisitor();
        debugVisitor = new DebugVisitor();
    }

    public void SaveGameState()
    {
        var elements = FindObjectsOfType<MonoBehaviour>().OfType<IElement>();
        foreach (var element in elements)
        {
            element.Accept(saveVisitor);
        }
    }

    public void DebugGameState()
    {
        var elements = FindObjectsOfType<MonoBehaviour>().OfType<IElement>();
        foreach (var element in elements)
        {
            element.Accept(debugVisitor);
        }
    }
}

 

핵심 개념

  • 객체 구조와 동작을 분리
  • 더블 디스패치(double dispatch)를 통한 동작 확장
  • 객체의 클래스를 수정하지 않고 새로운 동작 추가 가능
  • Visitor와 Element의 인터페이스 기반 설계

 

사용목적

  • 객체 구조와 알고리즘 분리
  • 새로운 동작의 쉬운 추가
  • 관련 동작들의 중앙 집중화
  • 객체 수정 없이 기능 확장

 

사용사례

  • 게임 저장/로드 시스템
  • 디버깅/로깅 시스템
  • 데이터 수집/분석
  • AI 행동 패턴 구현

 

사용 사례 간단한 예시 코드

기본 인터페이스

public interface IVisitor
{
    void Visit(Enemy enemy);
    void Visit(Item item);
}

public interface IElement
{
    void Accept(IVisitor visitor);
}

 

게임요소

public class Enemy : IElement
{
    public int health;
    
    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

 

저장 시스템

public class SaveVisitor : IVisitor
{
    public void Visit(Enemy enemy)
    {
        // 적 데이터 저장
        PlayerPrefs.SetInt("EnemyHealth", enemy.health);
    }

    public void Visit(Item item)
    {
        // 아이템 데이터 저장
        PlayerPrefs.SetString("ItemData", JsonUtility.ToJson(item));
    }
}

 

디버그 시스템

public class DebugVisitor : IVisitor
{
    public void Visit(Enemy enemy)
    {
        Debug.Log($"Enemy Health: {enemy.health}");
    }

    public void Visit(Item item)
    {
        Debug.Log($"Item: {item.name}");
    }
}

 

결론

장점

객체와 동작의 명확한 분리
새로운 기능 추가가 용이
코드의 유지보수성 향상
관련 동작들의 집중화

 

단점

새로운 Element 추가 시 모든 Visitor 수정 필요
복잡한 구조로 인한 러닝 커브
캡슐화 위반 가능성

 

적절한 사용 시점

복잡한 객체 구조에 다양한 동작 필요
객체 수정 없이 새로운 동작 추가 필요
관련 동작들의 중앙 집중적 관리 필요

 

Visitor 패턴은 복잡한 객체 구조에서 유연한 동작 확장이
필요할 때 유용한 디자인 패턴인 것 같다
반응형