영권's

디자인 패턴(Behavioral) - Visitor 본문

스터디/디자인 패턴

디자인 패턴(Behavioral) - Visitor

ykkkk 2021. 9. 10. 04:24

Visitor Patterns

객체 지향 프로그래밍 소프트웨어 공학에서 비지터 패턴(visitor pattern; 방문자 패턴)은 알고리즘(비즈니스 로직)을 객체 구조에서 분리시키는 디자인 패턴이다.

이렇게 분리를 하면 구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다. 개방-폐쇄 원칙을 적용하는 방법의 하나이다.  (출처 : 위키백과)

 

 

문제점 예시 상황

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

하나의 거대한 그래프로 구성된 지리 정보와 함께 작동하는 앱을 개발한다고 했을 때, 그래프의 각 노드는 도시와 같은 복잡한 실체를 나타낼 수 있지만, 산업, 관광 지역 등과 같은 보다 세분화된 것들을 나타낼 수도 있다. 노드가 나타내는 실제 개체 사이에 도로가 있으면 노드가 다른 개체와 연결됩니다.
이후에 그래프를 xml형식으로 내보내는 작업을 구현해야 합니다.

처음에는 작업이 매우 간단해 보였고 각 노드 클래스에 내보내기 메서드를 추가하고 재귀를 사용해서 그래프의 각 노드를 통과하여 내보내기 메서드를 실행하는 것으로 구현할 수 있습니다.하지만 다형성 때문에 노드 클래스를 구현한 곳에 내보내기 메서드를 호출하는 코드를 결합하지 못합니다.

또한, 사용자가 기존 노드 클래스를 변경할 수 있도록 허용하지 않고 구현된 후 만약 다른 형식으로 내보낼 수 있는 기능을 제공하거나 다른 요청을 원할 때 확장하기 어려울 수 있습니다.

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

 

문제에 대한 해결책

 

방문자 패턴은 새 동작을 기존 클래스에 통합하는 대신 방문자라는 별도의 클래스에 배치하는 것을 나타냅니다. 동작을 수행해야 했던 원래 개체는 이제 개체 내에 포함된 모든 필요한 데이터에 대한 메서드 액세스를 제공하는 방문자의 메서드 중 하나에 인수로 전달됩니다.

이제, 만약 그 행동이 다른 클래스의 객체들에 대해 실행될 수 있다면 어떨까요? 예를 들어 XML 내보내기의 경우 실제 구현은 다양한 노드 클래스에 따라 약간 다를 수 있습니다. 그러므로 방문자 클래스는 하나가 아니라 메소드 집합을 정의할 수 있으며, 각각은 다음과 같이 다른 유형의 인수를 취할 수 있다.

class ExportVisitor implements Visitor is
    method doForCity(City c) { ... }
    method doForIndustry(Industry f) { ... }
    method doForSightSeeing(SightSeeing ss) { ... }
    // ...



하지만 이런 방법들을 정확히 어떻게 호출할까요? 특히 전체 그래프를 다룰 때요. 이 메소드들은 서로 다른 시그니처을 가지고 있기 때문에 다형성을 사용할 수 없습니다. 지정된 개체를 처리할 수 있는 적절한 방문자 메서드를 선택하려면 해당 클래스를 확인해야 합니다. 

foreach (Node node in graph)
    if (node instanceof City)
        exportVisitor.doForCity((City) node)
    if (node instanceof Industry)
        exportVisitor.doForIndustry((Industry) node)
    // ...
}


