Computer Basis/디자인패턴 및 설계이론
[디자인 패턴] 방문자 패턴(Visitor Pattern)
DevPing9_
2022. 6. 4. 16:45
# 방문자 패턴 (Vistor Pattern)
SOP 달성, 버그 가능성, 코드 응집성 등의 이유로
기존코드를 건드리지 않고 새로운 기능을 추가하는 방법을 제안하는 패턴
# 방문자 패턴을 적용하기 전
아래와 같이 동일한 웹 페이지가 기기별로 다르게 렌더링 되어야 한다는 요청사항이 생겼다고 하자.

요청사항에 따라,
본래는 view 패키지의 View 의 집합들 뿐이었으나
이제 device 패키지가 추가되었다.
방문자 패턴을 적용하지 않는다면 아래와 같이 코딩을 할 수도 있다.
//View Interface
public interface View {
// render() -> render(Device device) 로 변경
void render(Device device);
}
//HomeView Class
public class HomeView implements View{
// 인터페이스 변경으로 인한 파라미터 Device 로 인한 코드변경
@Override
public void render(Device device)
// new Logic Created
if(device instanceof Watch) System.out.println("HomePage Rendered in Watch");
else if (device instanceof Pad) System.out.println("HomePage Rendered in Pad");
else if(device instanceof Desktop) System.out.println("HomePage Rendered in Desktop");
/* some shared logics here */
}
}
//LoginView Class
public class LoginView implements View{
// 인터페이스 변경으로 인한 파라미터 Device 로 인한 코드변경
@Override
public void render(Device device) {
// new Logic Created
if(device instanceof Watch) System.out.println("LoginPage Rendered in Watch");
else if (device instanceof Pad) System.out.println("LoginPage Rendered in Pad");
else if(device instanceof Desktop) System.out.println("LoginPage Rendered in Desktop");
/* some shared logics here */
}
}
Interface -> class 까지 변경이 전파되며, 다소 반복적인 코드가 상속받은 클래스들에게 구현이 강제된다.
새로운 Device 들이 정의 될 때마다 모든 View 클래스들의 수정이 일어난다.
새로운 View 들이 정의 될 때마다 모든 Device 에 대한 렌더링(render())을 추가적으로 오버라이딩 해야한다.
덕분에 OCP 도 깨졌고,
`화면을 렌더링하는 render() 가 View 클래스들의 역할인가?` 라고 생각이 들면,
SOP 또한 깨졌다고 볼 수 있다.
Client-Side
public class Client {
public static void main(String[] args) {
HomeView homeView = new HomeView();
Device device = new Pad();
homeView.render(device);
}
}
# 방문자 패턴의 적용
Double Dispatch 를 사용한다.
아래에서 예시코드를 들어 설명하겠다.
Visitor 인터페이스를 정의 후, 상속받은 Vistor 에서 메소드 오버로딩으로 구현하며
Vistor 를 사용할 쪽에서 accept(Visotor) 메소드를 구현한다.

방문자 패턴이 적용된 코드

Visitor Interface 는 Device Interface 이며,
ConcreteVisitor 는 Desktop, Pad, Watch 이다.
// Visitor Interface
public interface Device {
void render(LoginView loginView);
void render(HomeView homeView);
}
// Concreate Visitor
public class Desktop implements Device {
@Override
public void render(LoginView loginView) {
System.out.println("LoginView rendered in Desktop");
}
@Override
public void render(HomeView homeView) {
System.out.println("HomeView rendered in Desktop");
}
}
// Element Interface
public interface View {
void accept(Device device);
}
// Concreate Element
public class LoginView implements View {
@Override
public void accept(Device device) {
device.render(this);
}
}
새로운 Device 가 생성되어도 View 에선 코드 수정이 일어나지 않는다.
새로운 View 가 생성되면 Device 에서 새로운 View 에 대한 render() 의 구현의 책임을 진다.
SOP 가 깔끔하게 떨어지지 않았는가!!
이전에는 새로운 Device 가 추가되면 모든 View 가 갯수만큼 코드 수정이 일어났다.
물론 새로운 View 가 추가되면 모든 Device 에서 코드 수정이 일어나야 하지만
이 또한 다른 패턴을 더 공부하면 해결할 수 있으리라고 믿어본다... 🤔
일단 Device 에 대한 View 의 의존성은 사라졌다.
그리고 기존 코드(View)를 변경하지 않겠다 라는 목적을 달성시켰다.
Client-Side
public class Client {
public static void main(String[] args) {
HomeView homeView = new HomeView();
Device device = new Desktop();
homeView.accept(device);
}
}
Double dispatch
런타임에 구체적인 타입을 찾아가는 것을 dispatch 라고 한다.
위의 클라이언트 코드homeView.accept(device)에서
런타임에 어떤 타입의 Device 를 accept() 하는지 찾는 dispatch 가 1번,
어떤 타입의 View 를 render() 하는지 dispatch 가 1번
총 2번 발생하여 double dispatch 라고 한다.
방문자 패턴의 장점
기존코드의 변경이 거의 없다.
새로운 추가 기능들을 한 곳에 모아 놓을 수 있다.
방문자 패턴의 단점

위의 그림과 같이 Double Dispatch 라는 단어가 주는 흐름파악이 복잡하다.
새로운 Element(예제에서는 View)를 추가/삭제 할 때마다 모든 Visitor(Device)에 대한 수정이 필요하다
# Reference
코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
728x90