Visitor (방문자)

2022. 3. 25. 00:00CSharp/Design Pattern

반응형

알고리즘이 작동하는 객체와 알고리즘을 분리할 수 있게 해주는 행동 디자인 패턴

이렇게 분리를 하면 구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다. 

개방-폐쇄 원칙을 적용하는 방법의 하나이다.

 

문제

하나의 거대한 그래프로 구성된 지리 정보로 작동하는 앱을 개발한다고 상상해 보자

그래프의 각 노드는 도시와 같은 복잡한 엔터티를 나타낼 수 있다.

또한 산업, 관광 지역 등과 같은 더 세분화된 항목을 나타낼 수도 있다.

노드가 나타내는 실제 개체 사이에 도로가 있는 경우 노드는 다른 노드와 연결된다.

 

https://refactoring.guru/design-patterns/visitor

 

어느 시점에서 그래프를 XML 형식으로 내보내는 작업을 구현해야 한다. 

그래서 기존 노드 클래스에 노드별로 Export 기능을 구현하고 다형성을 통하여 재귀적으로 호출 하려고 했다.

하지만 기존 노드 클래스 변경을 시스템 설계자가 거부 했다.

이유는 프로덕션 단계이기 때문이고 실제 이 xml 내보내기 코드가 노드 안에 있어야 하는 이유가 없다고 생각했다.

또한 xml 이 아닌 csv 또는 json 형태 또는 image 로 변경되었을때 해당 내역을 노드안에 다시 구현하는 것도

문제가 있는 방법이다. 

 

해결책

새 동작 을 기존 클래스에 통합하는 대신 방문자 라는 별도의 클래스에 배치하 자

 

class diagram

https://refactoring.guru/design-patterns/visitor

Visitor(interface)

Element를 방문하고 동작을 구현하기 위한 인터페이스

 

ConcreateVisitors

Visitor interface 를 구현하고 있는 실제 알고리즘 구현체

 

Element

구조를 구성하는 인터페이스이자 Visitor 가 방문하여 수행해야 할 대상

 

ConcreateElement

Element 의 실제 구현체

 

 

 

적용

  • 복잡한 개체 구조(예: 개체 트리)의 모든 요소에 대해 작업을 수행해야 하는 경우
  • 보조 동작의 비즈니스 논리를 정리 하려는 경우
  • 행동이 클래스 계층의 일부 클래스에서만 의미가 있고 다른 클래스에서는 의미가 없을 경우

 

(직업은 전사와 도적만으로 고정된다고 가정해보자)

전사와 도적이 여러가지 Attack 을 한다고 하자.

NormalAttack

SpecialAttack

....

....

등을 한다고 가정하자

attack 은 계속 늘어 날수 있다고 가정한다면

 

둘은 공격시 동작도 다르고 무기도 다르다.

둘은 방어시 동작도 다르고  method 이름조차 다르다.

(한쪽은 defence, 다른한쪽은 evasion)

해당 내용을 코드로 구현해 보자

 

Visitor 

 public interface IVisitor
{
    void Attack(Warrior element);
    void Attack(Rogue element);

}

public class NormalAttack : IVisitor
{
    public void Attack(Warrior element)
    {
        Console.WriteLine("전사가 대검으로 공격");
        Console.WriteLine(element.Defence());
    }

    public void Attack(Rogue element)
    {
        Console.WriteLine("도적이 단검으로 공격");
        Console.WriteLine(element.Evasion());
    }
}

public class SpecialAttack : IVisitor
{
    public void Attack(Warrior element)
    {
        Console.WriteLine("전사가 해머로 공격");
        Console.WriteLine(element.Defence());
    }

    public void Attack(Rogue element)
    {
        Console.WriteLine("도적이 독묻은 단검으로 공격");
        Console.WriteLine(element.Evasion());
    }
}

Element

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

public class Warrior : IElement
{
    public void Accept(IVisitor visitor)
    {
        visitor.Attack(this);
    }
    public string Defence() => "방어모드";

}

public class Rogue : IElement
{
    public void Accept(IVisitor visitor)
    {
        visitor.Attack(this);
    }
    public string Evasion() => "회피실행";
}

 

사용법

IElement warrior = new Warrior();
IElement rogue = new Rogue();
IVisitor normalAttack = new NormalAttack();
IVisitor specialAttack = new SpecialAttack();

warrior.Accept(normalAttack);
rogue.Accept(specialAttack);
warrior.Accept(normalAttack);
rogue.Accept(specialAttack);
// output
전사가 대검으로 공격
방어모드
도적이 독묻은 단검으로 공격
회피실행
전사가 대검으로 공격
방어모드
도적이 독묻은 단검으로 공격
회피실행

 

관련영상

https://youtu.be/iZ76EgD9-8I

 

 

 

 

 

 

반응형

'CSharp > Design Pattern' 카테고리의 다른 글

Strategy (전략패턴)  (0) 2022.03.24
Observer  (0) 2022.03.23
Mediator  (0) 2022.03.22
Command  (0) 2022.03.21
Template Method  (0) 2022.03.18