본문 바로가기
Design Pattern/행동 패턴(Behavioral Patterns)

방문자(Visitor) - 행동 패턴(Behavioral Patterns)

by 김 민 준 2024. 5. 25.

 

방문자 패턴은 개체의 구조와 개체가 수행하는 작업을 분리하는 디자인 패턴이다. 개체의 구조는 변경하지 않고, 새로운 작업을 개체에 추가할 수 있다. 방문자 패턴은 주로 개체 구조가 자주 변경되지만, 수행해야할 작업이 자주 변경된는 경우에 유용하다. 

 

주요 개념 부터 살펴보자.

 

1. Visitor(방문자) : 개체 구조를 방문하여 수행할 작업을 정의하는 인터페이스이다.

2. ConcreteVisitor(구체적인 방문자) : Visitor 인터페이스를 구현하며, 각 개체에 대해 수행할 작업을 정의한다.

3. Element(요소) : Visitor를 받아들이는 인터페이스이다. 이 인터페이스는 accept 메서드를 정의한다.

4. ConcreteElement(구체적인 요소) : Element 인터페이스를 구현하며 accept 메서드에서 Visitor를 받아들인다.

5. ObjectStructure(개체 구조) : 개체 구조를 관리하면, Visitor 를 받아들여 각 요소를 방문하도록 한다.

 

예시 소스 코드로 확인해보자.

 

쇼핑몰에서 할인을 적용하는 로직을 구현해본다.

 

interface DiscountVisitor {
    void visit(Book book);
    void visit(Electronics electronics);
}

 

 

class DiscountCalculator implements DiscountVisitor {
    private double totalDiscount = 0.0;

    @Override
    public void visit(Book book) {
        double discount = book.getPrice() * 0.10; // 책은 10% 할인
        totalDiscount += discount;
        System.out.println("Book Discount: " + discount);
    }

    @Override
    public void visit(Electronics electronics) {
        double discount = electronics.getPrice() * 0.20; // 전자 제품은 20% 할인
        totalDiscount += discount;
        System.out.println("Electronics Discount: " + discount);
    }

    public double getTotalDiscount() {
        return totalDiscount;
    }
}

 

interface Item {
    void accept(DiscountVisitor visitor);
}

 

class Book implements Item {
    private double price;

    public Book(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(DiscountVisitor visitor) {
        visitor.visit(this);
    }
}

class Electronics implements Item {
    private double price;

    public Electronics(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(DiscountVisitor visitor) {
        visitor.visit(this);
    }
}

 

import java.util.ArrayList;
import java.util.List;

class ShoppingCart {
    private List<Item> items;

    public ShoppingCart() {
        items = new ArrayList<>();
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public void applyDiscounts(DiscountVisitor visitor) {
        for (Item item : items) {
            item.accept(visitor);
        }
    }
}

 

public class VisitorPatternShoppingDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.addItem(new Book(100));
        cart.addItem(new Electronics(200));

        DiscountCalculator discountCalculator = new DiscountCalculator();
        cart.applyDiscounts(discountCalculator);

        System.out.println("Total Discount: " + discountCalculator.getTotalDiscount());
    }
}

 

// 출력

//Book Discount: 10.0
//Electronics Discount: 40.0
//Total Discount: 50.0

 

각 상품에 대해 다른 할인율을 적용하고 총 할인 금액을 계산하는 작업을 Visitor 개체에 위임했다. 이렇게 하면 상품의 구조는 변경하지 않고도 할인 계산 로직을 Visitor 개체에 추가하거나 변경할 수 있다. 

 

방문자 패턴은 연선을 새로 추가하고 개체 구조와 연산을 분리함으로써 독립적으로 관리할 수 있다는 장점이 있지만 개체 구조가 자주 변경되면 비효율 적일 수 있다. 또한 방문자가 개체의 내부 상태에 접근해야해서 개체의 캡슐화가 위반될 수 있다.