Gof의 디자인 패턴 - 방문자 패턴

Groot·2024년 9월 1일
0

TIL

목록 보기
153/153
post-thumbnail

방문자 패턴

  • 객체의 구조를 이루는 원소에 대해 수행할 연산을 표현한다.
  • 연산을 적용할 원소의 클래스를 변경하지 않고도 새로운 연산을 정의할 수 있게 한다.

활용성

  • 비지터 객체는 복잡한 객체 구조​(예: 객체 트리)​의 모든 요소에 대해 작업을 수행해야 할 때 사용
  • 각각 특징이 있고, 관련되지 않은 많은 연산이 한 객체 구조에 속해있는 객체들에 대해 수행될 필요가 있으며, 연산으로 클래스들을 더럽히고 싶지 않을때
  • 객체 구조를 정의한 클래스는 거의 변하지 않지만, 전체 구조에 덜쳐 새로운 연산을 추가하고 싶을 때

구조

요소

  • Visitor: 객체 구조내에 있는 각 ConcreteElement 클래스를 위한 Visit() 연산을 선언한다.
    • 연산의 이름과 인터페이스 형태는 Visit() 요청을 방문자에게 보내는 클래스를 식별한다.
    • 방문자는 방문된 원소의 구체 클래스를 결정할 수 있다.
    • 방문자는 그 원소가 제공하는 인터페이스를 통해 원소에 접근할 수 있다.
  • ConcreteVisitor: Visit 클래스에 선언된 연산을 구현한다. 각 연산은 구조 내에 있는 객체의 대응 클래스에 정의된 일부 알고리즘을 구현한다.
  • Element: 방문자를 인자로 받아들이는 Accept() 연산을 정의한다.
  • ConcreteElement: 인자로 방문자 객체를 받아들이는 Accept() 연산을 구현
  • OnjectStructure: 객체 구조 내의 원소들을 나열할 수 있다.
    • 방문자가 이 원소에 접근하게 하는 상위 수준 인터페이스를 제공
    • 리스트나 집합 등 컬렉션일 수 있다.

협력 방법

  • 방문자 패턴을 사용하는 사용자는 ConcreteVisitor 클래스의 객체를 생성하고 객체 구조에 따라서 각 원소를 방문하며 순회한다.

  • 방문자가 구성 원소들을 방문할 때, 구성 원소는 해당 클래스의 Visitor 연산을 호출한다. 이 원소들은 자신을 Visitor 연산에 필요한 인자로 제공하여 방문자 자신의 상태에 접근할 수 있도록 한다.

  • 이중 디스패치 (Double Dispatch)를 사용하는 패턴

    • 이중 디스패치는 메서드 호출 시 두 객체의 런타임 타입에 따라 호출되는 메서드가 결정되는 방식.
    • 이중 디스패치는 주로 복잡한 객체 간의 상호작용을 처리할 때 사용.
    • 메서드 호출 시 두 객체의 타입을 모두 고려해 어떤 메서드를 실행할지 결정
  • 방문자 패턴과 이중 디스패치의 관계
    방문자 패턴에서 이중 디스패치가 중요한 이유는, 방문자 패턴의 핵심이 객체의 타입에 따라 적절한 연산을 수행하는 것이기 때문. 이중 디스패치를 통해, 방문자 패턴은 다음을 가능하게 한다.

    - 첫 번째 디스패치: 객체 구조에서 요소가 accept(visitor:) 메서드를 호출할 때, 호출된 요소 객체의 런타임 타입에 따라 적절한 방문자 메서드가 호출.
    - 두 번째 디스패치: accept(visitor:) 메서드 내에서, 전달된 방문자 객체의 타입에 따라 적절한 방문 메서드가 다시 호출.

장점

  • 다른 클래스를 변경하지 않으면서 해당 클래스의 객체와 작동할 수 있는 새로운 행동을 도입
  • 같은 행동의 여러 버전을 같은 클래스로 이동
  • 비지터 객체는 다양한 객체들과 작업하면서 유용한 정보를 축적할 수 있다. 이것은 객체 트리와 같은 복잡한 객체 구조를 순회하여 이 구조의 각 객체에 비지터 패턴을 적용하려는 경우에 유용할 수 있다.

단점

클래스가 요소 계층구조에 추가되거나 제거될 때마다 모든 비지터를 업데이트해야 함.

  • 비지터들은 함께 작업해야 하는 요소들의 비공개 필드들 및 메서드들에 접근하기 위해 필요한 권한이 부족할 수 있다.

예시 코드

// Visitor 프로토콜 정의
protocol Visitor {
    func visitCircle(_ circle: Circle)
    func visitRectangle(_ rectangle: Rectangle)
}

// Shape 프로토콜 정의
protocol Shape {
    func accept(visitor: Visitor)
}

// 구체적인 도형 클래스들
class Circle: Shape {
    func accept(visitor: Visitor) {
        visitor.visitCircle(self)
    }
    
    // Circle의 구체적인 로직들 (예: 반지름, 면적 계산 등)
    var radius: Double
    
    init(radius: Double) {
        self.radius = radius
    }
}

class Rectangle: Shape {
    func accept(visitor: Visitor) {
        visitor.visitRectangle(self)
    }
    
    // Rectangle의 구체적인 로직들 (예: 너비, 높이, 면적 계산 등)
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
}

// 구체적인 방문자 클래스
class AreaCalculator: Visitor {
    func visitCircle(_ circle: Circle) {
        let area = 3.14 * circle.radius * circle.radius
        print("Area of Circle: \(area)")
    }
    
    func visitRectangle(_ rectangle: Rectangle) {
        let area = rectangle.width * rectangle.height
        print("Area of Rectangle: \(area)")
    }
}

// 사용 예시
let circle = Circle(radius: 5.0)
let rectangle = Rectangle(width: 4.0, height: 6.0)

let calculator = AreaCalculator()

circle.accept(visitor: calculator)  // Output: Area of Circle: 78.5
rectangle.accept(visitor: calculator)  // Output: Area of Rectangle: 24.0

참고

profile
I Am Groot

0개의 댓글