Observer Pattern

pcsoyeon·2021년 11월 23일
0

What is Observer Pattern?

어떤 클래스에 변화가 일어났을 때, 이를 감지하여 다른 클래스에 통보하는 패턴으로 한 객체의 상태가 변경될 때마다 그에 상응하는 액션이 필요할 때 옵저버 패턴을 사용하면 된다.

-> 값이 변경됨에 따라서 피드백이 바로 나와야 하는 프로그램일 경우 좋다.

Observer Pattern

- One of the Behavioural Patterns

Behavioural Patterns (= 행동 패턴)
객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
한 객체가 수행할 수 없는 작업을 여러 개의 객체로 분배하며 객체 사이의 결합을 최소화하며 패턴을 주로 클래스에 적용하는지 아니면 객체에 적용하는지에 따라 구분된다.

- It defines one-many relationship. So when one object changes its state, its dependents are notified.

한 객체의 상태 변화에 따라서 다른 객체의 상태도 바뀌도록 1대 N객체 간의 의존 관계를 구성하는 디자인 패턴이다.

- Pub-Sub Pattern

Pub-Sub Pattern
알림을 수신하려는 객체(Subscriber)와 이벤트 발생 객체(Publisher)사이에 위치하는 Event Channel을 둔다. Publisher는 Subscriber를 모르는 상태로 이벤트 발생 시에 Event Channel에게 메시지를 넘겨주고 Subscriber 역시 Publisher에 대한 정보 없이 자신에게 맞는 메시지만을 전송 받는다. (응답과 상관없이 중간 객체를 건너서 가기 때문에 비동기 방식이다.)

Observer Pattern == Pub-Sub Pattern
옵저버 패턴의 경우 알림을 수신하는 Observer가 이벤트를 실행하는 주체 Publisher에 등록해야 한다. 그래서 한 객체의 상태가 바뀌면, 그 객체와 의존 관계를 맺는 다른 객체들에게 알림이 가고 자동으로 정보가 갱신되는 1대 N의 관계를 성립한다.
-> 객체 사이 강한 관계를 맺고 있다. 이와 다르게 Pub-Sub 패턴은 보다 느슨한 관계를 갖는다.

즉, 두가지 패턴의 가장 큰 차이는 중간에 Message Broker 또는 Event Bus가 존재하는지 여부이다.

Observer 패턴은 Observer와 Subject가 서로를 인지하지만 Pub-Sub패턴의 경우 서로를 전혀 몰라도 상관없다.

Subject에 Observer를 등록하고 Subject가 Observer에게 직접 알려야 한다.

그러나, Pub-Sub패턴의 경우 Publisher가 Subscriber의 위치나 존재를 알 필요없이 Message Queue와 같은 Broker역할을 하는 중간 지점에 메시지를 던져 놓기만 하면 된다. Subscriber 역시 Publisher의 위치나 존재를 알 필요없이 Broker에 할당된 작업만 모니터링하다 할당 받아 작업하면 되기 때문에 Publisher와 Subscriber가 서로 알 필요가 없다.

그래서 Observer패턴에 비해 Pub-Sub패턴이 더 결합도가 낮다. (의존성이 낮다.)

Observer패턴은 대부분 동기(synchronous) 방식으로 동작하나 Pub-Sub패턴은 대부분 비동기(asynchronous) 방식으로 동작한다.
이유는 Broker로 MessageQueue를 주로 사용하므로

Observer패턴은 단일 도메인 하에서 구현되어야 하나 Pub-Sub패턴은 크로스 도메인 상황에서도 구현 가능하다.
위와 마찬가지로 Broker라는 중간 매개체가 있어 앱의 도메인이 다르더라도 MessageQueue(Broker)에 접근만 가능하다면 처리가 가능하기 때문이다.

Observer와 비교했을 때의 장/단점

  • 장점 : 직접적인 관계가 아니기 때문에 코드 관리, 재사용성, 안정성이 높다. Publisher 관점에서 Subscriber들을 일일히 관리하지 않는다.
  • 단점 : 미들웨어를 통하기 때문에 의도한대로 메시지를 전달하지 못할 수도 있다.

Case of Observer Pattern

Apple Store가 있고 그 주변으로 총 5명의 구매자가 있다고 할 때, 그 중 한명(A)이 아이폰13을 구매하고 싶다고 가정해보자. A는 매번 애플 스토어에 방문해서 아이폰13의 재고가 있는지 확인해야 한다. 이 때, 한가지 방법은 애플 스토어에서 아이폰13의 재고가 있을 때 주변의 구매자에게 메일을 보내는 것이다. 이 상황에서 A에게는 해당 메시지가 적합한 피드백일 수 있지만, A를 제외한 나머지 4명에게는 적절한 피드백이 아니다. 그러므로 이는 좋은 접근이라고 할 수 없다.

