Visitor(방문자) 패턴

이원찬·2024년 6월 3일

디자인 패턴

목록 보기
4/4

알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴

💡 Visitor패턴은 객체 구조를 변경하지 않고도 새로운 기능을 추가

문제 상황

각 노드는 산업, 관광 지역들 이다.

각 노드에는 도로가 깔려있으면 이어진다.

여기서 각 노드,연결 정보를 xml로 내보내야 하는 상황이 왔다!

각 노드 클래스에 export(내보내기) 메서드를 추가한다음 재귀호출을 하면 될것 같았다.

class Node{
	pubilc void export(){
		if (...) return;
		this.nextNode().export();
	}
}

하지만 시스템 설계자는 Node 클래스가 변경되는것을 원치 않았다..

  • Node 클래스가 변경되는 것은 어디서 생길지 모르는 오류를 감수해야하기도 하고
  • 각 Node 에 내보내기 메서드가 추가되는것이 맞는지 의문이기도 하다.

해결책

새로운 행동을 기존 클래스에 통합하는 것이 아닌 visitor 라는 별도의 클래스에 배치 해보자

일단 visitor 클래스를 만들어 보자

class ExportVisitor implements Visitor {
    method doForCity(City c) { "city는 이렇게 하자~" }
    method doForIndustry(Industry f) { "산업단지는 이렇게 하자~" }
    method doForSightSeeing(SightSeeing ss) { "관광지는 이렇게 하자~" }
    // …
}

도시, 산업단지, 관광지 등 각 노드에 맞는 내보내기 메서드를 구현한 ExportVisitor이다.

각 노드를 가지고있는 graph 변수에 대해 export를 진행한다면 이렇게 가능할것 같다.

Visitor exportVisitor = new ExportVisitor();
for(Node node : graph){
	if (node instanceof City) exportVisitor.doForCity(node);
	if (node instanceof Industry) exportVisitor.doForIndustry(node);
	if (node instanceof SightSeeing) exportVisitor.doForSightSeeing(node);
	//...
}

하지만 이건 너무 비효율 적이다.

비지터가 호출할 메서드를 고르는게 아닌 각 클래스가 비지터를 호출하면 될것 같다.

class City extends Node {
	...
	public void accept(Visitor visitor){
		visitor.doForCity(this);
	}
}

다른 클래스들도 visitor을 받아 자신(this)를 넣어 visitor 메서드를 호출하는 메서드를 구현한다.

그렇다면 위 foreach 문은 이렇게 바뀐다.

Visitor exportVisitor = new ExportVisitor();
for(Node node : graph){
	node.accept(exportVisitor);
}

의문점

우리는 각 Node 클래스를 바꾸고 싶지 않아서 Visitor 패턴을 사용했지만 accept 라는 메서드가 추가됐다.

위 의문점에서 많은게 잘못 되었다고 생각했다.

하지만 더욱 찾아본 결과 Visitor의 진정한 의미에 대해 깨닫게 되고 의문점은 해소 되었다.

해결

아이템 객체인 Book, Pen 이 있다고 해보자

public class Book implements Item {
    private int price;
    @Override
    public void accept(Visitor visitor) {
        visitor.doForBook(this);
    }

    public int getPrice() {};
		public void setPrice(int price) {};
}

public class Pen implements Item {
    private int price;

    @Override
    public void accept(Visitor visitor) {
        visitor.doForPen(this);
    }

    public int getPrice() {};
    public void setPrice(int price) {};
}

이때 Visitor는 알고리즘을 포함하는 여러개가 나올수 있다.

전체가격을 조회하는 PriceCalculator

public class PriceCalculator implements Visitor {

    @Override
    public void doForBook(Book book) {
        System.out.println("book price : " + book.getPrice());
    }

    @Override
    public void doForPen(Pen pen) {
        System.out.println("pen price : " + pen.getPrice());
    }
}

세금이 붙은 가격을 조회하는 TaxCalculator

public class TaxCalculator implements Visitor {
    private float tax = 1.2f;

    @Override
    public void doForBook(Book book) {
        System.out.println("Book tax : " + book.getPrice() * tax);
    }

    @Override
    public void doForPen(Pen pen) {
        System.out.println("Pen tax : " + pen.getPrice() * tax);
    }
}

이렇게 다양한 알고리즘이 생겨도 아이템 클래스는 수정되지않는다

각 가격을 조회하고 싶다면 직접 visitor을 넘기면 된다.

PriceCalculator priceCalculatorVisitor = new PriceCalculator();
TaxCalculator taxCalculatorVisitor = new TaxCalculator();

ArrayList<Item> itemBox = new ArrayList<Item>();

Book book = new Book();
book.setPrice(1000);
itemBox.add(book);

Pen pen = new Pen();
pen.setPrice(500);
itemBox.add(pen);

for (Item i : itemBox) {
    i.accept(priceCalculatorVisitor);
}

for (Item i : itemBox) {
    i.accept(taxCalculatorVisitor);
}

참고자료

비지터 패턴

profile
소통과 기록이 무기(Weapon)인 개발자

0개의 댓글