iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 19
1

sushi

(引用自 https://www.flickr.com/photos/strollers/12465549/

想必沒吃過也聽過看過迴轉壽司吧,壽司師傅站在工作檯前,拿著出不同的食材,製作出一盤盤的美味的壽司,有鮭魚握壽司、海膽壽司、鮭魚卵軍艦壽司等等,做好的食材被放到迴轉台上送到按照順序坐好的食客面前。

每個食客都有自己心中想要吃的那一個種類,當壽司轉到面前的時候,看一下眼前這盤是不是自己所想要的,是的話就取下來盡情享用(喔,對這盤也有興趣的人就只好等下一盤了),至於沒有興趣的,就讓它過去吧,它總會遇到對的人把它給接收了。

我們今天要討論的 Chain of Responsibility 模式就跟迴轉壽司一樣,有人在製造需求,然後有一群人們把需求一個一個轉傳下去,只把自己有興趣、能處理的需求拿來處理。當然座位不是隨便你坐的,是店家幫忙安排的。

上面的迴轉壽司範例,想必大家看完都肚子餓了吧,忍耐一下,看完就可以開飯了。

來瞧瞧 Chain of Responsiblity 的定義

讓多個物件都有機會處理某一訊息,以降低訊息發送者和接收者之間的耦合關係。它將接收者物件串連起來,讓訊息流經其中,直到被處理了為止。

(取自 物件導向設計模式−可再利用物件導向軟體之要素

從定義中和迴轉壽司的例子中,我們發現所有的接收者應該都會擁有同樣的公開介面,而且每一個接收者都會有一個內部用來判定是否要處理訊息的標準,小提醒一下,即使是來者不拒也是一種判斷標準喔。然而,接收者的座位順序是由主程式或者是由發送者來決定的。於是我們可以來看看下面的 UML 類別圖了。

15_cor_01

來點實作吧-以迴轉壽司為名

class SushiMaker
  attr_reader :sushi_lover

  def initialize
    h1 = SalmonSushiLover.new
    h2 = TunaSushiLover.new
    h1.next_sushi_lover = h2
    sushi_lover = h1
  end

  def start
    sushi = make_susshi_with_random_material
    sushi_lover.eat(sushi)
  end
end

# Handler
class SushiLover
  attr_reader :next_sushi_lover

  def next_sushi_lover=(next_lover)
    self.next_sushi_lover
  end

  def eat(sushi)
    if next_sushi_lover
      next_sushi_lover.eat(sushi)
    end
  end
end

# Concrete Handlers
class SalmonSushiLover < SushiLover
  def eat(sushi)
    if eat?(sushi)
      puts 'I love Salmon!'
    else
      super
    end
  end

  private

  def eat?(sushi)
    sushi.topping == :salmon
  end
end

class TunaSushiLover < SushiLover
  def eat(sushi)
    if eat?(sushi)
      puts 'I love Tuna!'
    else
      super
    end
  end

  private

  def eat?(sushi)
    sushi.topping == :tuna
  end
end

不知道怎地,寫完範例總覺得兩個食客是被強迫餵食。 XD

Chain of Responsibility 的應用場景

從迴轉壽司的範例中,其實會注意到,食客其實並不知道壽司師傅接下來會做出什麼壽司,可能是鮭魚也可能是玉子燒。在實際工程場景中,如果我們的程式需要針對不同的訊息有不同的對應行為,可是在執行中卻無法提前知道會有什麼樣的需求時,就滿適合使用 Chain of Responsibility 來處理。有沒有覺得很熟悉?是的,網路請求就很符合這個場景。以 Ruby 開發網路程式為例,在 Ruby 的世界裡,有個很經典的網路程式架構叫做 Rack,一個 Rack 的程式,基本上會加入一系列 middleware,而每一個 middleware 都實作了 call() 這個方法,當請求進來的時候,Rack 主程式會依照註冊的順序,把請求傳遞進去,讓每一個 middleware 去處理。

優缺點

優點

我們可以自己控制接收者的順序以達到我們想要的處理流程;而且透過 Chain of Responsibility 模式,我們的程式可以更容易的加入新的行為,卻不會影響既有的行為,這也是 Open-Close Principle 所要求的;另外因為接收者只需要對自己有能力處理的訊息做處理,所以這邊也符合了 Single Responsibility Principle.

缺點

如果我們忘記把某個接收者串連起來,或者有某種類型的訊息沒有對應的接收者,那麼我們的程式就會漏掉某些請求。

總結

當我們的程式需要對不同的訊息有不同的處理方式,但是對外的介面是固定介面的情況下,我們可能會發現自己的主程式變得異常的長,站遠一點還會看到波動拳(一堆 if - elsif - elsif - elsif ... - else),這種情況其實就是我們的程式跟處理訊息的物件過度耦合了,這個時候我們其實可以思考是不是適合使用 Chain of Responsibility 來讓我們的程式更容易維護,也更容易增加功能,今天的介紹就到這邊啦,大家來去吃迴轉壽司吧!

作者:Yenting


上一篇
[Design Pattern] Null Object 空物件模式
下一篇
[Design Pattern] Facade 門面模式
系列文
什麼?又是/不只是 Design Patterns!?32

尚未有邦友留言

立即登入留言