본문 바로가기
공부/Object-Oriented Design Pattern

Visitor Pattern (방문자 패턴)이란

by 혼밥맨 2021. 5. 11.
반응형

Visitor Pattern (방문자 패턴)이란

 

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

 

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

방문자는 알고리즘을 동작하는 개체와 분리할 수 있는 동작 설계 패턴입니다.

Problem

Imagine that your team develops an app which works with geographic information structured as one colossal graph. Each node of the graph may represent a complex entity such as a city, but also more granular things like industries, sightseeing areas, etc. The nodes are connected with others if there’s a road between the real objects that they represent. Under the hood, each node type is represented by its own class, while each specific node is an object.

당신의 팀이 하나의 거대한 그래프로 구성된 지리적 정보로 작동하는 앱을 개발한다고 상상해보라. 그래프의 각 노드는 도시와 같은 복잡한 실체를 나타낼 수도 있지만 산업, 관광 지역 등과 같은 보다 세분화된 것들을 나타낼 수도 있다. 노드가 나타내는 실제 개체 사이에 도로가 있으면 다른 개체와 연결됩니다. 후드 아래에서 각 노드 유형은 자체 클래스로 표시되며, 각 특정 노드는 개체입니다.

 

At some point, you got a task to implement exporting the graph into XML format. At first, the job seemed pretty straightforward. You planned to add an export method to each node class and then leverage recursion to go over each node of the graph, executing the export method. The solution was simple and elegant: thanks to polymorphism, you weren’t coupling the code which called the export method to concrete classes of nodes.

Unfortunately, the system architect refused to allow you to alter existing node classes. He said that the code was already in production and he didn’t want to risk breaking it because of a potential bug in your changes.

어느 순간 그래프를 XML 형식으로 내보내는 작업을 구현하는 작업이 수행되었습니다. 처음에는 그 일이 꽤 간단해 보였다. export method을 각 노드 클래스에 추가한 다음 재귀 기능을 사용하여 그래프의 각 노드를 탐색하여 export method을 실행하도록 계획했습니다. 솔루션은 단순하고 우아했습니다. 다형성 덕분에 내보내기 방법을 특정 노드 클래스로 호출하는 코드를 결합하지 못했습니다.

시스템 설계자가 기존 노드 클래스를 변경할 수 있도록 허용하지 않았습니다. 그는 코드가 이미 생산 중에 있으며 변경 사항의 잠재적인 버그 때문에 코드를 파손하는 위험을 감수하고 싶지 않다고 말했습니다.

 

 

Besides, he questioned whether it makes sense to have the XML export code within the node classes. The primary job of these classes was to work with geodata. The XML export behavior would look alien there.

There was another reason for the refusal. It was highly likely that after this feature was implemented, someone from the marketing department would ask you to provide the ability to export into a different format, or request some other weird stuff. This would force you to change those precious and fragile classes again.

게다가, 그는 노드 클래스 내에 XML export code를 갖는 것이 타당한지에 대해 의문을 제기했습니다. 이 수업들의 주된 업무는 geodata를 다루는 것이었다. XML export behavior은 거기서 특이하게 보입니다.

거절한 데는 또 다른 이유가 있었다. 이 기능이 실행된 후 마케팅 부서의 누군가가 다른 형식으로 내보낼 수 있는 기능을 제공하거나 다른 이상한 기능을 요청할 가능성이 높습니다. 이것은 여러분이 그 precious and fragile classes을 다시 바꾸도록 강요할 것입니다.

 

Solution

The Visitor pattern suggests that you place the new behavior into a separate class called visitor, instead of trying to integrate it into existing classes. The original object that had to perform the behavior is now passed to one of the visitor’s methods as an argument, providing the method access to all necessary data contained within the object.

Now, what if that behavior can be executed over objects of different classes? For example, in our case with XML export, the actual implementation will probably be a little bit different across various node classes. Thus, the visitor class may define not one, but a set of methods, each of which could take arguments of different types, like this:

 

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

다른 클래스의 개체에서 이러한 동작이 실행될 수 있다면 어떨까요? 예를 들어 XML export의 경우 실제 구현은 다양한 노드 클래스에 따라 약간 다를 수 있습니다. 따라서, 방문자 클래스는 다음과 같이 각각 다른 유형의 주장을 취할 수 있는 하나의 방법이 아니라 일련의 방법을 정의할 수 있다.

 

1
2
3
4
5
class ExportVisitor implements Visitor is
    method doForCity(City c) { ... }
    method doForIndustry(Industry f) { ... }
    method doForSightSeeing(SightSeeing ss) { ... }
    // ...
cs

But how exactly would we call these methods, especially when dealing with the whole graph? These methods have different signatures, so we can’t use polymorphism. To pick a proper visitor method that’s able to process a given object, we’d need to check its class. Doesn’t this sound like a nightmare?

하지만, 특히 전체 그래프를 다룰 때, 우리는 이 방법들을 정확히 어떻게 부를까요? 이 방법들은 서로 다른 signatures을 가지고 있어서 우리는 polymorphism을 사용할 수 없습니다. 주어진 개체를 처리할 수 있는 적절한 visitor method을 선택하려면 해당 클래스를 확인해야 합니다. 악몽 같지 않아요?

