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

Visitor Pattern Double Dispatch (방문자 패턴 더블 디스패치란)

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

Visitor Pattern Double Dispatch (방문자 패턴 더블 디스패치란)

 

여러분이 생명체를 발견하기 위해 태양계의 행성들을 정기적으로 여행하는 행성 탐험가를 디자인하고 있다고 가정해 보자. 그러나 탐사 방법은 대기와 표면 구성의 차이로 인해 행성마다 다르다.

 

단순성을 위해 3개의 행성을 모형화해 봅시다.

1
2
3
4
5
6
7
public interface Planet { }
 
public class Mercury implements Planet { }
 
public class Mars implements Planet { }
 
public class Saturn implements Planet { }
cs

그런 다음, 우리는 각 행성에 탐사 방법을 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface Explorer {
    void visit(Mercury mercury);
    void visit(Mars mars);
    void visit(Saturn saturn);
    
    default void visit(Planet planet) {
        System.out.println("Landed on an unknown planet...");
    }
}
 
public class LifeExplorer implements Explorer {
    @Override
    public void visit(Mercury mercury) {
        System.out.println("Deploying tools specific to Mercury... exploring life");
    }
 
    @Override
    public void visit(Mars mars) {
        System.out.println("Deploying tools specific to Mars... exploring life");
    }
 
    @Override
    public void visit(Saturn saturn) {
        System.out.println("Deploying tools specific to Saturn... exploring life");
    }
    
    @Override
    public void visit(Planet planet) {
        System.out.println("Cannot explore life on an unknown planet...");
    }
}
cs

자, 이제 우리 탐험가를 행성에 배치해 봅시다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Planet mars = new Mars()
Planet saturn = new Saturn();
Planet mercury = new Mercury()
 
Explorer explorer = new LifeExplorer();
 
List<Planet> planetsToBeVisited = new ArrayList<>();
planetsToBeVisited.add(mars);
planetsToBeVisited.add(saturn);
planetsToBeVisited.add(mercury);
 
for (Planet planet : planetsToBeVisited) {
    explorer.visit(planet);
}
cs

결과

1
2
3
Cannot explore life on an unknown planet...
Cannot explore life on an unknown planet...
Cannot explore life on an unknown planet...
cs

Oops, seems like there isn’t enough information for the compiler to deduce the runtime type of the Planet when we called explorer.visit(planet)…

 

Double Dispatch

Languages like Java, Javascript, C++, Python support only Single Dispatch polymorphism — the selection of method to call based on a single object.

Double Dispatch is the selection of method based on the runtime types of two objects — the receiver and the argument.
이중 디스패치는 수신자와 인수라는 두 개체의 런타임 유형을 기반으로 하는 메서드를 선택하는 것입니다.

In the code above, specifically explorer.visit(planet), there are 2 levels of dispatch calls:

  1. Selection of which explorer to call (based on the receiver type)
  2. Selection of which visit(planet) to call (based on the argument type)

We can see that the first dispatch went to LifeExplorer successfully, of course, thanks to (Single Dispatch) Polymorphism that we all know so well.

 

However, the second dispatch call failed, and the compiler falls back to the generic visit(Planet planet) method because it doesn’t know which one.

Simulating Double Dispatch using Visitor Pattern

We can simulate the second level of polymorphism using Visitor pattern.

 

First, implement accept in the Planets, the purpose of accept is to give the Explorer more information about which Planet to visit via this.

 

accept의 목적은 이를 통해 탐험가가 방문할 행성에 대한 더 많은 정보를 제공하는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Planet {
    void accept(Explorer explorer);
}
 
public class Mercury implements Planet {
    @Override
    public void accept(Explorer explorer) {
        explorer.visit(this);
    }
}
 
public class Mars implements Planet {
    @Override
    public void accept(Explorer explorer) {
        explorer.visit(this);
    }
}
 
public class Saturn implements Planet {
    @Override
    public void accept(Explorer explorer) {
        explorer.visit(this);
    }
}
cs

 

Then, we will use planet.accept(explorer) to actually call explorer.visit(planet). Notice the extra layer of indirection, it is there to explicitly tell Explorer which Planet to visit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Planet mars = new Mars()
Planet saturn = new Saturn();
Planet mercury = new Mercury()
 
Explorer explorer = new LifeExplorer();
 
List<Planet> planetsToBeVisited = new ArrayList<>();
planetsToBeVisited.add(mars);
planetsToBeVisited.add(saturn);
planetsToBeVisited.add(mercury);
 
for (Planet planet : planetsToBeVisited) {
    planet.accept(explorer);
}
cs

결과

1
2
3
Deploying tools specific to Mars... exploring life
Deploying tools specific to Saturn... exploring life
Deploying tools specific to Mercury... exploring life
cs

This works because we have effectively broken down the initial explorer.visit(planet) into 2 layers. The first planet.accept(explorer) call utilizes Polymorphism to figure out which Planet to call, and the second explorer.visit(this) call do the same to figure out which Explorer to call.

 

 

이것은 우리가 초기 탐험가를 효과적으로 2층으로 분해했기 때문에 효과가 있다. 첫 번째 explorer.accept(planet) 콜은 폴리모피즘을 활용하여 어떤 플래닛을 호출할지, 두 번째 explorer.visit(this) 콜은 어떤 탐색기를 호출할지 알아낸다.

 

 

When to use Visitor Pattern?

  1. When the elements are known in advance, but the operation can be extended. (e.g . planets are known, whereas explorers are extendable).
  2. When the type of the element and operation are not known during runtime, but the correct method needs to be invoked (e.g the type of both planet and explorer are not known during runtime).
  3. elements를 미리 알 수 있지만 작업을 확장할 수 있습니다. (예: 행성은 알려져 있지만 탐험가는 확장 가능합니다.)
  4. type of element이 런타임 동안 알려져 있지 않지만 올바른 메서드가 호출되어야 할 때(예: planet과 explorer의 유형은 런타임 동안 알려져 있지 않음)
반응형

댓글