PostCover

디자인 패턴을 쉽게 풀어보자 - 방문자 패턴편

GoF의 23가지 디자인 패턴 중 하나인 방문자 패턴(Visitor Pattern)을 쉽게 풀어보기

방문자(Visitor) 패턴

방문자(Visitor) 패턴은 객체 구조(컬렉션 등)의 요소에 새로운 기능을 추가하고 싶을 때 사용하는 행동 디자인 패턴입니다.

이 패턴에서는 **방문자 객체(Visitor)**가 객체 구조의 요소를 방문하며 각기 다른 작업을 수행합니다.

특히, 객체의 구조를 변경하지 않고도 새로운 동작을 추가할 수 있는 점이 특징입니다.

문제점: 객체 구조를 변경하지 않고 새로운 기능을 추가해야 하는 경우

예를 들어, 다양한 종류의 도형(Circle, Rectangle)에 대해 면적 계산, 색상 변경 등 새로운 기능을 추가해야 한다고 가정합니다. 이때 각 도형 클래스에 기능을 추가하면 클래스의 복잡도가 증가합니다. 또한, 매번 기능을 추가할 때마다 도형 클래스를 수정해야 하므로 유지보수가 어렵습니다.

class Circle {

    public void draw() {
        System.out.println("Drawing a Circle");
    }

    public void calculateArea() {
        System.out.println("Calculating area of Circle");
    }
}

class Rectangle {

    public void draw() {
        System.out.println("Drawing a Rectangle");
    }

    public void calculateArea() {
        System.out.println("Calculating area of Rectangle");
    }
}

문제점

  • 코드 중복: 각 클래스에 비슷한 기능이 반복됩니다.
  • 유연성 부족: 새로운 기능이 필요할 때마다 모든 도형 클래스를 수정해야 합니다.
  • OCP(Open-Closed Principle) 위반: 기존 클래스를 수정하지 않고 기능을 확장해야 하는 원칙이 지켜지지 않습니다.

해결 방법: 방문자 패턴 적용

방문자 패턴에서는 객체의 구조와 기능을 분리합니다.

즉, 객체에 직접 기능을 추가하는 대신, Visitor 객체가 각 요소를 방문하면서 필요한 작업을 수행합니다.

이로써 기존 클래스에 대한 수정 없이 새로운 기능을 쉽게 추가할 수 있습니다.

Visitor 인터페이스 정의

interface ShapeVisitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

Visitor 구현체 구현

class AreaCalculator implements ShapeVisitor {

    @Override
    public void visit(Circle circle) {
        System.out.println("Calculating area of Circle");
    }

    @Override
    public void visit(Rectangle rectangle) {
        System.out.println("Calculating area of Rectangle");
    }
}

class ColorChanger implements ShapeVisitor {

    @Override
    public void visit(Circle circle) {
        System.out.println("Changing color of Circle");
    }

    @Override
    public void visit(Rectangle rectangle) {
        System.out.println("Changing color of Rectangle");
    }
}

도형 클래스 구현

interface Shape {
    void accept(ShapeVisitor visitor);
}

class Circle implements Shape {

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

class Rectangle implements Shape {

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

클라이언트 코드

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();

        ShapeVisitor areaCalculator = new AreaCalculator();
        ShapeVisitor colorChanger = new ColorChanger();

        // 면적 계산 기능 수행
        circle.accept(areaCalculator); // Calculating area of Circle
        rectangle.accept(areaCalculator); // Calculating area of Rectangle

        // 색상 변경 기능 수행
        circle.accept(colorChanger); // Changing color of Circle
        rectangle.accept(colorChanger); // Changing color of Rectangle
    }
}

방문자 패턴의 동작 원리

  1. 객체 구조는 방문자를 받아들이는 메서드(accept)를 구현합니다.
  2. Visitor 객체는 객체 구조의 각 요소를 방문하며 해당 요소에 적합한 작업을 수행합니다.
  3. 새로운 작업이 필요하면 Visitor 인터페이스를 확장하여 쉽게 구현할 수 있습니다.

방문자 패턴의 장단점

장점

  • 기능 확장 용이: 객체의 구조를 변경하지 않고도 새로운 기능을 쉽게 추가할 수 있습니다.
  • OCP 준수: 기존 코드를 수정하지 않고 기능을 확장합니다.
  • 코드 분리: 객체의 데이터와 기능을 분리하여 유지보수성이 향상됩니다.

단점

  • 객체 구조의 변경이 어렵다: 객체 구조가 자주 변경되면, 모든 Visitor 클래스를 수정해야 합니다.
  • 복잡성 증가: 객체와 Visitor 간의 상호작용이 많아지면 코드가 복잡해질 수 있습니다.
  • 클래스 수 증가: 각 기능마다 별도의 Visitor 클래스를 만들어야 하므로 클래스 수가 많아질 수 있습니다.

결론

Visitor 패턴은 객체의 구조와 기능을 분리하여 기존 클래스를 수정하지 않고 기능을 확장할 수 있는 유용한 패턴입니다. 특히, 다양한 객체 구조에 대해 일관된 작업을 수행해야 할 때 유용하며, 컴파일러, 파일 시스템 탐색, 게임 개발 등에서 자주 사용됩니다.

하지만 객체 구조가 자주 바뀌거나 코드가 단순할 경우 Visitor 패턴의 복잡성이 오히려 부담이 될 수 있으므로 상황에 따라 적절하게 사용해야 합니다.