2021. 12. 11. 15:43ㆍComputer Sciences/Design Patterns
개요
방문자 패턴(Visitor Pattern)은 데이터 구조와 처리를 분리하는 패턴이다. 무슨 말인지 잘 와닿지 않을 것이다. 시나리오를 통해 알아보도록 하자.
시나리오
사용자가 내 컴퓨터의 디렉토리를 순회할 수 있는 프로그램을 만들어달라고 한다. 우리는 이를 재귀를 통해서 해결할 수 있다. 대략적으로 코딩하면 다음과 같을 것이다.
// 디렉토리면 하위 디렉토리를 다시 순회하고 아니면 파일 정보 출력
if(file.isDirectory()) {
Iterator iter = file.iterator();
iter.search();
} else {
System.out.println(file.toString());
}
그런데 이젠 사용자가 각 파일에 대해 순회하면서 이름만 가져오는 기능도 원한다. 그렇다면 file.getName()과 같은 메서드로 이름을 가져오고 순회 클래스를 새로 만들어서 작성해야 할 것이다. 나쁜 냄새가 스멀스멀 올라온다.
문제
- 기능이 변경될 때마다 코드를 수정해야한다.
- 기능이 추가되면 클래스를 새로 작성해야 하는 경우도 발생한다.
해결
우리는 데이터 구조와 처리에 대해 로직을 분리함으로써 이 문제를 해결한다. 순회하고자 하는 요소들은 accpet()라는 메서드를 구현한다. 이 메서드는 외부에서 Visitor라고 하는 객체를 받고 해당 객체에서 작성된 방법대로 요소들에 대해 처리한다. 이말인즉슨 Visitor는 요소들에 대해 알고 있다는 것이 된다. 즉 캡슐화 관점에서 이 패턴은 썩 좋은 방법은 아니다. 하지만 클라이언트는 Visitor만 알고 있으면 원하는 방법대로 데이터들을 순회할 수 있게 된다. 따라서 복합 객체는 건드리지 않고 Visitor만 수정하거나 추가하여 사용할 수 있다.
글로만 보면 패턴을 이해하는데 어렵다(나만 그런가?). 따라서 코드를 직접 작성해보고 이해하도록 하자.
아래 예제에서는 ListVisitor만 생성해서 각 파일의 정보를 toString()으로 출력했지만 NameVisitor, SizeVisitor, 또는 이외에 각종 상태들에 대해 순회하는 Visitor를 만들어서 사용할 수도 있다.
Class Diagram
예제 코드
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
public interface Element {
void accept(Visitor v);
}
public abstract class Entry implements Element {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public Iterator<Entry> iterator() {
throw new FileTreatmentException();
}
public String toString() {
return getName() + "(" + getSize() + ")";
}
}
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends Entry {
private String name;
private ArrayList<Entry> dir = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator<Entry> iter = dir.iterator();
while (iter.hasNext()) {
Entry entry = iter.next();
size += entry.getSize();
}
return size;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
public Entry add(Entry entry) {
dir.add(entry);
return this;
}
public Iterator<Entry> iterator() {
return dir.iterator();
}
}
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
import java.util.Iterator;
public class ListVisitor extends Visitor {
private String currentDir = "";
@Override
public void visit(File file) {
System.out.println(currentDir + "/" + file);
}
@Override
public void visit(Directory directory) {
System.out.println(currentDir + "/" + directory);
String saveDir = currentDir;
currentDir = currentDir + "/" + directory.getName();
Iterator<Entry> it = directory.iterator();
while (it.hasNext()) {
Entry entry = it.next();
entry.accept(this);
}
currentDir = saveDir;
}
}
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 100000));
bindir.add(new File("latex", 20000));
rootdir.accept(new ListVisitor());
System.out.println();
System.out.println("Making user entries...");
Directory Kim = new Directory("Kim");
Directory Lee = new Directory("Lee");
Directory Park = new Directory("Park");
usrdir.add(Kim);
usrdir.add(Lee);
usrdir.add(Park);
Kim.add(new File("diary.html", 100));
Kim.add(new File("Composite.java", 200));
Lee.add(new File("memo.txt", 300));
Park.add(new File("game.doc", 400));
Park.add(new File("junk.mail", 500));
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
장단점
장점
- 구조 자체를 변경시키지 않으면서 복합 객체 구조에 새로운 기능을 추가할 수 있다.
- 비교적 쉽게 새로운 기능을 추가할 수 있다.
- Visitor에서 수행하는 기능와 관련된 코드를 한 곳에 집중시킬 수 있다.
단점
- 복합 객체의 캡슐화가 깨진다.
- 컬렉션 내의 모든 항목을 접근하기 때문에 복합 구조를 변경하기 어려워진다.
'Computer Sciences > Design Patterns' 카테고리의 다른 글
Memento Pattern (0) | 2021.12.11 |
---|---|
Flyweight Pattern (0) | 2021.12.11 |
Prototype Pattern (0) | 2021.12.11 |
14. Chain of Responsibility Pattern (0) | 2021.11.14 |
13. Bridge Pattern (0) | 2021.11.14 |