瞭解單一職責的內容後,我們可以進一步修正,讓程式碼的調整彈性更高、更易於修改,書中提及兩種修正方向:
1.依賴行為而非資料
2.全面實施單一職責
attr_reader
,它可以作為簡單的封裝建立方法class Gear
attr_reader :chainring, :cog # <-----------
def initialize(chainring, cog)
@chainring = chainring
@cog = cog
end
def ratio
chainring / cog.to_f # <-----------
end
end
p gear = Gear.new(4,2) #<Gear:0x000000013a81aed0 @chainring=4, @cog=2>
p gear.ratio #2.0
@data
包裝在方法裡。diameters
方法傳送data
訊息來存取變數的內容。class ObscuringReferences
attr_reader :data
def initialize(data)
@data = data
end
def diameters
# 0 代表鋼圈,1 代表輪胎
data.collect {|cell|
cell[0] + (cell[l] * 2)}
end
# ...其他大量索引該陣列的方法
end
不過,由於 @data
包含複雜的資料結構,所以只有隱藏實例變數還不夠。它依賴於陣列的結構,如果結構發生變化那麼這段程式碼就必須修改。修改方向如下:
Struct
類別來封裝複雜的結構,將其轉換成物件。在RevealingReferences
類別中,wheelify
方法使用Struct
將來源陣列轉換成結構化的物件陣列。wheelify
方法內部隔離了來源陣列的結構細節,使得程式碼只需在一個地方進行修改,而不需要在多處修改。class RevealingReferences
attr_reader :wheels
def initialize(data)
@wheels = wheelify(data)
end
def diameters
wheels.collect { |wheel|
wheel.rim + (wheel.tire * 2)}
end.
Wheel = Struct.new(:rim, :tire)
def wheelify(data)
data.collect{ |cell|
Wheel.new(cell[0], cell[1])}
end
p RevealingReferences.new([[1,2],[2,3]]) #<RevealingReferences:0x000000012908ce18 @wheels=[#<struct RevealingReferences::Wheel rim=1, tire=2>, #<struct RevealingReferences::Wheel rim=2, tire=3>]>
p RevealingReferences.new([[1,2],[2,3]]).wheels #[#<struct RevealingReferences::Wheel rim=1, tire=2>, #<struct RevealingReferences::Wheel rim=2, tire=3>]
p RevealingReferences.new([[1,2],[2,3]]).diameters #[5, 8]
end
與類別相同,方法也應該具有單一職責。
1.從方法撷取出額外職責
以RevealingReferences
類別的diameters
方法為例:
def diameters
wheels.collect {|wheel|
end
wheel.rim + (wheel.tire * 2)}
將程式碼簡化成兩個獨立的方法,各自擔負一項職責。
#一、迭代陣列
def diameters
wheels.collect {|wheel| diameter(wheel)}
end
#二、計算一個輪子的直徑
def diameter(wheel)
Wheel.rim + (wheel.tire * 2))
end
重新呼叫Gear
類別的gear_inches
方法:
輪子直徑的計算工作是隱藏在gear_inches
的內部。
def gear_inches
# 鋼圈加上圍繞的輪胎即弓輪子直徑
ratio * (rim + (tire * 2))
end
將這項工作撷取到新的diameter
方法裡,這樣針對類別的職責檢査就會變得更容易。
def gear_inches
ratio * diameter
end
def diameter
rim + (tire * 2)
end
重點摘要:
2.隔離類別的額外職責Gear
類別擁有一些像是輪子的行爲。那麼這支應用程式會需要一個Wheel
類別嗎?
其實答案應該要是肯定的,當應用程式對Wheel
類別有了明確的需求,它必須要能夠獨立在Gear
之外使用,假設未來有其他需求是處理wheel
的大小事務,也都可以統一交由Wheel
類別負責。
時間來到未來,你需要「計算自行車輪子周長」,由於你已經很小心地將Gear
類別裡的Wheel
行為隔離起來,所以這項修改是無痛的。
將Wheel
Struct 簡單地轉換成一個獨立的Wheel
類別,並增加新的circumference
方法:
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(chainring, cog, wheel=nil)
@chainring = chainring
@cog = cog
@wheel = wheel
end
def ratio
chainring / cog.to_f
end
def gear_inches
ratio * wheel.diameter
end
end
class Wheel
attr_reader :rim, :tire
def initialize(rim, tire)
@rim = rim
@tire = tire
end
def diameter
rim + (tire * 2)
end
def circumference
diameter * Math::PI
end
end
@wheel = Wheel.new(26, 1.5)
puts wheel.circumference
# -> 91.106186954104 38
puts Gear.new(52, 11, @wheel).gear_inches
# -> 137.090909090909
puts Gear.new(52, 11).ratio
# -> 4.72727272727273
我們應該努力實踐單一職責類別或方法,讓只負責單一事物的類別或方法能夠將事物與應用程式的其他部分有所隔離,確保程式碼保有在未來修改或擴增的彈性。
參考資料: