Java/Java 자료실

[디자인 패턴] 13. 방문자 패턴 ( Visitor Pattern )

Chipmunks 2018. 12. 19.
728x90

방문자 패턴 ( Visitor Pattern )

런타임 중에 하나 이상의 연산을 객체 집합에 적용되도록 해줍니다. 객체 구조로부터 연산이 분리됩니다.


클래스 다이어그램

시퀀스 다이어그램


예제 클래스 다이어그램


코드 중복 문제

visitor.accept ( this ); 코드를 반복하는 이유는, Java의 Single Dispatch 문제 때문이다. 메소드 호출이 런타임이 아닌 컴파일 시에 결정이 된다. 메소드 오버로딩과 흡사해보이지만, 메소드 오버로딩은 컴파일 시에 결정이 된다. Java에서 Double Patch를 구현하려면, 메소드 내에서 동적 디스패치를 적용하면 된다.

accept() 메소드를 갖고있는 인터페이스를 사용해서 동적으로 객체가 선택되게 한다.
여러 클래스를 파라미터로 오버로딩되어있는 Visitor 인터페이스를 accept 메소드의 인자로 전달한다.
그 메소드 안에서 Visitor 인터페이스의 visit() 메소드를 호출하고, 인자로 자기 자신을 보낸다.

따라서, 어떤 비지터 클래스든, 어떤 클래스든 accept() 메소드를 호출하는지 런타임시에 결정된다. 이를 Double Dispatch 라고 한다.

더블 디스패치 예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Query {
    ResultSet execute (DB db);
}
 
public class DB {
    public ResultSet execute(Query query) {
        query.execute(this);
    }
 
    public ResultSet read(ReadQuery query) {...}
    public ResultSet write(WriteQuery query) {...}
    public ResultSet delete(DeleteQuery query) {...}
}
 
public class ReadQuery implements Query {
    public ResultSet read(DB db) {return db.read(this);}
}
 
public class WriteQuery implements Query {
....
}
cs


예제 코드

interface CarElementVisitor {
    void visit(Wheel wheel);
    void visit(Engine engine);
    void visit(Body body);
    void visit(Car car);
}

interface CarElement {
    void accept(CarElementVisitor visitor); // CarElements have to provide accept().
}

class Wheel implements CarElement {
    private String name;

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

    public String getName() {
        return this.name;
    }

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

class Engine implements CarElement {
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Body implements CarElement {
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Car implements CarElement{
    CarElement[] elements;

    public CarElement[] getElements() {
        return elements.clone(); // Return a copy of the array of references.
    }

    public Car() {
        this.elements = new CarElement[]
          { new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left") , new Wheel("back right"),
            new Body(), new Engine() };
    }

    public void accept(CarElementVisitor visitor) {
        for(CarElement element : this.getElements()) {
            element.accept(visitor);
        }
        visitor.visit(this);
    }
}

class CarElementPrintVisitor implements CarElementVisitor {
    public void visit(Wheel wheel) {
        System.out.println("Visiting "+ wheel.getName()
                            + " wheel");
    }

    public void visit(Engine engine) {
        System.out.println("Visiting engine");
    }

    public void visit(Body body) {
        System.out.println("Visiting body");
    }

    public void visit(Car car) {
        System.out.println("Visiting car");
    }
}

class CarElementDoVisitor implements CarElementVisitor {
    public void visit(Wheel wheel) {
        System.out.println("Kicking my "+ wheel.getName() + " wheel");
    }

    public void visit(Engine engine) {
        System.out.println("Starting my engine");
    }

    public void visit(Body body) {
        System.out.println("Moving my body");
    }

    public void visit(Car car) {
        System.out.println("Starting my car");
    }
}

public class VisitorDemo {
    static public void main(String[] args){
        Car car = new Car();
        car.accept(new CarElementPrintVisitor());
        car.accept(new CarElementDoVisitor());
    }
}


댓글