iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 7
0

今天要和大家介紹 Observer Pattern,又稱作 Publish-Subscribe Pattern。簡單來說,Observer Pattern 就是事件發佈者 Subject (Publisher) 與事件觀察者 Observer (Subscriber) 兩種角色的結合,目的是處理一個主題物件(事件發佈者)對應多個觀察者物件之間的連動關係。就像你在臉書上追蹤了一個粉絲專頁,只要你(事件觀察者)還在追蹤該粉專(事件發佈者),一旦這個粉專有新的貼文或活動消息,你就會接收到通知;假如有一天你決定取消追蹤這個粉專,那你便不再是這個粉專的觀察者,即便這個粉專再發佈新的內容,你也不會再收到它的任何通知。一個粉絲專頁可以有眾多的觀察者,一有新的內容便非常有效率地發佈給所有訂閱自己動態的觀察者們。而對觀察者來說較為麻煩的是,除了自己真正在乎的資訊,還可能接收到許多無用的通知。

在實作 Observer Pattern 時,我們定義 Subject 與 Observer 的介面,讓遵循 Observer 介面的觀察者們透過 Subject 的介面將自己註冊為觀察者,以及將自己從觀察者名單移除。所有要成為觀察者的物件都必須遵循 Observer 介面,而這個介面只有一個最主要的方法 update(),就是 Subject 發生狀態改變、發送通知時,連帶跟著更新自身的狀態。

Observer Pattern 使用時機和優缺點

Observer Pattern 定義了一種一對多的依賴關係,讓一個或多個觀察者物件監聽其所關心的主題物件。當這個主題物件發生狀態改變時,會對所有的觀察者發送通知。這樣的設計模式解決了一個主題物件與眾多觀察者之間的溝通問題,當你需要實現這種一對多的溝通時,就是它大顯身手的時機。
使用 Observer Pattern 的優點是讓大量多組的對象在較少的依賴下實現合作,降低耦合。發佈通知的物件並不需要知道誰是他的觀察者,或這些觀察者的任何資訊,只知道他的觀察者都遵循 Observer 的共同介面。缺點是主題物件一有狀態改變便會向眾多的觀察者發送通知,可能會讓觀察者接收到遠多於有用的通知。

Observer Pattern 程式範例

假設今天我們有兩個時鐘錶面:一個類比時鐘與一個數位時鐘,這兩個時鐘作為觀察者,透過訂閱 ClockTimer 這個主題物件的時間狀態來每秒更新自己呈現的時間資訊。

以 Swift 為例,我們先來定義發佈通知的 Subject 及接收通知的 Observer 分別需要執行的介面。作為 Subject 必須執行三個動作:

  • 註冊新的 Observer,讓它開始接收狀態改變的通知
  • 移除不想繼續接收通知的既有 Observer
  • 自身狀態發生改變時,對所有 Observer 發佈通知

在 Observer 這邊則是需要在收到來自 Subject 的通知時更新手上的資訊。

protocol Subject {
    func register(observer: Observer)
    func remove(observer: Observer)
    func notifyObservers()
}

protocol Observer: AnyObject {
    func update(hour: Int, minute: Int, second: Int)
}

在主題物件方面,我們讓這個 ClockTimer 遵從 Subject 的介面,在這裡實作註冊觀察者、移除觀察者,以及通知所有觀察者的方法。在每秒的 tick() 讓時間狀態發生改變時呼叫notifyObservers(),通知所有的觀察者更新呈現的資訊。

class ClockTimer: Subject {
    private var observers = [Observer]()
    private var hour: Int
    private var minute: Int
    private var second: Int

    init(hour: Int = 0, minute: Int = 0, second: Int = 0) {
        self.hour = hour
        self.minute = minute
        self.second = second
    }

    func register(observer: Observer) {
        observers.append(observer)
    }

    func remove(observer: Observer) {
        observers = observers.filter{ $0 !== observer }
    }

    func notifyObservers() {
        for observer in observers {
            observer.update(hour: hour, minute: minute, second: second)
        }
    }

    func tick(hour: Int, minute: Int, second: Int) {
        // update internal time-keeping state
        self.hour = hour
        self.minute = minute
        self.second = second
        notifyObservers()
    }

}

另一方面,任何物件只要遵從 Observer 的介面,就能成為觀察者對。我們現在定義類比時鐘與 數位時鐘兩個物件皆遵從 Observer 介面,並在實例化的時候將自己註冊為 clockTimer 這個主題物件的觀察者。

class AnalogClock: Observer {
    private var clockTimer: Subject

    init(hour: Int = 0, minute: Int = 0, second: Int = 0, clockTimer: Subject) {
        self.clockTimer = clockTimer
        clockTimer.register(observer: self)
    }

    func update(hour: Int, minute: Int, second: Int) {
        print("Analog Clock time display \(hour):\(minute):\(second)")
    }

}

class DigitalClock: Observer {
    private var clockTimer: Subject

    init(hour: Int = 0, minute: Int = 0, second: Int = 0, clockTimer: Subject) {
        self.clockTimer = clockTimer
        clockTimer.register(observer: self)
    }

    func update(hour: Int, minute: Int, second: Int) {
        print("Digital Clock time display \(hour):\(minute):\(second)")
    }

}

現在來試試,讓類比時鐘、數位時鐘成為觀察者並改變時間的狀態,接著將數位時鐘從觀察者行列移除,再做時間狀態的更新。

class TimeCenter {
    static func main() {
    let clockTimer = ClockTimer()
    let analogClock = AnalogClock(clockTimer: clockTimer)
    let digitalClock = DigitalClock(clockTimer: clockTimer)

    clockTimer.tick(hour: 12, minute: 10, second: 10)
    clockTimer.tick(hour: 12, minute: 10, second: 11)
    print("---remove digital clock as observer---")
    clockTimer.remove(observer: digitalClock)
    clockTimer.tick(hour: 12, minute: 10, second: 12)
    }
}

TimeCenter.main()

我們看到 print 出的結果如下,數位時鐘離開觀察者行列後,在 clockTimer 的時間狀態改變時便不會再收到通知。

Analog Clock time display 12:10:10
Digital Clock time display 12:10:10
Analog Clock time display 12:10:11
Digital Clock time display 12:10:11
---remove digital clock as observer---
Analog Clock time display 12:10:12

Observer Pattern 被廣泛地使用,各種語言框架內皆有應用。以 Cocoa Touch 的框架來說,KVO 和 Notification Center 都實現了 Observer Pattern。以上就是今天的 Observer Pattern,感謝你的閱讀,我們明天再繼續設計模式的學習之路。

作者:Jo


上一篇
[Design Pattern] Decorator 裝飾者模式
下一篇
[Design Pattern] Adapter 配接器模式
系列文
什麼?又是/不只是 Design Patterns!?32

尚未有邦友留言

立即登入留言