Design Pattern | Visitor Pattern (비지터 패턴)


Visitor 패턴이란?

  • Visitor라는 영어 단어는 방문자라는 의미이다.
  • Visitor 패턴은 데이터 구조와 처리를 분리하는 방법이다.
  • 데이터 구조를 둘러싼 방문자 클래스를 준비하고 방문자 클래스에 처리를 맡긴다. 그러고 새로운 처리를 추가하고 싶을 때는 새로운 방문자를 만들면 된다. 그리고 데이터 구조에서는 방문자를 받아들이면 된다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Visitor 패턴의 예제 프로그램

디렉토리, 파일 목록을 표시하는 예제 프로그램이다.

Class Diagram
Visitor Pattern Class Diagram

1. Element 인터페이스

Visitor 클래스의 인스턴스를 받아들이는 데이터 구조를 나타내는 인터페이스이다.

Element.java

package com.devkuma.designpattern.behavioral.visitor;

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

2. Entry 클래스

File이나 Directory의 기본이 되는 클래스이다. Element 인터페이스를 구현한다.

Entry.java

package com.devkuma.designpattern.behavioral.visitor;

public abstract class Entry implements Element {

    public abstract String getName();

    public String toString() {
        return getName();
    }
}

3. File 클래스

파일을 나타내는 클래스이다. Visitor의 수락 역할을 한다.

File.java

package com.devkuma.designpattern.behavioral.visitor;

public class File extends Entry {

    private String name;

    public File(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

4. Directory 클래스

디렉토리를 나타내는 클래스이다. Visitor의 수락 역할을 한다.

Directory.java

package com.devkuma.designpattern.behavioral.visitor;

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;
    }

    public String getName() {
        return name;
    }

    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    public Iterator<Entry> iterator() {
        return dir.iterator();
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

5. Visitor 클래스

파일이나 디렉토리를 방문하는 방문자를 나타내는 추상 클래스이다.

Visitor.java

package com.devkuma.designpattern.behavioral.visitor;

public abstract class Visitor {
    public abstract void visit(File file);

    public abstract void visit(Directory directory);
}

6. ListVisitor 클래스

파일이나 디렉토리의 일람을 표시하는 클래스이다.

ListVisitor.java

package com.devkuma.designpattern.behavioral.visitor;

import java.util.Iterator;

public class ListVisitor extends Visitor {

    // 현재 위치한 디렉토리명
    private String currentDir = "";

    // 파일을 방문했을 때 호출
    public void visit(File file) {
        System.out.println(currentDir + "/" + file);
    }

    // 디렉토리를 방문했을 때 호출
    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;
    }
}

7. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.visitor;

public class Main {
    public static void main(String[] args) {

        Directory workspaceDir = new Directory("workspace");
        Directory compositeDir = new Directory("Visitor");
        Directory testDir1 = new Directory("test1");
        Directory testDir2 = new Directory("test2");
        workspaceDir.add(compositeDir);
        workspaceDir.add(testDir1);
        workspaceDir.add(testDir2);

        File element = new File("Element.java");
        File entity = new File("Entity.java");
        File file = new File("File.java");
        File directory = new File("Directory.java");
        File visitor = new File("Visitor.java");
        File listVisitor = new File("ListVisitor.java");
        File main = new File("Main.java");
        compositeDir.add(element);
        compositeDir.add(entity);
        compositeDir.add(file);
        compositeDir.add(directory);
        compositeDir.add(visitor);
        compositeDir.add(listVisitor);
        compositeDir.add(main);

        workspaceDir.accept(new ListVisitor());
    }
}

8. 실행 결과

/workspace
/workspace/Visitor
/workspace/Visitor/Element.java
/workspace/Visitor/Entity.java
/workspace/Visitor/File.java
/workspace/Visitor/Directory.java
/workspace/Visitor/Visitor.java
/workspace/Visitor/ListVisitor.java
/workspace/Visitor/Main.java
/workspace/test1
/workspace/test2

Visitor 패턴의 장점

Visitor 패턴은 처리를 복잡하게 하고 있는 것만으로, “반복의 처리가 필요하다면 데이터 구조안에 루프 처리를 쓰면 좋은 것은 아닐까?“라고 느낀다. 방문자 패턴의 목적은 데이터 구조와 처리를 분리하는 것이다. 데이터 구조는 요소를 집합으로 정리하거나 요소간을 연결해 주는 것이다. 그 구조를 유지해 두는 것과 그 구조를 기초로 한 처리를 쓰는 것은 다른 것이다.
방문자 역할(ListVisitor)은 허용 역할(File, Directory)과는 독립적으로 개발할 수 있다. 즉, Visitor 패턴은, 받아들이게 되는(File, Directory) 클래스의 부품으로서의 독립성을 높이고 있는 것이다. 만약, 처리의 내용을 File 클래스나 Directory 클래스의 메소드로서 구현하게 된다면, 새로운 처리를 추가해 기능 확장할 때마다 File 클래스나 Directory 클래스를 수정해야만 한다.