1
2
3
4
5
6
7
foreach (Node node in graph)
    if (node instanceof City)
        exportVisitor.doForCity((City) node)
    if (node instanceof Industry)
        exportVisitor.doForIndustry((Industry) node)
    // ...
}
cs

However, the Visitor pattern addresses this problem. It uses a technique called Double Dispatch, which helps to execute the proper method on an object without cumbersome conditionals. Instead of letting the client select a proper version of the method to call, how about we delegate this choice to objects we’re passing to the visitor as an argument? Since the objects know their own classes, they’ll be able to pick a proper method on the visitor less awkwardly. They “accept” a visitor and tell it what visiting method should be executed.

그러나 방문자 패턴은 이 문제를 해결합니다. 이것은 더블 디스패치라는 기술을 사용하며, 이것은 번거로운 조건 없이 개체에서 적절한 메소드를 실행할 수 있게 도와준다. 클라이언트가 호출할 메서드의 적절한 버전을 선택하도록 하는 대신, 방문자에게 인수로서 전달하려는 개체에 이 선택 사항을 delegate (위임)하는 것은 어떨까요? Objects은 그들 자신의 클래스를 알고 있기 때문에, 방문객들에게 덜 어색하게 적절한 방법을 선택할 수 있을 것입니다. 그들은 방문객을 "accept"하고 어떤 visiting method을 실행해야 하는지 알려준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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)
    // ...
cs

I confess. We had to change the node classes after all. But at least the change is trivial and it lets us add further behaviors without altering the code once again.

Now, if we extract a common interface for all visitors, all existing nodes can work with any visitor you introduce into the app. If you find yourself introducing a new behavior related to nodes, all you have to do is implement a new visitor class.

 

고백합니다. 우리는 결국 노드 클래스를 변경해야 했습니다. 그러나 적어도 그 변경은 사소한 것이어서 코드를 다시 변경하지 않고도 추가적인 동작을 추가할 수 있게 해준다.

이제, 모든 방문자를 위한 공통 인터페이스를 추출하면, 모든 기존 노드가 앱에 소개된 모든 방문자와 함께 작동할 수 있습니다. 만약 여러분이 노드와 관련된 새로운 행동을 도입하는 것을 발견한다면, 여러분이 해야 할 일은 새로운 방문자 클래스를 구현하는 것입니다.

 

Real-World Analogy

Imagine a seasoned insurance agent who’s eager to get new customers. He can visit every building in a neighborhood, trying to sell insurance to everyone he meets. Depending on the type of organization that occupies the building, he can offer specialized insurance policies:

If it’s a residential building, he sells medical insurance.
If it’s a bank, he sells theft insurance.
If it’s a coffee shop, he sells fire and flood insurance.

 

 

 

 

Structure

 

1. The Visitor interface declares a set of visiting methods that can take concrete elements of an object structure as arguments. These methods may have the same names if the program is written in a language that supports overloading, but the type of their parameters must be different.

방문자 인터페이스는 객체 구조의 구체적인 요소를 인수로 취할 수 있는 a set of visiting methods을 선언합니다. 프로그램이 오버로딩을 지원하는 언어로 작성된 경우 이러한 메서드는 동일한 이름을 가질 수 있지만 매개 변수의 유형은 서로 달라야 합니다.

 

2. Each Concrete Visitor implements several versions of the same behaviors, tailored for different concrete element classes.

각 방문자는 여러 버전의 동일한 동작을 구현하며, 다른 콘크리트 요소 등급에 맞게 조정된다.

 

3. The Element interface declares a method for “accepting” visitors. This method should have one parameter declared with the type of the visitor interface.

Element Interface“accepting” visitors 메서드를 선언한다. 이 메서드 매개 변수는 방문자 인터페이스의 형식으로 선언했어야 했다.

 

4. Each Concrete Element must implement the acceptance method. The purpose of this method is to redirect the call to the proper visitor’s method corresponding to the current element class. Be aware that even if a base element class implements this method, all subclasses must still override this method in their own classes and call the appropriate method on the visitor object.

concrete elementacceptance method을 구현해야 한다. 이 메서드의 목적은 호출을 현재 요소 클래스에 해당하는 적절한 visitor's method로 리디렉션하는 것입니다. base element class가 이 메서드를 구현하더라도 모든 하위 클래스는 여전히 자체 클래스에서 이 메서드를 재정의하고 visitor obejct에서 적절한 메서드를 호출해야 합니다.

 

5. The Client usually represents a collection or some other complex object. Usually, clients aren’t aware of all the concrete element classes because they work with objects from that collection via some abstract interface.

클라이언트는 일반적으로 컬렉션 또는 다른 복잡한 개체를 나타냅니다. 일반적으로 클라이언트는 추상 인터페이스를 통해 해당 컬렉션의 개체로 작업하기 때문에 모든 구체적인 요소 클래스를 인식하지 못합니다.

 

 

장점

Open/Closed Principle. You can introduce a new behavior that can work with objects of different classes without changing these classes.

