티스토리 뷰

반응형

안녕하세요 Pingu입니다!🐧

 

지난 글에서는 행동 패턴 중 하나인 Template Method Pattern(템플릿 메서드)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Visitor Pattern에 대해 알아보도록 하겠습니다.

방문자 패턴이란?

Visitor Pattern(방문자)은 알고리즘을 작동하는 객체에서 분리할 수 있는 디자인 패턴입니다. Visotor를 사용하면 작업이 수행되는 객체의 클래스를 변경하지 않고도 새로운 알고리즘을 정의할 수 있게 됩니다.

  • Visitor
    • Concrete Element를 매개변수로 사용할 수 있는 visit 메서드들을 정의합니다.
    • 언어가 오버 로딩을 지원하는 경우 메서드 이름이 같을 순 있지만 매개변수의 타입은 달라야 합니다.
  • Concrete Visitor
    • 서로 다른 Concrete Element에 맞게 메서드를 구현합니다.
  • Element
    • Visitor를 Accept 하는 메서드를 정의합니다. Accept 메서드는 Visitor 객체를 매개변수로 사용해야 합니다.
  • Concrete Element
    • Accept 메서드를 구현합니다.
    • Accept 메서드는 Element 객체에서 사용할 적절한 Visitor의 메서드를 호출하는 역할을 합니다.
  • Client
    • 컬렉션이나 트리와 같은 자료구조를 나타냅니다.

방문자 패턴은 언제 쓰나요?

객체들이 복잡한 구조(트리 등)의 모든 Element에 대해 작업을 수행해야 하는 경우 Visitor Pattern을 사용하면 좋습니다. 또한 클래스 계층 구조에서 몇몇 단계에 있는 클래스에만 의미 있는 메서드가 존재할 때도 사용하면 좋습니다. 이를 위해 방문자 패턴은 새로운 메서드를 기존 클래스에 새로 만들기보다는 Visitor라는 별도의 클래스에 만들게 됩니다. 이러한 메서드를 사용하는 객체는 Visitor 객체의 메서드 중 하나를 선택해서 사용하게 됩니다.

 

예를 들어 알람을 보내는 클라이언트가 있다고 가정해보겠습니다. 알람의 종류로는 email, sms, push가 있다고 가정해볼게요. 낮과 밤 모두 알람을 전송하다 보니 사용자들이 화를 내기 시작합니다. 그래서 앞으로는 밤에는 push 알람만 보내기로 결정하게 됩니다. 이러한 상황에서 클라이언트가 알람을 보낼 때 상황에 맞게 적절한 알람을 보내기 위해선 어떻게 해야 할까요?

 

Visitor Pattern을 사용하지 않는다면 시간에 따라 if문과 같은 조건문을 사용해서 알람을 보낼 방법을 찾고 해당 방법으로 알람을 전송해야 할 것 같습니다. 이렇게 만들게 되면 새로운 유형의 알람이 생기면 코드 전체를 수정해야 하는 불편함이 생깁니다.

 

하지만 Visitor Pattern을 사용하여 클라이언트가 직접 알람을 보낼 객체를 찾는 대신 Visitor 객체가 알람을 보내기에 적절한지를 알려주면 어떨까요? 즉 Visitor 객체가 허용한 객체만 알람을 보내주도록 하는 것이죠. 이렇게 하면 새로운 타입의 알람이 만들어지더라도 Visitor부분만 수정해주면 되기 때문에 클라이언트는 그냥 계속 동일하게 사용할 수 있게 됩니다. 

방문자 패턴의 결과

장점

  • Open / Closed Principle(개방 / 폐쇄 원칙)을 준수합니다. 클래스를 변경하지 않고도 다른 클래스의 객체에 사용 할 수 있는 메서드를 만들 수 있습니다.
  • Single Responsibility Principle(단일 책임 원칙)을 준수합니다. 동일한 동작을 하는 여러 개의 메서드를 동일한 클래스에 만들 수 있습니다.
  • Visitor 객체는 다양한 객체로 작업하는 동안 다양한 정보를 모을 수 있습니다.

단점

  • 클래스가 Element 계층 구조에 추가, 제거될 때마다 모든 Visitor를 업데이트해줘야 합니다.
  • Visitor 객체는 작업하는 Element의 private 프로퍼티, 메서드에 접근 권한이 없을 수 있습니다.

예제

그럼 이제 Visitor Pattern을 Swift로 구현해보도록 하겠습니다.

