본문 바로가기

CS study/design pattern

[디자인 패턴]방문자(visitor) 패턴 설명과 예

목차

    설명

    방문자 패턴은 알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴이다.

     

    방문자(Visitor) 패턴은 객체 지향 디자인 패턴의 하나로, 알고리즘을 객체 구조에서 분리하여, 객체 구조의 클래스를 변경하지 않고도 새로운 연산을 추가할 수 있도록 한다.

     

    이 패턴은 객체 구조 내의 각 요소에 대해 수행할 연산을 별도의 '방문자' 객체에 정의한다.

    연산을 추가하는 것이 객체 구조의 클래스들을 변경하는 것보다 훨씬 용이하기 때문이다. 특히, 복잡한 객체 구조를 다룰 때 유용하며, 다양한 연산을 객체 구조에 추가하고자 할 때 활용된다.

     

    내가 직접적으로 느꼈던 직관적인 설명을 서술하자면, "일반적인 로직과 달리 방문자가 로직을 가지고 accept를 통해 파라미터로 받는 방문자 객체에게 자신의 정보를 넘겨준다!" 라고 할 수 있을 것이다.

     

    방문자 패턴의 핵심은 '확장성'과 '유연성'이다.

    박물관에 새로운 작품이나 유물이 추가된다고 생각해보자. 방문자나 가이드라인(Visitor Interface)을 변경하지 않고도 새로운 요소를 쉽게 추가할 수 있다. 새로운 방문자(관광객)가 오더라도 기존의 전시된 작품들을 변경하지 않고 다양한 방문자의 요구를 수용할 수 있다.

     

    즉, 이걸 쓰는 핵심 이유는 Visitor에 로직을 추가하고 적용시켜서, 방문하는 주체(Painting 등)는 그냥 그대로 쓰고( 해당 구조의 클래스를 변경하지 않아도 된다.) 방문자 객체만 유동적으로 조절해서 로직을 확장하고 편리하게 사용하기 위함이다. 이는 유지 보수성과 재사용성을 크게 향상시킨다.

     

     

    방문자 패턴의 구성 요소

    visitor 패턴은 마치 박물관과 같다.

     

    예시를 위해 박물관이나 미술관을 방문한다고 가정하자. 이 곳에는 다양한 종류의 예술 작품이나 역사적 유물들이 있다.

     

    방문자는 이러한 각각의 작품이나 유물들을 보며, 각기 다른 반응이나 감정, 평가를 할 수 있다.

    여기서 중요한 점은, 작품이나 유물들이 방문자를 위해 존재하는 것이 아니라, 방문자가 그들을 해석하고 이해하기 위해 접근한다는 것이다.

    1. Visitor Interface (방문자 인터페이스)

    방문할 객체 구조의 각 요소(Element) 클래스에 대한 visit 메소드를 선언한다.
    각 메소드는 방문할 요소의 클래스에 따라 오버로드될 수 있다.

     

    이는 박물관에서 각기 다른 유물이나 작품을 어떻게 해석할지에 대한 '가이드라인'과 같다.

    예를 들어, "이 그림을 볼 때는 색감에 주목하라", "이 조각상은 재질을 느껴보라"와 같은 지침을 제공한다.

     

    // 예술 작품에 대한 Element 인터페이스
    public interface ArtPiece {
        void accept(ArtVisitor visitor);
    }
    
    // 방문자 인터페이스
    public interface ArtVisitor {
        void visit(Painting painting);
        void visit(Sculpture sculpture);
    }

     

    2. Concrete Visitor (구체적 방문자)

    Visitor 인터페이스를 구현하는 클래스로, 구조 내의 각 Element 인스턴스를 방문할 때 수행할 연산을 구현한다.

     

    이 클래스는 구체적인 요소(Concrete Element)를 방문할 때 수행해야 할 연산을 구현한다.

    방문자 그 자체라고 할 수 있다. 실제로 박물관에 가서 각 작품이나 유물에 대해 감상평을 남기거나, 사진을 찍는 등의 행동을 하는 것이다.

    /**
    ArtAppreciator와 ArtCritic은 ArtVisitor 인터페이스를 구현하는 클래스로, 
    각각 예술 작품을 감상하고 비평하는 다른 방식을 표현한다.
    */
    
    // 예술 작품 감상자
    public class ArtAppreciator implements ArtVisitor {
        @Override
        public void visit(Painting painting) {
            System.out.println("Appreciating the beautiful colors of " + painting.getTitle());
        }
    
        @Override
        public void visit(Sculpture sculpture) {
            System.out.println("Admiring the form and material of " + sculpture.getTitle());
        }
    }
    
    // 예술 비평가
    public class ArtCritic implements ArtVisitor {
        @Override
        public void visit(Painting painting) {
            System.out.println("Analyzing the use of color and technique in " + painting.getTitle());
        }
    
        @Override
        public void visit(Sculpture sculpture) {
            System.out.println("Evaluating the craftsmanship and symbolism of " + sculpture.getTitle());
        }
    }

     

     

    3. Element Interface (요소 인터페이스)

    accept 메소드를 선언하여, Visitor 객체가 자신을 방문할 수 있도록 한다.

     

    박물관에 있는 각 작품이나 유물들이 '방문을 받아들일 준비가 되었다'는 것을 나타낸다.

    즉, accept 선언을 통해 여러분이 그것들을 감상할 수 있도록 전시되어 있는 상태라고 할 수 있다.

     

    Element 인터페이스는 accept 메서드를 선언한다. 이 메서드는 Visitor 객체를 인자로 받아, 방문자가 자신을 방문할 수 있도록 한다.

    는 방문자가 방문할 수 있는 모든 객체들에 공통적으로 적용되는 인터페이스이며 accept 메소드 선언을 통해 구현하는 각 객체(Painting, Sculpture)가 방문자(Visitor)를 받아들일 수 있도록 한다.

     

    // 예술 작품에 대한 Element 인터페이스
    public interface ArtPiece {
        void accept(ArtVisitor visitor); // 방문자를 받아들이는 메소드
    }

     

    4. Concrete Element (구체적 요소)

    Element 인터페이스를 구현하는 클래스로, Visitor가 자신을 방문했을 때 호출될 accept 메소드를 구현한다.

     

    박물관에 전시된 각각의 유물이나 예술 작품이다. 방문자가 접근하면, 자신이 가진 이야기나 정보, 느낌을 제공한다.

    실 코드에서는 방문자 접근 시 호출할 로직 등을 제공할 것이다.

     

    /**
    Painting과 Sculpture 클래스는 각각 그림과 조각 작품을 나타내며, 
    accept 메서드를 통해 자신을 방문하는 ArtVisitor 객체를 받아들인다. 
    */
    
    
    // 그림 작품
    public class Painting implements ArtPiece {
        private String title;
    
        public Painting(String title) {
            this.title = title;
        }
    
        public String getTitle() {
            return title;
        }
    
        @Override
        public void accept(ArtVisitor visitor) {
            visitor.visit(this); // 방문자에게 자신을 전달하여 방문 처리
        }
    }
    
    // 조각 작품
    public class Sculpture implements ArtPiece {
        private String title;
    
        public Sculpture(String title) {
            this.title = title;
        }
    
        public String getTitle() {
            return title;
        }
    
        @Override
        public void accept(ArtVisitor visitor) {
            visitor.visit(this); // 방문자에게 자신을 전달하여 방문 처리
        }
    }

     

    5. Object Structure (객체 구조)

    Element 객체들의 구조를 정의하는 클래스로, Visitor가 구조 내의 각 Element를 방문할 수 있도록 한다.

     

    박물관 또는 미술관 전체를 의미한다. 방문자가 방문할 수 있는 다양한 작품이나 유물들이 어떻게 배열되어 있는지, 어떤 순서로 둘러볼 수 있는지를 정의한다.

     

    코드 예시

    public class Museum {
        public static void main(String[] args) {
        
       	//1. visitor 방문자와 방문할 요소를 interface로 정의하고, 이를 구현한다. 
            List<ArtPiece> gallery = new ArrayList<>();
            gallery.add(new Painting("Starry Night"));
            gallery.add(new Sculpture("The Thinker"));
    
            ArtVisitor appreciator = new ArtAppreciator();
            ArtVisitor critic = new ArtCritic();
    
    	//2. 구현체에는 accept가 존재하여, 방문자가 방문할 수 있다(accept로만 접근 가능하다)
            for (ArtPiece piece : gallery) {
                piece.accept(appreciator); // 감상자가 각 작품을 방문
                piece.accept(critic); // 비평가가 각 작품을 방문
                //3. accept시 인터페이스에서 정의한 메서드를 실행할 수 있다. 자신을 전달한다.
            }
        }
    }

     

    "자신을 전달한다"는 것의 예시는 이런 것이다.

    Painting 객체가 있을 때 이 객체의 accept 메서드가 호출되면, Painting 객체는 visitor.visit(this);를 실행한다. (4번 코드를 확인하자.)

     

    즉, 일반적인 로직과 다르게 반대로 동작한다.

    파라미터를 가져다 쓰는게 아니라, 오히려 파라미터(visitor)에게 본인의 정보를 넘겨준다.

     

    piece.accept(appreciator) 하면 Artpiece 구현체의 visit을 실행한다면, 방문자가 visit parameter로 들어가지만 실제로는 방문자의 visit 메서드에 이 Artpiece 구현체가 들어가는 것이다.

     

    // 그림 작품
    public class Painting implements ArtPiece {
        private String title;
    
        public Painting(String title) {
            this.title = title;
        }
    
        public String getTitle() {
            return title;
        }
    
        @Override
        public void accept(ArtVisitor visitor) {
            visitor.visit(this); // (1) 방문자에게 자신을 전달하여 방문 처리
            //즉, visitor 파라미터를 ArtPiece가 사용하는 것이 아닌, 
            //본인의 정보를 parameter로 받은 visitor에게 알려준다. 2를 확인하자.
        }
    }
    
    //*******************************************************//
    
    
    public class ArtCritic implements ArtVisitor {
        @Override
        public void visit(Painting painting) {
        	//(2) accept를 통해 painting 작품은 이 visitor에게 자기의 정보를 건네준다.
            //이를 통해 방문자는 paint 정보를 알고 이를 사용할 수 있다. 
            System.out.println("Analyzing the use of color and technique in " + painting.getTitle());
        }
    
        @Override
        public void visit(Sculpture sculpture) {
            System.out.println("Evaluating the craftsmanship and symbolism of " + sculpture.getTitle());
        }
    }

     

    이렇게 Visitor 패턴은 Element의 타입에 따라 다른 visit 메서드를 실행하도록 하여, 동일한 Visitor 객체가 다양한 Element에 대해 다른 작업을 수행할 수 있게 한다.

     

    이 과정에서 Visitor는 Element의 구조나 상태를 변경할 수도 있고, Element로부터 정보를 추출할 수도 있다.