개방/폐쇄 원칙. 클래스를 변경하지 않고 서로 다른 클래스의 개체로 작업할 수 있는 새 동작을 도입할 수 있습니다.

 Single Responsibility Principle. You can move multiple versions of the same behavior into the same class.
 단일 책임 원칙. 동일한 동작의 여러 버전을 동일한 클래스로 이동할 수 있습니다.


 A visitor object can accumulate some useful information while working with various objects. This might be handy when you want to traverse some complex object structure, such as an object tree, and apply the visitor to each object of this structure.

A Visitor object는 다양한 객체로 작업하는 동안 몇 가지 유용한 정보를 축적할 수 있다. 개체 트리와 같은 일부 복잡한 개체 구조를 건너뛸 때 이 구조의 각 개체에 방문자를 적용하는 것이 유용할 수 있습니다.

단점

You need to update all visitors each time a class gets added to or removed from the element hierarchy.

요소 계층에 클래스를 추가하거나 요소 계층에서 제거할 때마다 모든 visitors를 업데이트해야 합니다.

 

Visitors might lack the necessary access to the private fields and methods of the elements that they’re supposed to work with.

Visitors은 그들이 함께 작업해야 하는 요소들의 개인 필드와 방법들에 대한 필요한 접근이 부족할 수 있다.

 

 

 

Visitor Pattern vs Chain of Responsibility

<1>

In Chain of Responsibility, it is each object's responsibility to send the call on to the next object in the chain, if the object cannot handle it.

Chain of Responsibility에서 객체가 콜을 처리할 수 없는 경우, 콜을 체인의 다음 객체에 보냅니다.

 

<2>

All objects in the visitor pattern have the same interface, but some outside force has to supply which one is used.

방문자 패턴의 모든 객체는 같은 인터페이스를 가지고 있지만, 어떤 외부 힘은 어떤 물체는 어떤 물체는 어떤 물체가 사용되는지 공급해야 한다.

 

<3>

The chain pattern separates the responsibility of sending a request from the handling of the request. there could be a number of classes that can handle the same type of request (these classes generally implement the same interface) but the pattern allows the request to be passed along from one class (in the chain) to the other until a handler who is most fit to handle the request gets it and is responsible for handling the request (or until a null handler gets it and indicates the end of the chain). If you allow the wrong handler to handle the request, the result may "NEVER" be correct.

체인 패턴은 요청을 보내는 책임과 요청을 처리하는 책임을 구분합니다. 동일한 유형의 요청을 처리할 수 있는 다수의 클래스가 있을 수 있지만(이 클래스는 일반적으로 동일한 인터페이스를 구현함) 패턴은 요청을 처리하기에 가장 적합한 핸들러가 요청을 받고 요청을 처리할 때까지 요청을 한 클래스(체인에서) 다른 클래스로 전달하도록 허용합니다(또는 null이 될 때까지).l 핸들러가 그것을 가져와서 체인의 끝을 나타낸다.) 잘못된 처리기가 요청을 처리하도록 허용할 경우 결과가 "NEVER"일 수 있습니다.

 

<4>

The visitor pattern is about method of processing or algorithm selection. take example of a case where you want to calculate the average of some samples. Any algorithm may "ALWAYS" be correct in the given context (e.g all classes having a strategy does same thing: calculates the average ), but the way the average is calculated, or the visitor for calculating the average differs from one class to the other, and the visitor pattern allows you to select which concrete visitor to use in a decoupled manner.

Now compare this to the chain pattern , where there can be a request for calculating average in which there is one handler that is responsible for computing average and there could be another request to calculate the standard deviation in which there is another handler that is responsible for computing standard deviation. So, the request to compute average will not in any situation be handled by any other handler other than the handler most fit. where as, any class in the concrete visitor may calculate the average and if you don't like the way one class calculates average, you can "SWAP" one strategy for the other.

방문자 패턴은 처리 방법 또는 알고리즘 선택입니다. 일부 표본의 평균을 계산하려는 경우를 예로 들 수 있습니다. 어떤 알고리즘도 주어진 맥락에서 "항상" 정확할 수 있지만(예를 들어 전략을 가진 모든 클래스는 같은 일을 한다: 평균을 계산한다). 평균 계산 방법이나 평균 계산 방문자는 클래스마다 다르다. 방문자 패턴은 분리된 남성에서 사용할 구체적인 방문자를 선택할 수 있게 해준다.너그럽게 굴다

이제 이것을 체인 패턴과 비교해 보십시오. 평균 계산을 담당하는 핸들러가 한 개인 경우 평균 계산 요청이 있을 수 있고 표준 편차를 계산하는 다른 핸들러가 있을 경우 표준 편차를 계산하기 위한 요청이 있을 수 있습니다. 따라서 평균 계산 요청은 가장 적합한 처리기가 아닌 다른 처리기에서 처리되지 않습니다. 여기서, 콘크리트 방문자의 모든 등급은 평균을 계산할 수 있고, 만약 당신이 한 등급의 평균 계산 방식이 마음에 들지 않는다면, 당신은 다른 등급에 대한 하나의 전략을 "SWAP"할 수 있습니다.

반응형

댓글