[GoF] Visitor 패턴

패턴명칭

Visitor

필요한 상황

데이터와 이 데이터의 처리를 분리하여 구현하고자 할때 사용되는 패턴입니다. 데이터는 Composite 패턴으로 구현되므로 집합을 구성하는 단일 요소 역시 집합으로 저장될 수 있습니다. 이러한 집합에 대한 집합으로 구성된 데이터를 처리하는 로직을 독립적으로 구현할 수 있습니다.

예제 코드

Visitor 인터페이스는 데이터를 처리하는 클래스가 구현해야할 공통 인터페이스입니다. 코드는 다음과 같습니다.

package tstThread;

public interface Visitor {
	void visit(Unit unit);
}

이 Visitor를 구현하는 클래스로는 SumVisitor, MaxVisitor과 위의 클래스 다이어그램에는 표시되어 있지 않지만 MinVisitor, AvgVisitor이 있습니다. 이 네 클래스는 각각 데이터의 총합 계산, 데이터 중 최대값 파악, 데이터 중 최소값 파악, 데이터의 평균값 계산입니다. 데이터는 Unit 인터페이스를 구현해야 하며 코드는 다음과 같습니다.

package tstThread;

public interface Unit {
	void accept(Visitor visitor);
}

이 Unit 인터페이스를 구현하는 Item에는 하나의 정수값이 저장되며 ItemList는 여러개의 Unit 객체를 담을 수 있습니다. 먼저 Item 클래스는 다음과 같습니다.

package tstThread;

public class Item implements Unit {
	private int value;
	
	public Item(int value) {
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
	
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

ItemList 클래스는 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.Iterator;

public class ItemList implements Unit {
	private String name;
	
	private ArrayList<Unit> list = new ArrayList<Unit>();
	
	public ItemList(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void add(Unit unit) {
		list.add(unit);
	}
	
	@Override
	public void accept(Visitor visitor) {
		Iterator<Unit> iter = list.iterator();
		
		while(iter.hasNext()) {
			Unit unit = iter.next();
			visitor.visit(unit);
		}
	}
}

이제 이러한 데이터를 처리하는 Visitor 인터페이스의 구현 클래스를 살펴보겠습니다. 먼저 SumVisitor 클래스입니다.

package tstThread;

public class SumVisitor implements Visitor {
	private int sum = 0;
	
	public int getValue() {
		return sum;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			sum += ((Item)unit).getValue();
		} else {
			unit.accept(this);			
		}
	}
}

다음은 MaxVisitor 클래스입니다.

package tstThread;

public class MaxVisitor implements Visitor {
	private int max = Integer.MIN_VALUE;
	private String name = null;
	private String visitedName = null;
	
	public int getValue() {
		return max;
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			int value = ((Item)unit).getValue();
			if(value > max) {
				max = value;
				name = visitedName;
			}
		} else {
			visitedName = ((ItemList)unit).getName();
			unit.accept(this);			
		}
	}
}

다음은 MinVisitor 클래스입니다.

package tstThread;

public class MinVisitor implements Visitor {
	private int min = Integer.MAX_VALUE;
	private String name = null;
	private String visitedName = null;
	
	public int getValue() {
		return min;
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			int value = ((Item)unit).getValue();
			if(value < min) {
				name = visitedName;
				min = value;
			}
		} else {
			visitedName = ((ItemList)unit).getName();
			unit.accept(this);			
		}
	}
}

다음은 AvgVisitor 클래스입니다.

package tstThread;

public class AvgVisitor implements Visitor {
	private int sum = 0;
	private int count = 0;
	public double getValue() {
		return sum / count;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			sum += ((Item)unit ).getValue();
			count++;
		} else {
			unit.accept(this);			
		}
	}
}

지금까지의 클래스를 사용하는 예제 코드는 다음과 같습니다.

package tstThread;

public class Main {
	public static void main(String[] args) {
		ItemList root = new ItemList("root");
		root.add(new Item(10));
		root.add(new Item(20));
		root.add(new Item(40));
		
		ItemList subList1 = new ItemList("sub1");
		subList1.add(new Item(5));
		subList1.add(new Item(16));
		subList1.add(new Item(36));
		
		ItemList subList2 = new ItemList("sub2");
		subList2.add(new Item(50));
		subList2.add(new Item(70));
		
		ItemList subList3 = new ItemList("sub2-sub");
		subList3.add(new Item(8));
		subList3.add(new Item(21));
		subList3.add(new Item(37));
		
		root.add(subList1);
		root.add(subList2);
		subList2.add(subList3);
		
		SumVisitor sum = new SumVisitor();
		root.accept(sum);
		System.out.println("Sum: " + sum.getValue());
		
		MaxVisitor max = new MaxVisitor();
		root.accept(max);
		System.out.println("Max: " + max.getValue() + " @" + max.getName());
		
		MinVisitor min = new MinVisitor();
		root.accept(min);
		System.out.println("Min: " + min.getValue() + " @" + min.getName());
		
		AvgVisitor avg = new AvgVisitor();
		root.accept(avg);
		System.out.println("Avg: " + avg.getValue());
	}
}

실행 결과는 다음과 같습니다.

Sum: 313
Max: 70 @sub2
Min: 5 @sub1
Avg: 28.0
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다