Bicycle
有一個Parts
,而Parts
則有一個Part
物件集合。Parts
是一個扮演Parts
角色的類別,它實作spares
。Part
的角色則由OpenStruct
扮演,它會實作name
、description
和 needs_spare
。spares
現在會傳回一個像Part
物件的陣列class Bicycle
attr_reader :size, :parts
def initialize(args ={})
@size = args[:size]
@parts = args[:parts]
end
def spares
parts.spares
end
end
require 'forwardable'
class Parts
extend Forwardable
def_delegators :@parts, :size, :each
include Enumerable
def initialize (parts)
@parts = parts
end
def spares
select {|part| part.needs_spare)
end
end
require 'ostruct'
module PartsFactory
def self.build(config, parts_class = Parts)
parts_class.new(
config.collect {|part_config|
create_part(part_config)})
end
def self.create_part(part_config)
OpenStruct. new (
name: part_config[0],
description: part_config[1],
needs_spare: part_config.fetch(2, true))
end
end
road_config =
[['chain', ' 10-speed'],
['tire_size', '23 '],
['tape_color', 'red']]
mountain_config =
[['chain', '10-speed'],
['tire size', '2.1'],
['front shock', 'Manitou', false],
['rear_shock', 'Fox']]
road_bike =
Bicycle .new(
size: 'L',
parts: PartsFactory.build(road_config))
road_bike.spares
# -> [#<OpenStruct PartsFactory::Part name="chain", 等等......
mountain_bike =
Bicycle .new(
size: 'L',
parts: PartsFactory.build(mountain_config))
mountain_bike.spares
# -> [#<OpenStruct name="chain",
有了這些新類別,便可以輕易建新的自行車類型:
recumbent_config =
[['chain', '9-speed'],
['tire_size', '28'],
['flag', 'tall and orange']]
recumbent_bike =
Bicycle .new(
size: 'L',
parts: PartsFactory.build(recumbent_config))
recumbent_bike.spares
# -> [#<OpenStruct
#. name="chain",
#. description="9-speed",
#. needs_spare=true>,
#. #<OpenStruet
#. name= "tire_size",
#. description="28",
#. needs_spare=true>,
#. #<OpenStruct
#. name="flag",
#. description="tall and orange",
#. needs_spare=true>]
行為分散在物件裡面,而物件則被組織成類別關係,以便能夠自動委派訊息來呼叫正確的行為。
通常,當問題可以使用組合技巧解決時,應該優先考慮使用組合。組合具有較低的依賴關係,更加靈活,適用於大多數情況。只有在確實需要共享通用行為且能夠確保低風險的情況下,才應考慮使用繼承。
1. 繼承的獲益
滿足合理、可用和典範這三項程式碼目標。
合理性
允許我們建立合理的類層次結構,其中頂層方法的定義具有最廣泛的影響。正確建模其結構可以通過小的代碼更改來實現重大的行為變化。
典範性
正確編寫的類層次結構易於擴展,體現了抽象,並且每個新的子類都會插入某種具體的差異。這種模式容易遵循,具有典範性。
可用性
使用繼承的程式碼可以描述為“開-閉”原則,即對於擴展是開放的,但對於修改是封閉的。這意味著可以輕鬆擴展代碼以適應新的需求,而不需要修改現有代碼,可用程度高,可輕鬆創建新的子類來適應變化。
2. 繼承的代價
結論:
子類別不僅依賴於其父類別裡定義的方法,還會依賴於傳送給父類別的訊息自動委派。出於設計的緣故導致無可避免的綁定在一起,因此,當修改父類別時還是會引發廣泛的行為修改。
選擇使用繼承也應考量到將來程式碼的使用者,撰寫時請避免要求使用者必須以子類別方式繼承物件才能夠獲得程式碼的行爲。
1. 組合的獲益
2. 組合的代價
一個組合物件依賴於許多部分,即使每個部分都很小且易於理解,但整個組合操作可能沒有那麼明顯,組合物件必須明確地知道哪一則訊息需要委派給誰,相同的委派程式碼可能會被許多不同的物件所需要,但組合沒有提供共用這段程式碼的方式。
1. 繼承用於「是什麼」的關係
物件有時會很自然地形成靜態且相當明顯的特殊化層次結構,對於這類物件可以使用經典繼承來建模。適用於淺窄的層次結構,小型的層次結構尺寸讓它易於理解,意圖明顯,且易於擴展,符合成功利用繼承的標準。
2. 鴨子類型用於「表現得像什麼」的關係
有些情況需要許多不同的物件扮演一個共同的角色。有兩種方法可用於識別出存在的角色。第一種方法,儘管某個物件扮演了這個 角色,但這個角色並非該物件的主要職責。第二種方法,有某項普遍的需求,使得許多原本並不相關的物件都期望扮演同一個角色。
3. 組合用於「含有什麼」的關係
物件包含了大量的個別部分,而並不僅僅是個別部分的總和,包含了其他行為。
「是什麼」和「含有什麼」的區別,是決定繼承或組合的重點。物件的個別部分越多,就越有可能應該使用組合來建模。
參考資料: