본문 바로가기

디자인 패턴

디자인 패턴 - 비지터 패턴(Visitor Pattern)

1. 정의

  • 다양한 객체에 새로운 기능을 추가한다. 단, 캡슐화가 깨지게 된다.

2. 예시

  • 메뉴 복합 객체를 탐색하면서 깊이(Depth) 출력 기능추가한다.

💡 컴포지트 패턴 참고

3. 그림

4. 클래스 다이어그램

5. 코드

public class Client {
    public static void main(String[] args){
        Visitor visitor = new Visitor();

        MenuComponent breakfast = new Menu("아침 메뉴", "08:00~11:00", visitor);
        MenuComponent lunch = new Menu("점심 메뉴", "12:00~17:00", visitor);
        MenuComponent dinner = new Menu("저녁 메뉴", "18:00~23:00", visitor);

        MenuComponent all = new Menu("전체 메뉴", "00:00~23:59", visitor);

        all.add(breakfast);
        all.add(lunch);
        all.add(dinner);

        // 메인
        breakfast.add(new MenuItem("시리얼", "요거트", 4_000, visitor));
        breakfast.add(new MenuItem("식빵", "딸기쨈", 3_000, visitor));

        lunch.add(new MenuItem("팬케이크", "메이플 시럽", 6_000, visitor));

        dinner.add(new MenuItem("불고기", "국내산", 15_000, visitor));
        dinner.add(new MenuItem("비빔밥", "국내산", 10_000, visitor));

        // 디저트
        MenuComponent lunchDessert = new Menu("점심 디저트", "12:00~17:00", visitor);
        lunchDessert.add(new MenuItem("커피", "콜롬비아", 1_500, visitor));
        lunch.add(lunchDessert);

        MenuComponent dinnerDessert = new Menu("저녁 디저트", "18:00~23:00", visitor);
        dinnerDessert.add(new MenuItem("포도", "", 1_000, visitor));
        dinnerDessert.add(new MenuItem("딸기", "", 1_000, visitor));
        dinner.add(dinnerDessert);

        all.print();
    }
}
/* 출력
------ depth : 0 ------
[전체 메뉴](00:00~23:59)
------ depth : 1 ------
[아침 메뉴](08:00~11:00)
시리얼(요거트) : 4000
식빵(딸기쨈) : 3000
------ depth : 2 ------
[점심 메뉴](12:00~17:00)
팬케이크(메이플 시럽) : 6000
------ depth : 3 ------
[점심 디저트](12:00~17:00)
커피(콜롬비아) : 1500
------ depth : 4 ------
[저녁 메뉴](18:00~23:00)
불고기(국내산) : 15000
비빔밥(국내산) : 10000
------ depth : 5 ------
[저녁 디저트](18:00~23:00)
포도() : 1000
딸기() : 1000
*/
public class Visitor {
    private int depth = 0;

    public void getDepth(){
        System.out.println("------ depth : " + (depth++) + " ------");
    }
}
public abstract class MenuComponent {

    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    public void remove(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    public MenuComponent getChild(int i){
        throw new UnsupportedOperationException();
    }

    public String getName(){
        throw new UnsupportedOperationException();
    }

    public String getDescription(){
        throw new UnsupportedOperationException();
    }

    public int getPrice(){
        throw new UnsupportedOperationException();
    }

    public void print(){
        throw new UnsupportedOperationException();
    }
}
// Leaf Node
public class MenuItem extends MenuComponent {
    String name, description;
    int price;
    Visitor visitor;

    public MenuItem(String name, String description, int price, Visitor visitor) {
        this.name = name;
        this.description = description;
        this.price = price;
        this.visitor = visitor;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public int getPrice() {
        return price;
    }

    @Override
    public void print() {
        System.out.println(name + "("+ description +")" + " : " +price);
    }
}
import java.util.ArrayList;
import java.util.List;

// Node
public class Menu extends MenuComponent {
    List<MenuComponent> component = new ArrayList<>();
    String name;
    String description;
    Visitor visitor;

    public Menu(String name, String description, Visitor visitor) {
        this.name = name;
        this.description = description;
        this.visitor = visitor;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        component.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        component.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int i) {
        return component.get(i);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public void print() {
        visitor.getDepth(); // vistor의 depth()사용
        System.out.println("["+name+"]" + "("+ description +")");

        for (MenuComponent menuComponent : component) {
            menuComponent.print();
        }
    }
}

https://github.com/kang-seongbeom/design_pattern

💡 Github에서 디자인 패턴 코드를 볼 수 있습니다.

6. 설명

  • 기존의 메뉴 복합 객체에서 Visitor만 추가해 준 것 이외에 바뀐것이 없다.
  • 새로운 기능을 추가하고 싶으면, 구조 변경 없이 Visitor 내부에 추가해 사용하면 된다.
  • 단, 로직이 메뉴와 비지터로 분산되서 저장되기 때문에 캡슐화가 깨진다.

💡 비지터 패턴은 거의 필요하지 않지만, 한 번 필요해지면 비지터 패턴 밖에 답이 없다고 한다. (아마 계층 구조가 너무 복잡해서 새로운 기능을 추가하기 위해 비지터 패턴을 사용하는게 아닐까 싶다.)