CS/디자인패턴

[디자인패턴] Visitor 패턴 (방문자, 비지터 패턴)

mabb 2023. 6. 21. 12:51
반응형

▶디자인 패턴 ( Design Pattern)

:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four) 라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 

▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.

 

---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶Visitor 패턴

"방문자에게 처리를 위임"
"accept and visit"
"node.accept(this)"
"visitor.visit(this)"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스

▶Visitor 패턴에 대한 이해

-Visitor는 방문자이다.

-기존 데이터 구조에 대한 수정을 최소화하고 기능을 확장하고자 할 때 사용한다. (OCP)

-확장하고자 하는 새로운 기능을 기존 클래스가 아닌 Visitor라는 별도의 클래스에서 처리한다.

-Visitor는 데이터 구조에 방문하면서 특정 처리를 하는 역할을 한다.

-데이터 구조와 처리를 분리하는 것이다.

-데이터 구조에서는 Visitor의 방문을 받아들일 수 있어야 한다.

-예제에서 Element인터페이스의 구현은, Visitor를 받아들일 수 있음을 나타낸다.

-Visitor가 방문해서 방문하는 곳의 this를 받는다. Visitor의 구현 클래스에서는 방문해서 가져온 this의 메서드를
이용하여 로직을 수행한다.

-비지터는 노드의 하위 노드들을 순회하며 차례차례 하나하나 방문한다.

-각각의 노드들에 대한 처리는 모두 비지터가 수행한다. (비지터에게 처리가 집중된다.)

-방문자 승낙, 방문자에게 자기자신(this)을 위임한다. 방문자는 위임받은 노드의 메서드를 활용하여 기능을
수행한다. 

-방문자가 노드의 하위 노드를 순회하며 처리할 수 있도록 노드에서는 iterator()를 반환하는 메서드를 구현한다.

- 두 개의 메소드가 서로를 호출하는 더블 디스패치(Double Dispatch, 이중 분리) 구조이다.

-Composit패턴으로 구조와 기본 기능이 완성되어 있다. 여기서 각 노드들에
새로운 기능을 추가(확장)하고자 한다. 각각의 노드 (복합체 또는 Leaf)에 대한
수정을 최소화 하면서 기능은 확장하고자 한다.(OCP) 이때 노드의 구조와 처리(기능)를
분리하는 VIsitor패턴을 이용하면 가능하다.

트리 구조의 데이터 구조에서 각 노드들에 새로운 기능을 추가하고자 한다.

 

▶왜 사용하는가

트리 구조의 데이터 구조에서 각 노드들에 새로운 기능을 추가하고자 한다. 물론 Branch클래스나 Leaf클래스를 수정하여 기능을 추가할 수 있을 것이다. 하지만 추가하는 기능이 많아진다면 '데이터의 구조'에 집중해야 할 Branch클래스나 Leaf클래스의 상태가 복잡해진다. '데이터 구조'를 담당하는 노드 Class의 수정을 최소화하면서 새로운 기능을 확장하고자 할 때 Visitor패턴을 사용한다. (OCP)

 

▶클래스다이어그램

Visitor패턴의 클래스다이어그램

 

▶각 클래스(인터페이스)의 역할

▷Visitor 역할

노드를 방문하여 처리하기 위한 비지터의 최상위 클래스이다.
노드를 처리하기 위한 추상 메소드를 선언한다. 노드의 종류 별로 메서드를 오버로드한다.

▷ConcreteVisitor 역할

방문하여 방문한 노드의 기능으로 특정 기능(로직)을 수행한다.
Visitor는 노드를 방문하면 노드 자기자신(this) 을 가지고 특정 기능(로직)을 수행한다. 하위 노드에 대한 재귀적 처리를 위하여 node.accept(this)로 하위 노드에도 자기 자신을 방문시킨다.

 

▷Visitable 역할

노드에서 Visitor를 받아들일 수 있도록 appcept(Visitor v) 추상 메소드를 선언한다.

 

▷ Branch 역할

컴포지트패턴에서 복합체 노드에 해당한다. 하위 노드로 Branch나 Leaf를 가질 수 있다.

 

▷ Leaf 역할

컴포지트패턴에서 최하위 노드에 해당한다(Leaf) 

▷BranchAndLeaf역할

Branch와 Leaf를 동일시 하기위한 클래스이다.

 

▷Main역할

Branch와 Leaf로 트리 구조를 생성한다.
예제에서는 나뭇잎의 개수를 세는 노드의 기능을 각각 수행해 보고
뿌리 노드에 Visitor를 방문시키고 있다. ( root.accept(wind) )

 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/

 

