본문 바로가기

java/Design Pattern

Visitor

자바 디자인 패턴 16 - Visitor


1. Visitor 패턴은..


복잡한 구조체 안을 돌아다니면서 어떤 일을 해야 할 경우가 있습니다. Visitor는 어떤 구조체에 대해 그 안을 돌아다니면서 어떤 일을 하는 것입니다. 이 때, 구조체 1개에 하는 일이 딱 1개라는 보장은 없습니다. 하나의 구조체에 대해 다양한 일들을 할 수 있습니다. 하고 싶은 일이 추가된다고 해서 구조체를 변경하는 것은 무리입니다. 이런 때는 Visitor를 추가하면 됩니다. 예제에서는 PC의 디렉토리-파일 구조에 대해 야동을 찾는 일을 하는 Visitor를 구현해보았습니다. 


2. 예제


--------- Component, Composite, Leaf 등은 Composite 패턴 설명에 썼던 것을 거의 그대로 사용했습니다. 바뀐 부분은 색깔 처리했습니다. 처리한 부분만 보시면 됩니다.



package ch16_Visitor;

import java.util.ArrayList;

import java.util.List;


public abstract class Component implements Acceptor{

    public void accept(Visitor visitor) {

        visitor.visit(this);

    }

    private String componentName;

    protected List<Component> children = new ArrayList<Component>();

    public Component(String componentName) {

        this.componentName = componentName;

    }

    public String getComponentName() {

        return componentName;

    }

    public abstract void add(Component c);

    public List<Component> getChildren(){

        return children;

    }

}


package ch16_Visitor;

public class Composite extends Component {

    public Composite(String componentName) {

        super(componentName);

    }

    @Override

    public void add(Component c) {

        children.add(c);

    }

}


package ch16_Visitor;

public class Leaf extends Component{

    public Leaf(String componentName) {

        super(componentName);

    }

    @Override

    public void add(Component c) {

        throw new UnsupportedOperationException();

    }

}

------------------ Visitor를 받아들일 수 있는 구조체 ---------------- 

package ch16_Visitor;


public interface Acceptor {

    void accept(Visitor visitor);

}

------------------ Acceptor를 방문하는 Visitor---------------- 

package ch16_Visitor;


public interface Visitor {

    void visit(Acceptor acceptor);

}

------------------ 야동 찾는 YadongFinder ---------------- 

package ch16_Visitor;

import java.util.ArrayList;

import java.util.List;

public class YadongFinder implements Visitor {

    private List<String> yadongList = new ArrayList<String>();

    private List<String> currentList = new ArrayList<String>();

    public void visit(Acceptor acceptor) {

        if (acceptor instanceof Composite) {

            Composite composite = (Composite) acceptor;

            currentList.add(composite.getComponentName());

            List<Component> children = composite.getChildren();

            for (Component component : children) {

                component.accept(this);

            }

            currentList.remove(currentList.size()-1);

        }else  if (acceptor instanceof Leaf) {

            Leaf leaf = (Leaf) acceptor;

            doSomething(leaf);

        }

    }

    protected void doSomething(Leaf leaf){

        if (isYadong(leaf)) {

                String fullPath = getFullPath(leaf);

                yadongList.add(fullPath);

            }

    }

    protected String getFullPath(Leaf leaf) {

        StringBuilder fullPath = new StringBuilder();

        for (String element : currentList) {

            fullPath.append(element).append("\\");

        }

        return fullPath.append(leaf.getComponentName()).toString();

    }

    private boolean isYadong(Leaf leaf) {

        return leaf.getComponentName().endsWith(".avi");

    }


    public List<String> getYadongList() {

        return yadongList;

    }

}

------------------ 테스트 클래스 ----------------  

package ch16_Visitor;


public class Test {

    public static void main(String[] args) {

        Composite main = createComposite();

        YadongFinder visitor = new YadongFinder();

        visitor.visit(main);

        for (String string : visitor.getYadongList()) {

            System.out.println(string);

        }

    }


    private static Composite createComposite() {

        Composite main = new Composite("C:");

        Composite sub1 = new Composite("Program Files");

        Composite sub2 = new Composite("WINDOWS");

        Composite sub11 = new Composite("Pruna");

        Composite sub21 = new Composite("system32");

        Composite sub111= new Composite("Incoming");


        Leaf leaf1111 = new Leaf("강호동 닮은여자-짱이쁨.avi");

        Leaf leaf1112 = new Leaf("EBS야동특강.avi");

        Leaf leaf211 = new Leaf("야메떼-다이조부.avi");

        Leaf leaf212 = new Leaf("이건 야동아님.jpg");

        

        main.add(sub1);

        main.add(sub2);

        sub1.add(sub11);

        sub2.add(sub21);

        sub11.add(sub111);


        sub111.add(leaf1111);

        sub111.add(leaf1112);

        sub21.add(leaf211);

        sub21.add(leaf212);

        return main;

    }

}

