객체 구조를 돌아다니면서 동일한 조작을 반복해서 적용하기. 이 때, '객체 구조'와 '처리(Algorithm)'을 분리하는 것이 핵심. 개방-폐쇄 원칙(OCP, Open-Closed Principle)1)을 적용한 방법중 하나.
시나리오
Method Overloading을 이용하여 각각의 처리할 Logic 수행하는 역할.
package visitor; import element.objectstructure.concrete.Directory; import element.objectstructure.concrete.File; public abstract class Visitor { public abstract void visit(File file); public abstract void visit(Directory directory); }
package visitor.concrete; import java.util.Iterator; import element.objectstructure.Entry; import element.objectstructure.concrete.Directory; import element.objectstructure.concrete.File; import visitor.Visitor; public class ListVisitor extends Visitor { private String currentDirectoryName = ""; @Override public void visit(File file) { System.out.println(currentDirectoryName + " / " + file); } @Override public void visit(Directory directory) { System.out.println(currentDirectoryName + " / " + directory); String tempDirectoryName = currentDirectoryName; currentDirectoryName = new StringBuilder(currentDirectoryName).append("/").append(directory.getName()).toString(); Iterator<Entry> it = directory.iterator(); while(it.hasNext()) { it.next().accept(this); } currentDirectoryName = tempDirectoryName; } }
방문할 객체를 지정하는 역할. accept()로 요청하여 객체 각각의 처리 Logic 수행.
package element; import visitor.Visitor; public interface Element { void accept(Visitor visitor); }
package element.objectstructure; import java.util.Iterator; import element.Element; import element.objectstructure.exception.FileTreatmentException; public abstract class Entry implements Element { public abstract String getName(); public abstract int getSize(); /* Directory에서만 유효 */ public Entry add(Entry entry) throws FileTreatmentException { throw new FileTreatmentException(); } /* 객체 구조의 각 요소에 특정한 처리를 실행하는 데 필요 */ /* Directory에서만 유효 */ public Iterator<Entry> iterator() throws FileTreatmentException { throw new FileTreatmentException(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getName()); sb.append(" ("); sb.append(getSize()); sb.append(") "); return sb.toString(); } }
package element.objectstructure.concrete; import element.objectstructure.Entry; import visitor.Visitor; public class File extends Entry { private String name; private int size; public File(String name, int size) { this.name = name; this.size = size; } @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getName() { return name; } @Override public int getSize() { return size; } }
package element.objectstructure.concrete; import java.util.ArrayList; import java.util.Iterator; import element.objectstructure.Entry; import element.objectstructure.exception.FileTreatmentException; import visitor.Visitor; public class Directory extends Entry { private String name; private ArrayList<Entry> directories = new ArrayList<>(); public Directory(String name) { this.name = name; } @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getName() { return name; } @Override public int getSize() { return directories.size(); } @Override public Entry add(Entry entry) throws FileTreatmentException { directories.add(entry); return this; } @Override public Iterator<Entry> iterator() throws FileTreatmentException { return directories.iterator(); } }
package element.objectstructure.exception; public class FileTreatmentException extends RuntimeException { public FileTreatmentException() { } public FileTreatmentException(String message) { super(message); } }
import element.objectstructure.concrete.Directory; import element.objectstructure.concrete.File; import visitor.concrete.ListVisitor; public class Client { public static void main(String[] args) { 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("sub1", 10000)); bindir.add(new File("sub2", 20000)); rootdir.accept(new ListVisitor()); } } /* / root (3) /root / bin (2) /root/bin / vi (10000) /root/bin / latex (20000) /root / tmp (0) /root / usr (0) */