▷Visitor.java

package main.java.designpattern.visitor2;

public abstract class Visitor {
    public abstract void visitAndProcess(Branch branch);
    public abstract void visitAndProcess(Leaf leaf);
    public abstract String getName();
}

 

▷ConcreteVisitor.java

package main.java.designpattern.visitor2;

import java.util.Iterator;

public class ConcreteVisitor extends Visitor{
    private String name;

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

    @Override
    public void visitAndProcess(Branch branch) {
        System.out.println();
        System.out.println("■비지터 " + this.getName() +" 이/가 " + branch.getName() +  " 에 방문합니다.");
        System.out.println(branch.getName() + " 가지가 바람에 흔들립니다.");
        Iterator<BranchorLeaf> it = branch.iterator();
        System.out.println(branch.getName() + " 의 하위 노드에 방문하여 처리합니다↓");
        while(it.hasNext()){
            BranchorLeaf b = it.next();
            b.accept(this);
        }
    }

    @Override
    public void visitAndProcess(Leaf leaf) {
        System.out.println("  " + leaf.getName() + " 나뭇잎이 바람에 흔들립니다.");
    }

    @Override
    public String getName() {
        return name;
    }
}

 

▷Visitable.java

package main.java.designpattern.visitor2;


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

 

▷Branch.java

package main.java.designpattern.visitor2;

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

public class Branch extends BranchorLeaf {
    private String name;
    private int countLeaf;
    private List<BranchorLeaf> childs = new ArrayList<>();

    public Branch(String name){
        this.name = name;
    }
    @Override
    public int getCountLeaf() {
        int tmp = 0;
        Iterator<BranchorLeaf> iterator = childs.iterator();
        while(iterator.hasNext()){
            BranchorLeaf bol = iterator.next();
            tmp += bol.getCountLeaf();
        }
        countLeaf = tmp;
        return countLeaf;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public BranchorLeaf add(BranchorLeaf bol){
        childs.add(bol);
        return this;
    }
    @Override
    public void accept(Visitor v) {
        v.visitAndProcess(this);
    }
    //Visitor가 하위 노드에 접근할 수 있도록 iterator 반환 메소드 추가
    public Iterator<BranchorLeaf> iterator(){
        return childs.iterator();
    }
}

 

▷Leaf.java

package main.java.designpattern.visitor2;

public class Leaf extends BranchorLeaf {
    private String name;

    public Leaf(String name){
        this.name = name;
    }
    @Override
    public int getCountLeaf() {
        return 1;
    }

    @Override
    public String getName() {
        return name;
    }
    @Override
    public void accept(Visitor v) {
        v.visitAndProcess(this);
    }
}

 

▷BranchAndLeaf.java

package main.java.designpattern.visitor2;

public abstract class BranchorLeaf implements Visitable{
    public abstract int getCountLeaf();

    public String getName(){
        return null;
    }
    protected BranchorLeaf add(BranchorLeaf bol) throws Exception {
        throw new Exception();
    }

    public void printCountLeaf(){
        System.out.println(getName() + "가지의 잎파리의 개수는 " + getCountLeaf());
    }

}

 

▷Main.java

 

package main.java.designpattern.visitor2;

public class Main {
    public static void main(String[] args){
        Branch root = new Branch("root");
        Branch branchA = new Branch("branchA");
        Branch branchB = new Branch("branchB");
        Branch branchA1 = new Branch("branchA1");
        Branch branchA2 = new Branch("branchA2");

        Leaf l1 = new Leaf("leaf1");
        Leaf l2 = new Leaf("leaf2");
        Leaf l3 = new Leaf("leaf3");
        Leaf l4 = new Leaf("leaf4");
        Leaf l5 = new Leaf("leaf5");
        Leaf l6 = new Leaf("leaf6");

        root.add(branchA);
        root.add(branchB);
        branchA.add(l1);
        branchA.add(branchA1);
        branchA.add(branchA2);

        branchA1.add(l2);
        branchA1.add(l3);
        branchB.add(l4);
        branchB.add(l5);
        branchB.add(l6);

        root.printCountLeaf();
        branchA.printCountLeaf();
        branchA1.printCountLeaf();
        branchA2.printCountLeaf();
        branchB.printCountLeaf();


        /////////////////
        System.out.println("==============================");

        Visitor wind = new ConcreteVisitor("wind");
        root.accept(wind);
    }
}

 

▷실행 결과

실행결과

---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형