본문 바로가기
Design Pattern

22. 비지터 패턴 (visitor pattern)

by 킹차니 2022. 1. 2.

비지터 패턴

기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법

--> 더블 디스패치(Double Dispatch)를 활용할 수 있다.

특정 클래스에 SRP를 지키고 싶어 더는 책임을 늘리고 싶지 않다거나, 버그가 발생할 수 있으니 더 이상 코드를 작성하고 싶지 않을 때, 또는 해당 코드는 다른 곳에도 사용될 수 있을 거 같은데? 라는 생각을 한다면 해당 패턴을 적용할 수 있다.

 


 

코드로 알아보기

 

특정 디바이스에 도형을 그려야한다고 해보자. 디바이스 클래스를 각 구체적인 디바이스(phone, watch)들이 상속하고, Shape 인터페이스를 도형들이 implements한다.

Device 클래스와 그것을 구현한 각 디바이스들은 모두 비어있는 클래스다.

 

 

Shape 인터페이스

각 도형들은 printTo의 인자로 받는 Device의 종류에 따라 다른 방법으로 도형을 그린다고 가정한다.

 

Client는 아래와 같다.

실행결과:

 

 

 

❗️문제점❗️

하지만 만약 다른 디바이스가 추가된다면 기존의 모든 코드(Circle, Triangle, Rectangle)에 if문을 추가해주어야 한다.

즉 OCP를 위반하고 있다. 또한 과연 Shape 구현체들이 직접 도형을 그려주는 것이 맞나?라는 의문점도 생긴다. 비지터 패턴을 적용하여 이를 해결해보자.

(참고로 위의 예는 if문 없이도 가능하지만 최대한 간단하고 직관적인 이해를 위해 if문으로 분기해야만 한다고 하자.)

 

 

먼저 Shape의 그림을 그리는 로직을 모두 device로 옮길 것이다. Shape에는 accept 메서드 하나만 만들어준다.

이제 각 Shape 구현체들은 Device로 옮겨진 print메서드를 호출한다.

이때 구체 클래스 타입이 아닌, 인터페이스 Device 타입으로 들어온 device의 print메서드를 실행하기 위해 디스패치가 발생한다.

 

이제 Device를 보자.

Device는 각 도형들을 그리는 모든 메서드들을 가지고 있다. (오버로딩 메소드들)

 

 

이제 이를 실행하는 클라이언트를 보자.

클라이언트 코드를 실행하면 총 2번의 디스패치가 발생한다.

1. Shape rectangle의 타입이 Shape이므로 어떤 구체 클래스의 accept메서드를 실행해야 할지 디스패치 발생

2. 위에서 언급했듯이 accept메소드 안에서 Device 타입으로 들어온 device의 print메서드를 실행하기 위해 디스패치 발생

 

실행결과:

 

또한 이제 새로운 Devive가 추가되더라도 기존의 코드를 변경하지 않고, 추가할 수 있다. IPad를 추가해보자.

새로운 Device인 IPad를 추가하였는데 기존의 코드는 전혀 변경하지 않았다.

 


장점과 단점

 

장점 

1. 기존 코드를 수정하지 않고 새로운 코드를 추가할 수 있다.

2. 추가 기능을 한 곳애 모아둘 수 있다.

 

단점

1. 복잡하다.

2. 새로운 Element를 추가하거나 제거할 때 모든 Visitor 코드를 수정해야 한다.

 


실제로 어디에 사용되나?

 

1. 자바의 File Visitor, SimpleFile Visitor, AnnotationValue Visitor, ElementVisitor

2. 스프링의 BeanDefinitionVisitor

 

 

출처: 인프런 백기선님 '코딩으로 학습하는 GoF 강의'
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4