아이폰13을 구매하고 싶은 구매자에게만 해당 메일을 보내는 것이 좋은 접근이다. 그래서 특정한 이벤트에 대해 subscribing을 하고 있는 객체에게만 이벤트에 대한 반응을 보이고 이를 해당 객체만 알아채도록 하는 것이 좋은 방법이다. 이러한 방식을 옵저버 패턴이라고 할 수 있다.
(뉴스 구독도 비슷한 방식으로 생각할 수 있다.)

Basic Structure of Observer Pattern

Publisher

  • [] Observers
  • addObserver(), removeObserver(), notifyObservers()
  • Business Logic

옵저버 패턴을 구성하는 기본 구조 중 하나는 Publisher이다. 몇몇은 이를 Subject라고 부르기도 한다. 이것은 세가지 중요한 요소를 갖고 있다.

먼저 Observers 배열의 경우 옵저버들에 대한 referernce를 담고 있는 배열이다. 그리고 세가지 메소들이다. 특정 publisher에 대한 이벤트들을 알고 싶을 때 addObserver 메소드를 통해 추가하고 이에 대한 reference가 옵저버 배열에 담긴다. 그리고 만약 이벤트에 대한 반응을 멈추고 싶다면 removeObserver 메소드를 통해 배열에서 삭제되고 피드백을 주지 않을 수 있다.

마지막 business logic의 경우 특정 이벤트가 시작되었을 때 실행될 코드들이다.

Observer

  • Update()
    옵저버는 publisher의 이벤트에 관심이 있는 entity이다. 옵저버는 주로 여러개가 있다.

이런 옵저버가 addObserver 메소드를 통해서 특정 Publisher에 subscribe를 한다. 그리고 이벤트가 발생하면 Publisher에서 옵저버의 Update 메소드를 실행하기 위해 notifyObserver 메소드를 부른다.

Protocol (Observer Protocol)

  • Update()
    Publisher에서 어떻게 이런 옵저버들이 Update라고 불리는 메소드를 갖고 있음을 protocol을 통해 알 수 있다.

When should you use Observer Pattern?

Whenever multiple entities are interested in the changes of state of an object, you can use Observer Pattern.

Example - 구독 서비스

앞서서 말했던 예시와 비슷하게 구독 서비스의 경우, 옵저버 패턴을 사용하면 보다 효율적으로 관리할 수 있다.

구독자 3명에 대해서 이들을 Subscriber로 지정하고 하나의 서비스에 대해서 유저로 등록한 다음 (addObserver를 통해) 상황에 따라서 구독을 취소한 사용자의 경우 removeObserver를 이용하면 된다. 그리고 특정 이벤트를 받았을 때 구독한 유저들에게만 특정 피드백을 보내면 된다.

예를 들어, 미세먼지농도 알림 서비스를 생각해보자.

위에서 설명한 요소에 맞게 코드를 작성하면 다음과 같다.

protocol Observer: AnyObject {
    func update(_ temp: Float, density: Float)
}

옵저버 프로토콜을 만들어주고

protocol Observable {
    func addObserver(_ observer: Observer)
    func removeObserver(_ observer: Observer)
}

class Publisher: Observable {
    var observers = [Observer]()
    
    func addObserver(_ observer: Observer) {
        observers.append(observer)
    }
    
    func removeObserver(_ observer: Observer) {
        observers = observers.filter({ $0 !== observer })
    }
}
class DustMeter: Publisher {
    var temperature: Float = 0.0
    var density: Float = 0.0
    
    func notify() {
        for observer in observers {
            observer.update(temperature, density: density)
        }
    }
}

Publisher에서 옵저버를 등록, 제거, 변화에 대한 피드백을 구현한다.

class Subscriber: Observer {
    var name: String = ""
    
    init(name: String) {
        self.name = name
    }
    
    func update(_ temp: Float, density: Float) {
        print("\(name)님, 오늘의 온도는 \(temp)이고 미세먼지 농도는 \(density)이에요 😇")
    }
}

그리고 각 옵저버에 대해 특정 이벤트에 대한 Update 함수를 구체화한다.

class ViewController: UIViewController {
    
    // MARK: - Properties
    
    let dustMeter = DustMeter()
    
    // MARK: - Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 관찰자
        let user1 = Subscriber(name: "만수")
        let user2 = Subscriber(name: "소연")
        let _ = Subscriber(name: "만동")
        
        // 알림을 받을 유저 추가
        dustMeter.addObserver(user1)
        dustMeter.addObserver(user2)
        
        // 알림을 더이상 받지 않을 유저
        dustMeter.removeObserver(user1)
    }
    
    // MARK: - IB Actions
    
    @IBAction func touchUpButton(_ sender: Any) {
        dustMeter.temperature = 32
        dustMeter.density = 80
        
        dustMeter.notify()
    }
}

그리고 실제로 사용할 때는 위와 같이 사용할 수 있다.

profile
Slowly But Surely

0개의 댓글