2024-06-18|閱讀時間 ‧ 約 30 分鐘

設計模式與程式架構(六)

    ※ 觀察者模式

    定義:

    • 觀察者模式(Observer Pattern)是一種設計模式,涉及兩個主要角色:觀察者(Observers)和被觀察者(Subject)。在這種模式中,一群觀察者訂閱並觀察某個被觀察的對象。當被觀察者的狀態發生改變時,它會通知所有觀察者,讓他們知曉並作出相應的反應。這種模式通過訂閱和觀察機制實現了對象間的鬆散耦合,使得狀態更新能夠高效地傳遞給所有相關的觀察者。
    • 觀察者模式的工作流程如下:
    1. Subject(被觀察者): 它是被觀察的對象,持有一個觀察者列表(Observers),當自身狀態改變時,會通知這些觀察者。
    2. Observer(觀察者): 它們是對Subject感興趣的對象,會訂閱(觀察)Subject的變化。
    3. 訂閱/取消訂閱: Observer可以訂閱(附加到觀察者列表)或取消訂閱(從觀察者列表移除)。
    4. 通知更新: 當Subject的狀態發生變化時,它會遍歷觀察者列表,通知所有觀察者進行更新。


    好處是:

    • 高效通知:比起觀察者每隔一段時間詢問被觀察者是否有更新,讓被觀察者在更新時直接通知觀察者更加有效率,解決了輪詢(polling)這種低效的問題。
    • 降低耦合度:利用訂閱和通知機制,使得程式的關注點從觀察者分離出來,減少了被觀察者與觀察者之間的耦合度,提升了程式碼的可維護性和靈活性。
    • 耦合度:指的是物件跟物件之間參雜在一起的程度。

    使用場景:觀察者模式能有效地解決多對一或多對多對象之間的通信問題,提升系統的靈活性和可擴展性。

    • 事件處理系統當某個事件發生時,需要通知多個觀察者。例如UI元件的按鈕點擊事件。
    • 數據更新通知:當某個資料源發生變化時,需要通知相關的觀察者自動更新。例如新聞應用程式中,當新聞內容更新時通知訂閱者。
    • 跨系統通信:當某個服務狀態改變或有重要事件時,通知其他相關服務。
    • 聊天室或論壇應用:在實時通訊應用中,當有新的消息發送時,需要通知所有在線的用戶。

    觀察者模式基本架構範例解說:

    //定義觀察者該有的東西,在觀察者模式中習慣用Observer作為關鍵字。
    class Observer {
    // 唯一標識觀察者的 ID
    id: string
    //希望每個被創造出來的觀察者都是獨一無二,所以需要id並且用random方式產出
    constructor() {
    this.id = String(~(Math.random() * 1000)).padStart(3, "0")
    }
    /**
    *用於當 subject 發布消息時,接收消息
    * @param data any- 任意類型,保有 subject 發布的消息內容
    */
    update = (data: any) => {
    console.log(`觀察者 ${this.id} 收到更新消息瘩內容 : ${data}`)
    }
    }

    //定義被觀察者該有的東西
    class Subject {
    //定義一個私有的佇列屬性
    private queue = <Observer[]>[]

    //註冊觀察者,需要一個註冊用的接口
    register = (observer: Observer) => {
    this.queue.push(observer)
    }
    //移除觀察者
    /**
    * 移除觀察者
    * @param observer - Observer 類型的觀察者對象
    */
    remove = (observer: Observer) => {
    const queue = this.queue //將 this.queue 存儲到一個局部變數 queue 中,方便後續操作。
    let len = queue.length //表示佇列的長度。
    //循環遍歷佇列中的每個元素。
    for (let i = 0; i < len; i++) {
    //判斷當前元素是否是要移除的觀察者
    if (queue[i] === observer) {
    queue.splice(i, 1) // 從佇列中移除當前元素。
    }
    }
    }
    //通知觀察者
    /**
    * 通知所有註冊的觀察者
    * @param data - 任意類型,保有 subject 發布的消息內容
    */
    notify = (data: any) => {
    // 使用 forEach 方法遍歷 this.queue 中的每個觀察者(observer)
    // 對每個觀察者調用其 update 方法,並將 data 作為參數傳遞過去
    this.queue.forEach((observer) => observer.update(data))
    }
    }
    //建立主題
    const subject = new Subject()

    //建立觀察者
    const observer1 = new Observer()
    const observer2 = new Observer()
    const observer3 = new Observer()
    const observer4 = new Observer()
    const observer5 = new Observer()

    //註冊主題
    subject.register(observer1)
    subject.register(observer2)
    subject.register(observer3)
    subject.register(observer4)
    subject.register(observer5)
    //通知觀察者
    subject.notify("更新內容" + "hello world")
    /**印出
    觀察者 -127 收到更新消息瘩內容 : 更新內容hello world
    觀察者 -878 收到更新消息瘩內容 : 更新內容hello world
    觀察者 -835 收到更新消息瘩內容 : 更新內容hello world
    觀察者 -249 收到更新消息瘩內容 : 更新內容hello world
    觀察者 -770 收到更新消息瘩內容 : 更新內容hello world
    */

    ※ 補充說明:

    佇列(Queue)【kyoo】是什麼?

    佇列(Queue)是一種資料結構,具有先進先出(FIFO, First In First Out)的特性。簡單來說,這意味著最先加入佇列的元素會最先被移除。

    我們可以把佇列想像成排隊的場景。比如在餐廳排隊點餐,先來的人會先排隊,然後他們會先點餐並離開隊伍。後來的人則會排到隊伍的尾端,依序等待自己的順序。

    這個特性在現實生活中經常出現,例如排隊買票、排隊取號等。佇列通常使用陣列或鏈結串列(Linked list)來實現。

    javascript 底層機制:

    在 JavaScript 中,佇列是一種重要的資料結構,因為 JavaScript 是單執行緒的程式。這就像一家餐廳只有一位店員在服務所有顧客。如果有一個非常耗時的任務獨佔了這位店員,其他顧客就會被迫等待,導致整個系統(網頁)卡住,使用者體驗會很差。

    為了解決這個問題,可以使用佇列來管理這些任務。當有需要等待回應的任務時,不需要讓店員(執行緒)一直等著,而是可以先去服務其他顧客(處理其他任務),把這個需要等待的任務記到佇列裡,等稍後再回來處理。

    例如:在電影院排隊買電影票的過程。

    • 排隊買票:你和其他人排隊等著買電影票。這就像佇列,先來的人先買票,後來的人排在後面。
    • 服務過程:售票員一次只能處理一個顧客。如果你需要選擇座位並且花了一些時間,那麼售票員會記下你的訂單,告訴你「選好座位再來取票」,然後繼續為其他顧客售票。

    這樣一來,售票員就不會因為一個需要時間的顧客而讓整個隊伍卡住,其他顧客也可以快速買到票,整個過程就更有效率。

    結論:

    佇列是一種資料結構,適合處理需要按照先進先出順序執行的任務。它不僅像排隊買電影票那樣處理多人的需求,也能有效地安排和管理程式中的各種執行順序。


    分享至
    成為作者繼續創作的動力吧!
    © 2024 vocus All rights reserved.