您可於此 design_pattern repo 下載 Design Pattern 系列程式碼。

需求

我們的任務是設計一個 安全系統主機 (Panel),需求如下:

  • 主機負責監控不同的感測器,例如煙霧探測器或門窗感測器。
  • 當警報觸發時,主機需要通知所有已註冊的設備,例如平板、iOS 和 Android 手機。
  • 設備可以動態地加入或移除通知清單。

物件導向分析 (OOA)

理解需求後,讓我們來快速實作物件導向分析吧!

observer_pattern_uml_1

察覺 Forces

在未使用設計模式的情況下,我們可能面臨以下挑戰:

  1. 高耦合性 (High Coupling)

    • 如果主機直接與每一個設備互動,程式碼會變得難以維護,每次新增或移除設備都需要修改主機邏輯。
  2. 缺乏彈性 (Lack of Flexibility)

    • 新增設備需要修改現有程式碼,違反開放關閉原則 (OCP)。
  3. 通知不一致 (Inconsistent Notifications)

    • 當警報觸發時,難以確保每個設備都能正確接收到通知。

套用 Observer Pattern (Solution) 得到新的 Context (Resulting Context)

做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Observer Pattern 解決這個問題

先來看一下 Memento Pattern 的 UML

observer_pattern_uml_2

觀察者模式提供了一個一對多的通知機制,當主機的狀態改變時,會自動通知所有已訂閱的設備。

  • Subject (主體):安全系統主機,負責管理所有設備並在警報觸發時發送通知。
  • Observer (觀察者):設備,例如平板、iOS 和 Android 手機,接收通知並根據警報執行操作。
  • ConcreteSubject (具體主體):實際的安全系統主機,包含警報邏輯。
  • ConcreteObserver (具體觀察者):具體的設備實現,例如 Android 設備或 iOS 設備。

將 Observer Pattern 套用到我們的應用吧

observer_pattern_uml_3

實作

[Subject: AlarmSystem]

interface AlarmSystem {
    fun addObserver(observer: Device)
    fun removeObserver(observer: Device)
    fun notifyObservers(alarmMessage: String)
}

[Observer: Device]

interface Device {
    fun onAlarmTriggered(alarmMessage: String)
}

[ConcreteSubject: SecurityPanel]

class SecurityPanel : AlarmSystem {
    private val devices = mutableListOf<Device>()

    override fun addObserver(observer: Device) {
        devices.add(observer)
    }

    override fun removeObserver(observer: Device) {
        devices.remove(observer)
    }

    override fun notifyObservers(alarmMessage: String) {
        for (device in devices) {
            device.onAlarmTriggered(alarmMessage)
        }
    }

    fun triggerAlarm(zone: String) {
        val message = "警報觸發於 $zone!"
        println("主機通知: $message")
        notifyObservers(message)
    }
}

[ConcreteObserver: Devices]

class Tablet : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("平板收到通知: $alarmMessage")
    }
}

class IOSDevice : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("iOS 設備收到通知: $alarmMessage")
    }
}

class AndroidDevice : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("Android 設備收到通知: $alarmMessage")
    }
}

[Client]

fun main() {
    val securityPanel = SecurityPanel()

    val tablet = Tablet()
    val iosDevice = IOSDevice()
    val androidDevice = AndroidDevice()

    // add observers
    securityPanel.addObserver(tablet)
    securityPanel.addObserver(iosDevice)
    securityPanel.addObserver(androidDevice)

    // trigger alarm
    securityPanel.triggerAlarm("客廳")
    securityPanel.triggerAlarm("廚房")

    // remove observer
    securityPanel.removeObserver(androidDevice)
    securityPanel.triggerAlarm("臥室")
}

[Output]

主機通知: 警報觸發於 客廳!
平板收到通知: 警報觸發於 客廳!
iOS 設備收到通知: 警報觸發於 客廳!
Android 設備收到通知: 警報觸發於 客廳!

主機通知: 警報觸發於 廚房!
平板收到通知: 警報觸發於 廚房!
iOS 設備收到通知: 警報觸發於 廚房!
Android 設備收到通知: 警報觸發於 廚房!

主機通知: 警報觸發於 臥室!
平板收到通知: 警報觸發於 臥室!
iOS 設備收到通知: 警報觸發於 臥室!

結論

透過 Observer Pattern,我們構建了一個靈活的安全系統通知機制,設備可以動態地加入或移除,且主機與設備之間的耦合度降低,遵循開放關閉原則 (OCP)。此模式適用於任何需要實現通知機制的場景,例如:

  • 即時警報系統
  • 訊息推送系統
  • 事件分發系統

Leave a comment