今天的主題來到了 Decorator Pattern 修飾模式,在進入內文前,讓我們先看一下 GoF 四人幫為它下的定義。
將額外權責動態附加於物件身上,不必衍生子類別及可彈性擴增功能。
繼續往下之前,想請讀者先記得上面這段定義的幾個重點,分別是,額外、動態、物件、不必衍生子類別、彈性擴增。唔,好像整段話都是畫重點了。(毆飛)
想像你正經營著一間製卡工廠,為了方便工人的操作,所有的製卡器都只有一個 make()
介面,看起來像下圖。
工廠剛開始經營的時候,只主要生產三種樣式,點點卡(Dotted)、條紋卡(Striped)以及純色卡(Filled)。為此我們有三種卡片製造器,看起來像下面這張。
過了段時間,為了增加產品多樣性,我們決定多生產兩種卡片,分別是點點條紋相間卡(DottedStriped)以及點點純色卡(DottedFilled)兩種,所以我們又新增加了兩種機器。
因為想要更加的多元,因此又新增了條紋全色卡(StripedFilled)和點點條紋全色卡(DottedStripedFilled)
隨著生產越來越多樣的卡片,加入系統的製卡器也越來越多;然而,我們注意到當我們需要製造新的卡片類型的時候,需要對系統進行修改,要對原有系統增加製造新產品的功能,並且不能讓製卡器壞掉。
看起來,透過繼承(Inheritance)來新增不同的製卡器似乎有點不太好維護啊。
當某類別有了一些行為需要被改變的時候,我們經常會自然地選擇繼承(Inheritance),在物件導向程式開發的過程中這是很常見的狀況。然而我們必須要記得下面兩件事。
因此,當我們在使用繼承的時候,要注意這父類別跟子類別是不是真的有行為上以及屬性上的相依。如果新增子類別的目的是擴增父類別既有的行為,但兩者基本上並沒有直接的關連,通常使用聚合(Aggregation)或組合(Composition)會是更好的選擇。
讓我們回頭看看故事中可以觀察到的事情,在圖二和圖三中新增的製卡器在製造卡片時,與最一開始的三種製卡器並沒有絕對性的不同;更甚者,新增的製卡器更像是基於其中一種製卡器的結果,再拿去給另一種製卡器加工。
例如,點點條紋卡可以是先製造點點卡,再把這張卡拿去當作條紋卡製造機的輸入。這使得點點條紋卡製卡器像是把點點卡製卡器包裝進條紋卡製卡器在裡面。相同的,點點全色卡就像是把全色卡製卡器放進點點卡製卡器裡。可以發現,越複雜多樣的製卡器,其實只是把其他的製卡器包裝起來,並且可以透過不同的包裝順序,組合出不一樣的輸出成果。
類似這種設計,擁有同一種介面的每一個類別,都能夠被使用來去修飾(decorate)另一個類別的輸出時,就是所謂的 Decorator Pattern 修飾模式–它也有個別名是 Wrapper。
一般而言, Decorator Pattern 的設計如下面這張類別圖,對主程式來說,它只需知道有一種元件會回應operation()
方法。
而程式執行的時候,會像是下面這張圖。
class Worker
def operate(card_maker)
card_maker.make if card_maker
end
end
# Decorator
class Card
end
class CardMaker
attr_reader :other_maker
def initialize(other_maker)
@other_maker = other_maker
end
def make
if @other_maker
@other_maker.make
else
Card.new
end
end
end
class DottedCardMaker < CardMaker
def make
card = super
print_dots(card)
card
end
def print_dots(card)
...
end
end
class StripedCardMaker < CardMaker
def make
card = super
print_strips(card)
card
end
def print_strips(card)
...
end
end
class FilledCardMaker < CardMaker
def make
card = super
fill_color(card)
card
end
def fill_color(card)
...
end
end
custom_card_maker = DottedCardMaker.new(FilledCardMaker.new)
worker = Worker.new
worker.operate(custom_card_maker)
讓多個物件都有機會處理某一訊息,以降低訊息發送者與接收者之間的耦合關係。它將接收者物件串連起來,讓訊息流經其中,直到被處理了為止。
因為 Decorator Pattern 也是透過遞迴的方式來完成最後的結果,因此常常被拿來跟 Chain of Responsibility 相提並論。可以注意的是,後者可以中斷整個遞迴的進行而前者不能,另一個不同點是後者的實作類別通常會有不相關且獨立的行為,而前者只是擴增既有的行為。
定義一整族演算法,將每一個演算法封裝起來,可互換使用,更可在不影響外界的情況下個別抽換所引用的演算法。
與 Strategy 相較,Decorator Pattern 是調整了物件的外觀,而 Strategy 則是改變了物件的內在。
將物件組織成樹狀結構、「部分-全體」層級關係,讓外界以一致性的方式對待個別物件和整體物件
Composite 跟 Decorator Pattern 擁有很類似的類別圖,因為他們都是遞迴的把擁有同一種介面的類別組織起來,然而有幾個不同的地方可以區別他們。
整理一下,當我們發現下面這幾點的時候,就還挺適合使用 Decorator Pattern 的!
作者:Yenting