메서드 오버로딩을 사용하는 것이 어떻겠냐고 물을 수도 있습니다. 다른 매개 변수 집합을 지원하는 경우에도 모든 메서드에 동일한 이름을 지정합니다. 유감스럽게도 프로그래밍 언어가 이를 지원한다고 가정하더라도(Java와 C#처럼) 도움이 되지 않습니다. 노드 객체의 정확한 클래스는 미리 알 수 없으므로 오버로드 메커니즘은 실행할 올바른 메서드를 결정할 수 없습니다. 기본 노드 클래스의 개체를 사용하는 메서드가 기본값입니다.

그러나 방문자 패턴은 이 문제를 해결합니다. Double Dispatch라는 기술을 사용하여 번거로운 조건 없이 객체에서 적절한 메소드를 실행할 수 있습니다. 

클라이언트가 호출할 메소드의 적절한 버전을 선택하도록 하는 대신 방문자에게 인수로 전달할 개체에 이 선택을 위임하는 것은 어떨까요? 

객체는 자신의 클래스를 알고 있기 때문에 방문자에게 적절한 방법을 덜 어색하게 선택할 수 있을 것이다. 그들은 방문객을 "accept" 하고 어떤 메서드가 실행되어야 하는지 알려줍니다.

// Client code
foreach (Node node in graph)
    node.accept(exportVisitor)

// City
class City is
    method accept(Visitor v) is
        v.doForCity(this)
    // ...

// Industry
class Industry is
    method accept(Visitor v) is
        v.doForIndustry(this)
    // ...


결국 노드 클래스를 변경해야 했습니다. 하지만 적어도 그 변화는 사소하고 그것은 우리가 코드를 다시 바꾸지 않고 더 많은 행동을 추가하도록 해준다.

이제 모든 방문자를 위한 공통 인터페이스를 추출하면 앱에 소개하는 모든 방문자와 기존의 모든 노드가 작동할 수 있습니다. 만약 당신이 노드와 관련된 새로운 행동을 도입하고 있다면, 당신은 새로운 방문자 클래스를 구현하기만 하면 된다.

 

 

 

 

 

 

 

Visitor Patterns Dlass Diagram

  1. Visitor interface는 개체 구조의 구체적인 요소를 인수로 사용할 수 있는 방문 메서드 집합을 선언합니다. 프로그램이 오버로딩을 지원하는 언어로 작성된 경우 이러한 메서드는 동일한 이름을 가질 수 있지만 매개 변수의 유형은 달라야 합니다.
  2. Concrete Visitor는 서로 다른 concrete element 클래스에 맞게 조정된 동일한 동작의 여러 버전을 구현합니다.
  3. Element interface는 방문자를 "accept"하는 방법을 선언합니다. 이 메서드에는 방문자 인터페이스 형식으로 선언된 매개 변수가 하나 있어야 합니다.
  4. Concrete Element는 accept() 메서드을 구현해야 합니다. 이 메서드의 목적은 현재 요소 클래스에 해당하는 적절한 방문자 메서드로 호출을 리다이렉션하는 것입니다. 기본 요소 클래스가 이 메서드를 구현하더라도 모든 하위 클래스는 여전히 자체 클래스에서 이 메서드를 재정의하고 방문자 개체에서 적절한 메서드를 호출해야 합니다.
  5. 클라이언트는 일반적으로 컬렉션이나 다른 복잡한 개체(예: 복합 트리)를 나타냅니다. 일반적으로 클라이언트는 추상 인터페이스를 통해 해당 컬렉션의 개체와 함께 작동하기 때문에 concrete element 클래스를 모두 인식하지 못합니다.

 

적용 가능성

  • 복잡한 개체 구조의 모든 요소(예: 개체 트리)에 대해 작업을 수행해야 하는 경우 방문자를 사용합니다.
    • 방문자 패턴은 방문자 객체가 모든 대상 클래스에 해당하는 동일한 연산의 여러 변형을 구현하도록 하여 다른 클래스를 가진 객체 집합에 대해 연산을 실행할 수 있도록 합니다.
  • Visitor를 사용하여 보조 동작의 비즈니스 논리를 정리합니다.
    • 이 패턴을 사용하면 다른 모든 동작을 Visitor클래스에 추출하여 주요 비즈니스 로직을 작성하는데 더 집중할 수 있습니다.
  • 동작이 클래스 계층의 일부 클래스에서만 의미가 있고 다른 클래스에서는 의미가 없는 경우 패턴을 사용합니다.
    • 이 동작을 별도의 방문자 클래스에 추출하고 관련 클래스의 개체를 수락하는 visiting 메서드만 구현할 수 있으며 나머지는 비워 둘 수 있습니다.

 

장점

  • Open/Closed Principle. : 클래스를 변경하지 않고 다른 클래스의 개체와 함께 작동할 수 있는 새로운 동작을 도입할 수 있습니다.
  •  Single Responsibility Principle : 동일한 동작의 여러 버전을 동일한 클래스로 이동할 수 있습니다.
  • 방문자 개체는 다양한 개체로 작업하는 동안 유용한 정보를 축적할 수 있습니다. 이 기능은 개체 트리와 같은 일부 복잡한 개체 구조를 이동하고 이 구조의 각 개체에 방문자를 적용하려는 경우에 유용할 수 있습니다.

 

단점

  • 클래스가 element 계층에 추가되거나 제거될 때마다 모든 방문자를 업데이트해야 합니다.
  • 방문자는 작업해야 할 요소의 개인 필드와 메소드에 필요한 액세스 권한이 부족할 수 있습니다.

 

예제 코드


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

https://www.youtube.com/watch?v=s-tXAHub6vg&t=1s 

 

receiver parameter :

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html

https://medium.com/the-java-report/the-java-8-feature-you-never-ever-used-d0b343125cea

Comments