S E P H ' S

[행동 패턴] 방문자(Visitor) 패턴 본문

Programing & Coding/Design Pattern

[행동 패턴] 방문자(Visitor) 패턴

yoseph0310 2023. 3. 19. 18:51

방문자(Visitor) 패턴

방문자 패턴이란?

방문자와 방문 공간을 분리하여, 방문 공간이 방문자가 맞이할 때, 이후에 대한 행동을 방문자에게 위임하는 패턴

알고리즘을 객체구조에서 분리시키는 디자인 패턴이다. OCP(계방-폐쇄) 원칙을 적용하는 방법 중 하나이며

구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다.

 

언제 사용할까?

 

  • 자료 구조(데이터)와 자료 구조를 처리하는 로직을 분리해야 할 때
  • 데이터 구조보다 로직이 더 자주 변경될 때

 

구조

 

  • Visitor : 데이터 구조 내 각각의 구체적 요소에 visit 메소드를 선언
  • ConcreteVisitor : Visitor 인터페이스를 구현하고, ConcreteAdapter 역할을 처리
  • Acceptor : Visitor 역할이 방문할 곳을 나타내는 역할. 방문자를 받아들이는 accept메소드를 선언
  • ConcreteAcceptor : Acceptor 인터페이스를 구현
  • ObjectStructure : Acceptor 역할의 집합을 취급하는 역할

 

장단점

 

장점

 

  • 작업 대상과 작업 항목을 분리시킬 수 있다.
  • 데이터와 알고리즘이 분리되어 데이터의 독립성을 높인다.
  • 작업 대상의 입장에서는 accept()로 인터페이스를 통일시키기 때문에 사용자에게 동일한 인터페이스를 제공할 수 있다.

 

단점

  • 새로운 작업대상이 추가될 때마다 작업 주체도 이에 대한 로직을 추가해야 한다.
  • 두 객체의 결합도가 높아진다.

 

예시

 

하나의 웹을 만든다고 가정해보자. 이 웹은 Desktop, Pad, Mobile에서 모두 다른 스타일로 보여진다. 같은 화면을 만들어놓고 접근한 기기에 따라서 다른 화면을 보여주는 것이다. 데이터보다는 보여지는 작동에 대해 교체가 빈번한 예시이다.

 

먼저 Visitor Interface인 Device Interface와 ConcreteVisitor인 Desktop, Pad, Mobile을 작성한다.

 

public interface Device {
    void render(HomeView homeView);
    void render(LoginView loginView);
}

 

public class Desktop implements Device {
    @Override
    public void render(HomeView homeView) {
        System.out.println("HomeView rendered in Desktop");
    }

    @Override
    public void render(LoginView loginView) {
        System.out.println("LoginView rendered in Desktop");
    }
}

 

public class Tab implements Device {
    @Override
    public void render(HomeView homeView) {
        System.out.println("HomeView rendered in Tab");
    }

    @Override
    public void render(LoginView loginView) {
        System.out.println("LoginView rendered in Tab");
    }
}

 

public class Mobile implements Device {
    @Override
    public void render(HomeView homeView) {
        System.out.println("HomeView rendered in Mobile");
    }

    @Override
    public void render(LoginView loginView) {
        System.out.println("LoginView rendered in Mobile");
    }
}

 

그리고 Acceptor 역할인 View Interface와 ConcreteView역할을 하는 View들을 만든다.

 

public interface View {
    public void accept(Device device);
}

 

public class HomeView implements View {

    @Override
    public void accept(Device device) {
        device.render(this);
    }
}



public class LoginView implements View {

    @Override
    public void accept(Device device) {
        device.render(this);
    }
}

 

 

테스트

 

이제 클라이언트에서 테스트로 확인해보자.

 

public class Test {
    public static void main(String[] args) {
        View homeView = new HomeView();

        Device desktop = new Desktop();
        Device tab = new Tab();
        Device mobile = new Mobile();

        homeView.accept(desktop);
        homeView.accept(tab);
        homeView.accept(mobile);
    }
}

 

 

새로운 Device가 생성되어도 View에서는 코드 수정이 필요하지 않다.

새로운 View가 생성되면 Device에서 새로운 View에 대한 render() 구현의 책임을 진다.

 

이렇게 Device에 대한 View 의 의존성을 제거하고 기존 코드를 변경하지 않게 되었다.

 

 

Double Dispatch

런타임에 구체적인 타입을 찾아가는 것을 dispatch라고 한다.

위의 테스트 코드 homeView.accept(device) 에서
런타임에 어떤 타입의 Device를 accept() 하는지 찾을 때 1번,
어떤 타입의 View를 render() 하는지 찾을 때 1번

총 2번 발생하여 double dispatch라고 한다.

 


예시 출처 : https://developer-ping9.tistory.com/427