본문 바로가기
개발/디자인 패턴

[Java 언어로 배우는 디자인 패턴 입문] 구조를 돌아다니기 13. Visitor

by hongdor 2021. 1. 11.
728x90

출처 : 책 - java 언어로 배우는 디자인 패턴 입문

 

13. Visitor - 데이터 구조를 돌아다니면서 처리하기

 

 

1.  목적

 

 - 말 그대로 데이터 구조를 방문(visit)하기 위한 패턴이다. 

 - 재귀가 활용된다.

 Ex) List = { L1, L2 } 라고하면

      List가 visitor를 받아들이면 visitor는 List 내부를 돌며 L1, L2도 자신을 받아들이게 한다.

      List(visitor) -> L1(visitor), L2(visitor)

 

 

2. 예제 - 이전 Composite 패턴 예제가 사용된다.

 

(1) Visitor - 구현을 위한 부모 추상클래스. visit 메소드를 가지고 있다.

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

 

 

(2) ListVisitor - Visitor 구현, visit 메소드가 오버로딩 되어있다. 

                         visit(Directory directory) 메소드를 보면 내부를 돌며 accept함수를 실행해 자기를 받아 들이게 한다.

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 it = directory.iterator();
        while (it.hasNext()) { // 내부를 돌며 accept 하게 만드는것이 핵심
            Entry entry = (Entry)it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

 

 

(3) Element - 방문자를 받아 들이는 부모 인터페이스. accept 메소드를 가지고 있다.

public interface Element {
    public abstract void accept(Visitor v);
}

 

 

(4) Entry - Element를 상속받는 부모 추상클래스. 즉, visitor를 받아들이는 역할이다.

import java.util.Iterator;

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 iterator() throws FileTreatmentException {          
        throw new FileTreatmentException();
    }
    public String toString() {                                         
        return getName() + " (" + getSize() + ")";
    }
}

 

 

(5) File - Entry 자식 클래스. accept함수가 Visitor의 visit을 실행시킨다.

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;
    }
    public void accept(Visitor v) {
        v.visit(this);             
    }                          
}

 

 

(6) Directory - Entry 자식 클래스. accept 함수가 Visitor의 visit을 실행시키고, visit이 다시 accept를 실행시킨다.

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

public class Directory extends Entry {
    private String name;                    
    private ArrayList dir = new ArrayList();   
    public Directory(String name) {         
        this.name = name;
    }
    public String getName() {               
        return name;
    }
    public int getSize() {                  
        int size = 0;
        Iterator it = dir.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            size += entry.getSize();
        }
        return size;
    }
    public Entry add(Entry entry) {        
        dir.add(entry);
        return this;
    }
    public Iterator iterator() {             
        return dir.iterator();
    }
    public void accept(Visitor v) {         // 방문자 승낙
        v.visit(this);              
    }                           
}

 

 

(7) Main

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", 10000));
            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.tex", 300));
            Park.add(new File("game.doc", 400));
            Park.add(new File("junk.mail", 500));
            rootdir.accept(new ListVisitor());              
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

 

 

3. 참고사항

 

cf) The Open-Closed Principle(OCP) : 확장에는 열려있고 수정에 대해서는 닫혀있다.

    1. visit의 알고리즘을 바꾸고 싶으면 ListVisitor 외에 다른 visit 구현클래스를 만들어 Element에 넘겨주면 된다.

        - OCP 만족

    2. Driectory, file 클래스 외에 Device라는 Element 상속 클래스가 늘어나면, 기존 Visit 클래스에 

        visitor(Device device) 메소드를 추가 해줘야한다. 기존 클래스의 수정이 발생하므로

        - OCP를 만족하지 못한다.

728x90

댓글