[디자인패턴] Visitor 패턴
Visitor 패턴
데이터구조를 돌아다니면서 처리한다.
데이터의 구조와 처리를 분리한다.
데이터 구조를 돌아다니는 '방문자'를 정의해서, 이 방문자가 '처리'를 담당하도록 한다.
데이터 구조는, 문을 두드리는 '방문자'를 받아들이기만 하면 된다.
FileTreatmentException 예외처리
File 엔트리에 무언가를 추가(add)하고자 할 때 발생되는 예외
더블 디스패치 (Double Dispatch)
Visitor와 Acceptor는 서로 대응관계에 있으며, 서로가 서로를 호출한다.
ConcreteVisitor 역할의 추가는 간단하다.
구체적인 처리는 ConcreteVisitor 역할에 맡길 수 있고, 그 처리를 위한 ConcreteElement 역할을 수정할 필요는 전혀 없기 때문이다.
ConcreteElement 역할의 추가는 곤란하다.
만약 새로운 역할을 추가하였을 때에, Visitor 클래스에는 메소드를 또 만들 필요성이 생긴다. 그리고 하위 클래스에 그 메소드를 구현해야한다.
연습문제
13-1) 이 장의 예제 프로그램에 추가할 클래스로 FileFindVisitor 클래스를 작성하십시오. 이 클래스는 지정된 확장자의 파일을 모으는 것으로 가정합니다.
import java.util.Iterator;
import java.util.ArrayList;
public class FileFindVisitor extends Visitor {
private String filetype;
private ArrayList found = new ArrayList();
public FileFindVisitor(String filetype) { // '.txt'와 같이 확장자에 .을 붙여 지정
this.filetype = filetype;
}
public Iterator getFoundFiles() { // 발견한 파일을 얻는다.
return found.iterator();
}
public void visit(File file) { // 파일을 방문할 때 호출
if (file.getName().endsWith(filetype)) {
found.add(file);
}
}
public void visit(Directory directory) { // 디렉터리를 방문할 때 호출
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.accept(this);
}
}
}
13-2) 예제 프로그램의 Directory 클래스의 getSize 클래스에서는 '사이즈를 얻는' 처리를 실행합니다. '사이즈를 얻는' 처리를 실행하는 SizeVisitor 클래스를 도입해서 Directory 클래스의 getSize 메소드를 다시 기술하십시오.
SizeVisitor.java
import java.util.Iterator;
public class SizeVisitor extends Visitor {
private int size = 0;
public int getSize() {
return size;
}
public void visit(File file) {
size += file.getSize();
}
public void visit(Directory directory) {
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.accept(this);
}
}
}
13-3) java,util.ArrayList에 Element 인터페이스의 기능을 가진 ElementArrayList 클래스를 작성하십시오. ElementArrayList의 인스턴스에 대해서는 Directory나 File의 인스턴스를 add할 수 있고, ListVisitor의 인스턴스를 accept할 수 있도록 합니다.
import java.util.ArrayList;
import java.util.Iterator;
class ElementArrayList extends ArrayList implements Element {
public void accept(Visitor v) {
Iterator it = iterator();
while (it.hasNext()) {
Element e = (Element)it.next();
e.accept(v);
}
}
}
13-4) final 선언되어 있는 클래스는 하위 클래스를 만들 수 없습니다. 예를 들어 String 클래스는 final 클래스로 선언되어있기 때문에 다음과 같이 MyString 클래스를 정의할 수 없습니다.
// 컴파일 에러가 뜬다.
class MyString extends String {
...
}
이와 같이 String 클래스는 확장에 관한 The Open-Closed Principle을 위반하고 있지만, 사실은 위반할만한 정당한 이유가 있습니다. 그 이유는 무엇입니까?
--> 효율적이기 때문.
String 클래스는 문자열을 다루는 기본적인 클래스로 중요한 역할을 가진다.
따라서, java 언어처리계에서는 String 클래스가 '확장되지 않는다.'는 전제아래 처리속도에 관한 최적화 및 메모리 양에 관한 최적화를 실행하고 있다.