방문자 패턴(Visitor Pattern)이란?

Visitor는 사전적인 의미로 어떤 사람이나 장소를 찾아오는 사람 이라는 의미를 가지고 있다. 방문자 패턴에서는 데이터 구조와 처리를 분리한다. 데이터 구조 안을 돌아다니는 주체인 방문자 를 나타내는 클래스를 준비해서 그 클래스에게 처리를 맡긴다. 새로운 처리를 추가하고 싶을 땐 새로운 방문자 를 만들고 데이터 구조는 문들 두드리는 방문자 를 받아들이면 된다.

방문자 패턴은 개방-폐쇄 원칙(The Open-Closed Principle : OCP) 을 적용하는 방법 중 하나다.

개방-폐쇄 원칙의 두 가지 속성

확장에 대해서는 열려있지만 수정에 대해서는 닫혀있어야 한다.

확장에 대해 열려 있다.

클래스를 설계할 때에 특별한 이유가 없는 한 장래의 확장을 허락해야 한다. 이유 없이 확장을 금지해서는 안되는 것이 확장에 대해서는 열려있다 라는 의미다.

수정에 대해 닫혀 있다

확장할 때마다 기존의 클래스를 수정하는 것은 곤란하다. 확장을 하더라도 기존의 클래스는 수정할 필요가 없는 것이 수정에 대허서는 닫혀있다 라는 의미다.


방문자 패턴에 사용되는 역할

flickr

Visitor

Visitor가 하는 역할은 데이터 구조 내의 각각의 구체적인 요소에 visit 메소드를 선언하는 것이다. 실제로 처리를 하는 메소드는 ConcreteVisitor에서 다루고 있다.

ConcreteVisitor

ConcreteVisitor는 Visitor 역할의 인페이스를 구현하는 역할을 한다. visit이라는 형태의 메소드를 구현하고 각각의 ConcreteAcceptor 역할의 처리를 기술한다.

Acceptor

Acceptor는 Visitor 역할이 방문할 곳을 나타내는 역할을 하고 있다. 방문자를 받아들이는 accept 메소드를 선언한다. accept 메소드의 인수로는 Visitor의 역할이 넘겨진다.

ConcreteAcceptor

ConcreteAcceptor는 Acceptor 역할의 인터페이스를 구현하는 역할을 한다.

ObjectStructure

ObjectStructure는 Acceptor 역할의 집합을 취급하는 역할을 한다.

예제

Element 인터페이스
/*
 * Element.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

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

대상 객체 클래스들이 방문자를 허용해야 하므로 그 방문자 객체를 받아들인다는 의미의 인터페이스를 작성한다.


Cart 클래스
/*
 * Cart.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

import java.util.ArrayList;

public class Cart implements Element {
    ArrayList<Element> cart = new ArrayList<Element>();

    public Cart() {
        cart.add(new Fruits());
        cart.add(new Milk());
    }

    @Override
    public void accept(Visitor visitor) {
        System.out.println("Cart가 준비되었습니다");
        visitor.visit(this);

        for (Element element : cart) {
            element.accept(visitor);
        }
    }
}

생성자에서 나머지 객체들을 멤버 자료구조에 추가하고 accept() 메소드를 구현한다.


Fruits 클래스
/*
 * Fruits.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

public class Fruits implements Element{

    @Override
    public void accept(Visitor visitor) {
        System.out.println("과일이 준비되었습니다");
        visitor.visit(this);
    }
}


Milk 클래스
/*
 * Milk.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

public class Milk implements Element {

    @Override
    public void accept(Visitor visitor) {
        System.out.println("우유가 준비되었습니다");
        visitor.visit(this);
    }
}


Visitor 인터페이스
/*
 * Visitor.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

public interface Visitor {
    public void visit(Cart cart);
    public void visit(Fruits fruits);
    public void visit(Milk milk);
}

아래에 Cart, Fruits, Milk의 visit() 메소드에 각각 객체들 자신을 담아서 던지고 있으므로 인터페이스에서 이를 정의해준다. 동일한 visit() 메소드지만 overloading을 이용해서 서로 다른 객체를 처리한다.


Shopper 클래스
/*
 * Shopper.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

public class Shopper implements Visitor {
    @Override
    public void visit(Cart cart) {
        System.out.println("Cart를 이용합니다.");
    }

    @Override
    public void visit(Fruits fruits) {
        System.out.println("과일을 넣었습니다");
    }

    @Override
    public void visit(Milk milk) {
        System.out.println("우유를 넣었습니다");
    }
}

Visitor 인터페이스를 구현한 클래스다. Visitor 인터페이스에서 오버로딩으로 정의했기 때문에 이를 구현할 Shopper 클래스에서도 각 객체타입마다 visit() 메소드를 정의했다. visit() 메소드에 담겨 호출되는 객체의 종류에 따라서 자각기 다른 visit()이 호출된다.


Main 클래스
/*
 * HBNMain.java
 *
 * version 1.0
 *
 * 2017-12-26
 *
 * Copyright 1996 - 2017 hibrain.net All rights reserved.
 */

package io.hibrain.tutorial.designpattern.visitor;

/**
 *
 * @author 우성민
 * @version 1.0
 */

public class HBNMain {
    public static void main(String[] args) {
        Shopper shopper = new Shopper();
        Cart cart = new Cart();
        cart.accept(shopper);
    }
}

실행 결과

Cart가 준비되었습니다

Cart를 이용합니다

과일이 준비되었습니다

과일을 넣었습니다

우유가 준비되었습니다

우유를 넣었습니다

cart의 accept() 메소드에 비지터를 구현한 shopper를 던진다. cart의 accept() 메소드에서는 cart 자신의 객체를 담아 전달한다. 다시 Shopper 클래스로 와서 visit(Cart cart)을 실행하고 나머지 카트에 담겨진 Fruits와 Milk은 위에서 받은 visitor를 이용해 Fruits.accept(visitor)가 실행되고 호출한 Visitor 클래스 내의 visit(Fruits fruits)메소드를 실행, Milk.accept(visitor)가 실행되서 호출한 Visitor 클래스 내의 visit(Milk milk)메소드를 실행한다.

참고자료

Java 언어로 배우는 디자인 패턴, Yuki Hiroshi ,2001 http://techbard.tistory.com/2869