아까 예로 들었던 메시지 알림 프로그램을 한 번 만들어볼게요.

일단 Element를 만들어줍니다.

// Element Interface
protocol Notification {
    var notificationType: String { get }
    func accept(visitor: NotificationPolicy) -> Bool
}

그런 다음에는 Concrete Element들을 만들어줍니다. 지금 예에서는 Email, SMS, Push가 있겠죠?

// Concrete Element
class Email: Notification {

    let emailAddressOfSender: String
    var notificationType: String {
        return "Email"
    }
    init(emailAddressOfSender: String) {
        self.emailAddressOfSender = emailAddressOfSender
    }
    
    func accept(visitor: NotificationPolicy) -> Bool {
        visitor.isTurnedOn(for: self)
    }
}

// Concrete Element
class SMS: Notification {
    
    let phoneNumberOfSender: String
    var notificationType: String {
        return "SMS"
    }
    init(phoneNumberOfSender: String) {
        self.phoneNumberOfSender = phoneNumberOfSender
    }
    
    func accept(visitor: NotificationPolicy) -> Bool {
        visitor.isTurnedOn(for: self)
    }
}

// Concrete Element
class Push: Notification {
    
    let userIdOfSender: String
    var notificationType: String {
        return "Push"
    }
    init(userIdOfSender: String) {
        self.userIdOfSender = userIdOfSender
    }
    
    func accept(visitor: NotificationPolicy) -> Bool {
        visitor.isTurnedOn(for: self)
    }
}

이젠 이러한 Element들이 보내지기에 적합한 시간인지를 알려주기 위한 Visitor를 정의합니다.

// Visitor Interface
protocol NotificationPolicy {
    var policyType: String { get }
    func isTurnedOn(for email: Email) -> Bool
    func isTurnedOn(for sms: SMS) -> Bool
    func isTurnedOn(for push: Push) -> Bool
}

그런 뒤 Concrete Visitor도 만들어 줍니다. 저는 밤, 낮 2개의 Visitor를 만들어줄게요.

// Concrete Visitor
class DayPolicyVisitor: NotificationPolicy {
    var policyType: String {
        return "Day Policy"
    }
    func isTurnedOn(for email: Email) -> Bool {
        return true
    }
    
    func isTurnedOn(for sms: SMS) -> Bool {
        return true
    }
    
    func isTurnedOn(for push: Push) -> Bool {
        return true
    }
}

// Concrete Visitor
class NightPolicyVisitor: NotificationPolicy {
    var policyType: String {
        return "Night Policy"
    }
    func isTurnedOn(for email: Email) -> Bool {
        return false
    }
    
    func isTurnedOn(for sms: SMS) -> Bool {
        return false
    }
    
    func isTurnedOn(for push: Push) -> Bool {
        return true
    }
}

즉 Concrete Element의 타입에 따라 Visitor객체가 현재 보낼 수 있는 알람인지 확인해줍니다.

 

마지막으로 이를 사용하는 Client를 만들어주면 됩니다.

class Client {
    func alert(notifications: [Notification], policy: NotificationPolicy) {
        print(policy.policyType)
        notifications.forEach { notitication in
            if notitication.accept(visitor: policy) {
                print("\(notitication.notificationType) notification send complete")
            } else {
                print("\(notitication.notificationType) notification can't send now")
            }
        }
        print("\n")
    }
}

 

바로 사용해보면 아래와 같아집니다.

원하는 대로 보내고 싶은 알람만 시간에 맞춰 보내진 것을 볼 수 있습니다.

즉 알람을 보낼 수 있는지에 대한 여부를 판별하는 알고리즘을 Visitor 객체에서 처리한 것 입니다.

 

이렇게 Visitor Pattern을 알아보고 간단하게 구현도 해봤습니다.

Visitor Pattern이 행동 패턴 중 가장 어려운 패턴이었던 거 같아요.

제가 이해한 게 맞는지도 의문이네요 ㅠ

혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.

 

전체 코드는 여기에서 볼 수 있습니다.

 

이번 Visitor Pattern을 끝으로 GoF의 모든 디자인 패턴을 공부했습니다.

가장 대표적인 디자인 패턴들을 공부하다 보니 실제 코드에서 사용 중인 패턴들도 보이기 시작한 거 같아요.

더 많이 공부해서 자유롭게 패턴들을 사용할 실력이 되고 싶습니다.

 

감사합니다.😊

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함