---------------- 테스트 결과 -------------

C:\Program Files\Pruna\Incoming\강호동 닮은여자-짱이쁨.avi

C:\Program Files\Pruna\Incoming\EBS야동특강.avi

C:\WINDOWS\system32\야메떼-다이조부.avi


위의 예제에서 중요한 것은 Visitor의 visit(Acceptor) 와  Acceptor의 accept(Visitor)  입니다.


Visitor는 visit(Acceptor) 를 가지고 있고, Acceptor는 accept(Visitor) 를 가지고 있습니다. 둘의 차이가 헤깔립니다. 게다가 accept(Visitor) 를 구현해 놓은 것을 보면 아래와 같이 그냥 Visitor한테 자기 자신을 던져버리는 게 하는 일의 전붑니다.


public void accept(Visitor visitor) {

    visitor.visit(this);

}


이렇게 해 놓은 이유는 구조체를 돌아다닐 수 있게 하기 위한 것입니다. 실제로 구조체를 돌아다니는 일은 Visitor에서 담당하게 됩니다. 만약 이렇게 해놓지 않았다면, Visitor 안에서 구조체를 돌기 위해 재귀적인 호출을 해야만 복잡한 구조를 다 돌 수 있습니다. YadongFinder의 visit(Acceptor) 를 보면, 재귀적인 호출은 없습니다. 일반적으로 accept(Visitor)의 구현은 위와 같으며 달라질 일이 거의 없습니다.


Visitor의 visit(Acceptor)나 Acceptor의 accept(Visitor) 중 하나는 구조체를 도는 역할을 해야합니다. 구조체를 도는 역할은 Visitor의 visit(Accept)에 맡기는 것이 좋습니다. 구조체를 돌면서 하는 일 뿐만 아니라 "구조체를 도는 방법"도 다른 게 할 수도 있기 때문입니다. 위의 예제에서 YadongFinder는 아무래도 엄마가 짠 것 같습니다. 만약에 아들이 짰다면 구조체를 돌 때 "C:\Program Files\Pruna\Incoming\" 과 같은 비밀스러운 디렉토리는 슬쩍 뛰어넘는 로직을 짤 수 있었겠지요.


그러나 일반적으로 순화하는 로직은 거의 바뀌지 않습니다. 위의 예제에서는 YadongFinder를 상속 받아 doSomething(Leaf) 만 override 하면 뭔가 새로운 Visitor를 만들 수 있습니다.


3. 기타


visit(Acceptor) 안 쪽에서 instance of 로 어떤 클래스인지를 찾아냅니다. 이것은 visit(Composite) 와 visit(Leaf)로 분리시켰더라면 굳이 instance of를 쓸 필요가 없었을 것입니다. 하지만, 그렇게되면 Acceptor라는 인터페이스가 무의미해집니다.


Visitor와 Acceptor는 매우 밀접하게 묶여있습니다. 하지만 사실 Visitor의 구현체와 구조체 사이도 꽤 끈끈하게 묶여있습니다. YadongFinder 안에서 instance of로 체크한 것은 전부 구조체에서 정의된 클래스들(Composite, Leaf) 입니다.


테스트 코드의 YadongFinder를 선언하는 부분을 보면,

YadongFinder visitor = new YadongFinder();  와 같이 되어있습니다. 왜

Visitor visitor = new YadoingFinder(); 라고 인터페이스로 정의를 하지 않았을까요?


YadongFinder의 getYadongList() 부분이 포인트입니다. visitor는 구조체를 돌아다니면서직접 일을 할 수도 있습니다.( 예를들어, YadongFinder를 조금만 수정하면 YadongRemover 로 만들 수도 있습니다.) 하지만 돌아다니면서 뭔가를 수집하는 것과 같이 직접 구조체를 수정하지 않고 단지 정보만 수집하는 경우가 있는데, 그런 경우는 수집한 정보를 다시 뽑아서 사용할 수 있는 방법을 제공해야 합니다. Test 클래스에서 야동 리스트를 찍는 부분을 보시면 됩니다.

'java > Design Pattern' 카테고리의 다른 글

Mediator  (0) 2016.10.12
Builder  (0) 2016.10.12
Flyweight  (0) 2016.10.12
Prototype  (0) 2016.10.12
Observer  (0) 2016.10.12