티스토리 뷰

반응형

Head First - Design Patterns 의 템플릿 메소드 패턴 기반으로 작성하였습니다. 자세한 설명은 도서를 참고해주세요.

비지터 패턴에 대해서 이번 포스트에서는 알아보겠습니다.구조 자체를 변경하지 않으면서 복합 객체 구조에 새로운 기능을 추가할 방법은 없을까요? 이럴 때 생각해보면 좋을 패턴은 비지터 패턴입니다.

 

비지터 패턴이란?

다양한 객체에 새로운 기능을 추가해야하는데 캡슐화가 별로 중요하지 않는 경우 비지터 패턴을 사용합니다.

비지터 패턴은 트래버서객체와 함께 구현하는데요. 트래버서는 컴포지트 패턴을 사용하는 경우 객체 내에 속해 있는 모든 객체들에 접근하는 걸 도와주는 역할을 합니다. 비지터 객체에서 복합 객체 내의 모든 객체들에 대해서 원하는 작업을 할 수 있는 환경을 만들어줍니다.

비지터는 각각의 상태를 모두 가져온 후 클라이언트에서 요구하는 다양한 작업을 처리할 수 있게됩니다.

장점
구조 자체를 변경시키지 않으면서 복합 객체 구조에 새로운 기능을 추가할 수 있습니다. 비지터 수행하는 기능과 관련 코드를 한 곳에 집중시킬 수 있습니다.

단점
비지터를 사용 시 캡슐화는 깨지게 됩니다. 트래버서가 있기 때문에 복합 구조를 변경이 더 어려워질 수 있습니다.

 

코드를 통한 비지터 패턴 알아보기

예제 코드는 wikipedia 의 예제 코드를 활용하였습니다.

간단한 코드를 통한 비지터 패턴에 대해서 알아보겠습니다.
자동차 부품은 엔진, 차본체, 바퀴가 있습니다. 이 모든 부품들에 접근하는 로직을 만든다고 가정합니다.

internal interface CarElement {
    fun accept(visitor: CarElementVisitor)
}

컴포지트 패턴을 활용하기 위해 인터페이스를 생성합니다.

interface CarElementVisitor {
    fun visit(wheel: Wheel)
    fun visit(engine: Engine)
    fun visit(body: Body)
    fun visit(car: Car)
}

비지터 객체에서 처리 할 수 있도록 인터페이스를 생성합니다. CarElement에서 Accept(CarElementVisitor) Fuction 호출로 비지터 객체어 접근할 수 있도록 구성합니다.

class Engine : CarElement {
    override fun accept(visitor: CarElementVisitor) {
        visitor.visit(this)
    }
}
class Wheel(val name: String) : CarElement {
    override fun accept(visitor: CarElementVisitor) {
        visitor.visit(this)
    }
}
class Body  : CarElement {
    override fun accept(visitor: CarElementVisitor) {
        visitor.visit(this)
    }
}
class Car : CarElement {
  private var elements = ArrayList<CarElement>()

  init {
   this.elements.apply {
         this.add(Wheel("front left"))
         this.add(Wheel("front right"))
         this.add(Wheel("back left"))
         this.add(Wheel("back right"))
	}
}

  override fun accept(visitor: CarElementVisitor) {
      for(element in elements){
          element.accept(visitor)
      }
      visitor.visit(this)
  }
}

부품들은 CarElement 인터페이스를 참조하여 컴포지트 패턴을 활용 할 수 있도록 설정하였습니다. 또한 Car 클래스는 Car에 필요한 부품을 List로 갖고 있으며, Car 클래스는 CarElement을 참조하여 구성하고 있습니다.

class CarElementPrintVisitor : CarElementVisitor{
    override fun visit(wheel: Wheel) {
        println("Visiting ${wheel.name} wheel")
    }

    override fun visit(engine: Engine) {
        println("Visiting engine")
    }

    override fun visit(body: Body) {
        println("Visiting body")
    }

    override fun visit(car: Car) {
        println("Visiting Car")
    }
}

CarElementPrintVisitor 클래스는 CarElement에서 Accept(CarElementVisitor)을 호출 시 동작할 동작을 구성 합니다.

class CarElementDoVisitor : CarElementVisitor {
    override fun visit(wheel: Wheel) {
       println("Kicking my ${wheel.name} wheel")
    }

    override fun visit(engine: Engine) {
       println("Starting my engine")
    }

    override fun visit(body: Body) {
       println("Moving my body")
    }

    override fun visit(car: Car) {
       println("Starting my Car")
    }
}

CarElementPrintVisitor 클래스와 동일하게  CarElementDoVisitor 클래스는  CarElement에서 Accept(CarElementVisitor)을 호출 시 동작할 동작을 구성 합니다. 해당 클래스에서는 자동차 상태를 체크하고 자동차 시동을 겁니다.

 

마무리

비지터 패턴에 대해서 알아봤습니다. 컴포지트 패턴을 활용한 비지터 패턴은 복합 객체의 원하는 작업을 할 수 있도록 만들어 줄 수 있는 장점이 있지만 트래버서 때문에 복합객체의 변경이 어려울 수도 있습니다.  비지터 패턴의 장단점을 고려한 개발 하시길 응원합니다.

 

참고

https://ko.wikipedia.org/wiki/비지터_패턴
https://ko.wikipedia.org/wiki/컴포지트_패턴

반응형

'프로그래밍 > Design Patterns' 카테고리의 다른 글

프로토타입(Prototype) 패턴  (0) 2022.02.10
메멘토(Memento) 패턴  (0) 2022.02.09
미디에이터(Mediator) 패턴  (0) 2022.02.08
인터프리터(Interpreter) 패턴  (0) 2022.02.02
플라이웨이트(Flyweight) 패턴  (0) 2022.01.26
댓글