설계패턴 25. Visitor Pattern

LSDrug·2024년 6월 14일

설계패턴(完)

목록 보기
26/26

1. 정의

데이터구조와 처리를 분리하는 패턴

데이터 구조 안에 많은 요소가 저장되어 있고, 그 각 요소에 대해서 무엇인가 처리해 나간다고 할 때, 이때 그 처리 코드는 데이터 구조를 표시하고 있는 클래스 안에 기술 해야한다.

하지만, 만약 그 처리가 여러 종류라면 새로운 처리가 필요할 때마다 데이터 구조의 클래스를 수정해야 한다.

즉, 다음과 같다.

  • 요소들의 클래스를 변경하지 않고 새로운 직업을 추가할 수 있게 해주는 디자인 패턴
  • 객체 구조를 변경하지 않고도 새로운 작업을 쉽게 추가할 수 있도록 한다.

Visitor 패턴은 객체의 구조는 안정적이지만, 객체에 수행할 작업이 자주 바뀌는 상황에서 유용하게 사용할 수 있는 디자인 패턴입니다.

Visitor 패턴을 사용하면 좋은 경우

객체 구조가 자주 변경되지 않는 경우: 객체 구조, 즉 상태 변수들이 변화가 적을 때 유용합니다.

객체 구조에 여러 다른 연산이 필요한 경우: 각 연산을 객체에 직접 추가하는 대신, Visitor 패턴을 사용하면 각 연산을 별도의 Visitor 클래스로 캡슐화하여 관리할 수 있습니다.

다양한 연산이 객체의 타입에 따라 달라지는 경우: Visitor 패턴을 사용하면 각 타입에 맞는 연산을 쉽게 정의할 수 있습니다.

클래스가 많고 연산이 많아질 때: 클래스와 연산의 수가 많아질수록 각 클래스에 연산을 추가하는 방식은 유지보수와 확장성 측면에서 어려움이 있습니다. Visitor 패턴을 사용하면 연산을 독립적인 클래스로 분리하여 관리할 수 있으므로 이러한 문제를 해결할 수 있습니다.

Visitor 패턴의 장점

객체 구조와 연산의 분리: 객체 구조를 수정하지 않고도 새로운 연산을 추가하거나 기존 연산을 변경할 수 있습니다. 이는 시스템의 유연성을 높이고, 개방/폐쇄 원칙(OCP)을 준수하는 데 도움을 줍니다.
각 타입별 연산의 명확한 정의: 각 Visitor 클래스는 특정 타입의 객체에 대한 연산만을 담당하므로 코드의 가독성과 유지보수성을 향상시킵니다.

코드 중복 감소: 여러 클래스에 걸쳐 동일한 연산을 수행해야 할 때, Visitor 패턴을 사용하면 중복 코드를 줄일 수 있습니다.

Visitor 패턴의 단점

객체 구조 변경의 어려움: 객체 구조가 변경될 경우, 모든 Visitor 클래스를 수정해야 할 수 있습니다.

복잡성 증가: Visitor 패턴을 도입하면 클래스의 수가 늘어나고 코드 구조가 복잡해질 수 있습니다.

결론

Visitor 패턴은 객체 구조와 연산을 분리하여 시스템의 유연성과 확장성을 높이는 데 유용한 디자인 패턴입니다. 하지만 객체 구조 변경의 어려움과 복잡성 증가라는 단점도 있으므로, 상황에 따라 적절하게 사용해야 합니다.

2. 알고리즘을 Object structure로부터 분리하는 방법

3. 예시

도형을 그려보자. 단, visitor pattern을 이용하라.

# Element Interface
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def accept(self, visitor: 'Visitor'):
        pass

# Concrete Elements
class Circle(Shape):
    def accept(self, visitor: 'Visitor'):
        visitor.visit_circle(self)

    def draw(self):
        print("Drawing a Circle")

class Rectangle(Shape):
    def accept(self, visitor: 'Visitor'):
        visitor.visit_rectangle(self)

    def draw(self):
        print("Drawing a Rectangle")

class Triangle(Shape):
    def accept(self, visitor: 'Visitor'):
        visitor.visit_triangle(self)

    def draw(self):
        print("Drawing a Triangle")

# Visitor Interface
from abc import ABC, abstractmethod

class Visitor(ABC):
    @abstractmethod
    def visit_circle(self, element: 'Circle'):
        pass

    @abstractmethod
    def visit_rectangle(self, element: 'Rectangle'):
        pass

    @abstractmethod
    def visit_triangle(self, element: 'Triangle'):
        pass

# Concrete Visitors
class AreaCalculator(Visitor):
    def visit_circle(self, element: 'Circle'):
        print("Calculating area for Circle")

    def visit_rectangle(self, element: 'Rectangle'):
        print("Calculating area for Rectangle")

    def visit_triangle(self, element: 'Triangle'):
        print("Calculating area for Triangle")

class PerimeterCalculator(Visitor):
    def visit_circle(self, element: 'Circle'):
        print("Calculating perimeter for Circle")

    def visit_rectangle(self, element: 'Rectangle'):
        print("Calculating perimeter for Rectangle")

    def visit_triangle(self, element: 'Triangle'):
        print("Calculating perimeter for Triangle")

# Client Code
if __name__ == "__main__":

    shapes = [Circle(), Rectangle(), Triangle()]

    area_calculator = AreaCalculator()
    perimeter_calculator = PerimeterCalculator()

    print("Calculating Areas:")
    for shape in shapes:
        shape.accept(area_calculator)

    print("\nCalculating Perimeters:")
    for shape in shapes:
        shape.accept(perimeter_calculator)

profile
마약같은 코딩, 마약같은 코딩러

0개의 댓글