데이터구조와 처리를 분리하는 패턴
데이터 구조 안에 많은 요소가 저장되어 있고, 그 각 요소에 대해서 무엇인가 처리해 나간다고 할 때, 이때 그 처리 코드는 데이터 구조를 표시하고 있는 클래스 안에 기술 해야한다.
하지만, 만약 그 처리가 여러 종류라면 새로운 처리가 필요할 때마다 데이터 구조의 클래스를 수정해야 한다.
즉, 다음과 같다.
- 요소들의 클래스를 변경하지 않고 새로운 직업을 추가할 수 있게 해주는 디자인 패턴
- 객체 구조를 변경하지 않고도 새로운 작업을 쉽게 추가할 수 있도록 한다.
Visitor 패턴은 객체의 구조는 안정적이지만, 객체에 수행할 작업이 자주 바뀌는 상황에서 유용하게 사용할 수 있는 디자인 패턴입니다.
Visitor 패턴을 사용하면 좋은 경우
객체 구조가 자주 변경되지 않는 경우: 객체 구조, 즉 상태 변수들이 변화가 적을 때 유용합니다.
객체 구조에 여러 다른 연산이 필요한 경우: 각 연산을 객체에 직접 추가하는 대신, Visitor 패턴을 사용하면 각 연산을 별도의 Visitor 클래스로 캡슐화하여 관리할 수 있습니다.
다양한 연산이 객체의 타입에 따라 달라지는 경우: Visitor 패턴을 사용하면 각 타입에 맞는 연산을 쉽게 정의할 수 있습니다.
클래스가 많고 연산이 많아질 때: 클래스와 연산의 수가 많아질수록 각 클래스에 연산을 추가하는 방식은 유지보수와 확장성 측면에서 어려움이 있습니다. Visitor 패턴을 사용하면 연산을 독립적인 클래스로 분리하여 관리할 수 있으므로 이러한 문제를 해결할 수 있습니다.
객체 구조와 연산의 분리: 객체 구조를 수정하지 않고도 새로운 연산을 추가하거나 기존 연산을 변경할 수 있습니다. 이는 시스템의 유연성을 높이고, 개방/폐쇄 원칙(OCP)을 준수하는 데 도움을 줍니다.
각 타입별 연산의 명확한 정의: 각 Visitor 클래스는 특정 타입의 객체에 대한 연산만을 담당하므로 코드의 가독성과 유지보수성을 향상시킵니다.
코드 중복 감소: 여러 클래스에 걸쳐 동일한 연산을 수행해야 할 때, Visitor 패턴을 사용하면 중복 코드를 줄일 수 있습니다.
객체 구조 변경의 어려움: 객체 구조가 변경될 경우, 모든 Visitor 클래스를 수정해야 할 수 있습니다.
복잡성 증가: Visitor 패턴을 도입하면 클래스의 수가 늘어나고 코드 구조가 복잡해질 수 있습니다.
Visitor 패턴은 객체 구조와 연산을 분리하여 시스템의 유연성과 확장성을 높이는 데 유용한 디자인 패턴입니다. 하지만 객체 구조 변경의 어려움과 복잡성 증가라는 단점도 있으므로, 상황에 따라 적절하게 사용해야 합니다.


도형을 그려보자. 단, 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)