Things don’t just happen; they are made to happen.

— John F. Kennedy

核心概念#

在早期,電腦不太靈活——我們會根據它們的限制來組織與它們的互動方式。今天,我們期望更多:電腦必須融入我們的世界,而不是反過來。我們的世界是混亂的——事情不斷發生、東西被移動、我們改變主意。我們寫的應用程式必須設法弄清楚該做什麼。

本節討論的是撰寫這些響應式應用程式。核心概念是事件(event)

什麼是事件#

事件代表資訊的可用性。它可能來自外部世界(使用者按下按鈕、股票報價更新),也可能是內部的(計算結果就緒、搜尋完成)。

如果我們撰寫根據事件響應並調整行為的應用程式,這些應用程式在真實世界中會運作得更好——使用者會覺得它們更互動,應用程式本身也能更好地利用資源。

作者介紹了四種策略來處理事件:

  1. 有限狀態機(Finite State Machines)
  2. 觀察者模式(The Observer Pattern)
  3. 發佈/訂閱(Publish/Subscribe)
  4. 響應式程式設計與串流(Reactive Programming and Streams)

有限狀態機(FSM)#

狀態機基本上就是一個處理事件的規格。它由一組狀態組成,其中一個是當前狀態。對於每個狀態,列出對該狀態有意義的事件;對於每個事件,定義系統的新的當前狀態。

簡單範例:訊息解析器#

從 websocket 接收多段訊息:先是 header,接著任意數量的 data,最後是 trailer。

FSM 的美妙之處在於,我們可以將它純粹表達為資料

TRANSITIONS = {
  initial: {header: :reading},
  reading: {data: :reading, trailer: :done},
}

處理程式碼同樣簡單——索引轉換表使用當前狀態和訊息類型,如果沒有匹配的新狀態就設為 :error

加入動作#

純粹的 FSM 只是事件串流解析器,其唯一輸出是最終狀態。我們可以透過在特定轉換上觸發動作來增強它。例如提取原始碼檔案中的字串,每個轉換有兩個標籤——觸發事件和要執行的動作。

狀態機被開發者嚴重低估。它們實作簡單(通常只需幾行程式碼),卻能解開大量潛在的混亂。將狀態存儲在外部儲存中,用它來驅動狀態機,是處理工作流需求的好方法。

觀察者模式(Observer Pattern)#

在觀察者模式中,有一個事件來源稱為 observable,以及一組有興趣的客戶端稱為 observers

觀察者透過傳遞要被呼叫的函式參考來向 observable 註冊興趣。當事件發生時,observable 遍歷觀察者列表並呼叫每個傳遞的函式。

module Terminator
  CALLBACKS = []
  def self.register(callback)
    CALLBACKS << callback
  end
  def self.exit(exit_status)
    CALLBACKS.each { |callback| callback.(exit_status) }
    exit!(exit_status)
  end
end

觀察者模式有一個問題:因為每個觀察者都必須向 observable 註冊,這引入了耦合。此外,典型的實作中回呼是由 observable 同步內聯處理的,這可能引入效能瓶頸

發佈/訂閱(Publish/Subscribe)#

Publish/Subscribe(pubsub)將觀察者模式一般化,同時解決了耦合和效能問題。

在 pubsub 模型中,我們有 publisherssubscribers。它們透過頻道(channels) 連接。頻道在獨立的程式碼體中實作——可能是函式庫、行程、或分散式基礎設施。所有這些實作細節都對你的程式碼隱藏。

每個頻道有一個名稱。訂閱者對一個或多個具名頻道註冊興趣,發佈者向頻道寫入事件。不同於觀察者模式,發佈者和訂閱者之間的通訊是在你的程式碼之外處理的,而且可能是非同步的

Pubsub 適合用來去耦合非同步事件的處理。它允許在不修改現有程式碼的情況下新增和替換程式碼,甚至在應用程式運行時也可以。缺點是很難看清使用大量 pubsub 的系統中正在發生什麼。

響應式程式設計、串流與事件#

如果你用過試算表,你就熟悉響應式程式設計。一個儲存格包含參考另一個儲存格的公式,當被參考的儲存格更新時,第一個儲存格也會更新。值會隨著它們使用的值改變而反應

串流(Streams) 讓我們將事件視為一種資料集合。就好像我們有一個事件列表,每當新事件到來時列表就會變長。我們可以像任何其他集合一樣操作串流——操作、組合、過濾。串流可以是非同步的,這意味著你的程式碼可以在事件到達時立即回應。

事件串流統一了同步和非同步處理,隱藏在一個共同、方便的 API 之後。

事件無所不在#

事件無處不在。有些很明顯:按鈕點擊、計時器到期。其他的不那麼明顯:有人登入、檔案中的一行符合某個模式。但無論來源為何,圍繞事件建構的程式碼可以比其更線性的對應物更加響應和更好地去耦合。

相關章節#

  • Topic 28,去耦合
  • Topic 36,黑板