iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0
自我挑戰組

Ruby OOP to Oops !n 30系列 第 4

IT 邦鐵人賽 Day 4 - Dependencies

  • 分享至 

  • xImage
  •  

依賴關係(Dependencies)

導演,先來個情境!

Ken: 哈囉! Mike跟Mike (別懷疑,同名同姓)

Mike: 嗨! Ken (x2)

Ken: 你們對於Ruby有什麼看法啊? 好玩嗎?

Mike: 我覺得..... (x2)


別懷疑,在上述的情境裡Mike是兩個人,而且同手同腳地走路甚至想法和呼吸頻率一切都一樣,根本就是同一人(但世界上不會有這種狀況)
所以我們把這種一模一樣的彼此兩人,定義成依賴關係100%,代表他們完全懂彼此的一切,沒有任何秘密。
那麼0%則是代表兩個人彼此不相識,而且在世界的兩端... 毫無關聯。
而每個人都是介於0% ~ 100%間的關係吧! 程式碼也是如此
當物件的關係上升時,代表耦合(Coupling)上升,但我們可以理解耦合是必然的,可怕的是如果過於嚴重時,就應該要重新設計兩者的依賴關係。(如果是我,我會砍掉一個Mike,要不然會煩死)

class Ken
  attr_reader :location, :time, :weather, :content

  def initialize(location, time, weather, content)
    @location = location
    @time = time
    @weather = weather
    @content = content
  end

  def chatting
    res = Mike.new(content).talk
  end
end

從上述程式碼可以看出幾個耦合的關係

  1. 我們必須知道Mike(類別)的存在才可以聊天(而且我只能跟他聊天...)
  2. 並且我希望Mike會有talk的方法可以來回答我
  3. 在產生Ken的時候需要給予地點時間天氣… 而且要依照順序
  4. Ken知道Mike需要content這個參數

解決辦法

  1. 第一點可以使用鴨子型別來達成依賴方法而非類別
  2. 第二點無法避免,但至少我只要知道那個物件,有talk而且參數型態是對的,就可以獲得回應
  3. 第三點可以使用散列表預設值
  4. 第四點無法避免,但可以使用反轉依賴來減少Ken類別的負擔
    而實際設計中,若是遇到不可避免的依賴時,那就隔離依賴,來讓他顯而易見
    這樣的設計在未來需要更改時,就可以直接找到正確的位置!

實際範例

接下來我會使用Sandi Metz著作中的例子來解說
(絕對不是我偷懶噢!是因為寫得太好了)/images/emoticon/emoticon12.gif

在管理依賴關係前

class Gear
  attr_reader :chainring, :cog, :rim, :tire
  def initialize(chainring, cog, rim, tire) 
    #若少任何一個值,都會導致程式出錯
    @chainring = chainring
    @cog = cog
    @rim = rim
    @tire = tire
  end
  
  def gear_inches
    ratio * Wheel.new(rim, tire).diameter #gear_inches只限定給Wheel類別使用
  end
  
  def ratio
    chainring / cog.to_f
  end
  
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire) 
    @rim = rim
    @tire = tire
  end
 
  def diameter
    rim + (tire * 2)
  end
 
end

Gear.new(52, 11, 26, 1.5).gear_inches #需要知道參數輸入順序

在管理依賴關係後

class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(args) 
    #使用||提供預設值
    @chainring = args[:chainring] || 40
    @cog = args[:cog] || 18
    @wheel = args[:wheel]
  end
  
  def gear_inches
    #當wheel變成參數時,只要任何有diameter方法的類別,都可以執行gear_inches
    #而不會限定在Wheel類別中
    #意味著Gear不用知道誰執行diameter只要會執行diameter就好(鴨子型別)
    ratio * wheel.diameter
  end
  
  def ratio
    chainring / cog.to_f
  end
  
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire) 
    @rim = rim
    @tire = tire
  end
 
  def diameter
    rim + (tire * 2)
  end
 
end

#使用args可以避免知道順序,而且增加可讀性
Gear.new(
         cog: 11,
         chainring: 52, 
         wheel: Wheel.new(26, 1.5)).gear_inches

以上的重構,就已經解釋完上面前三點囉! 再來先解釋何謂隔離依賴~

隔離依賴

隔離依賴的概念其實很簡單,就是在讓這個類別依賴內容直接顯露。
就像是把依賴Wheel.new(rim, tire)時,在initialize就產生實體,這樣讓依賴內容顯而易見
或者是額外封裝依賴行為,在往後找code時就不需要在茫茫程式碼中尋找,而是直接找到方法就好囉~

依賴反轉

比較困難的就是依賴反轉,因為需要先理解何謂抽象
其實當我們使用鴨子型別時,就開始在定義抽象介面
試想看看,Gear告訴我們你可以在wheel這個參數上,幫他放上任何一個擁有diameter方法的類別喔!
這就代表這是一個非限定類別的參數,而傳入著個參數的地方,就是抽象介面
抽象介面其中一個好處就是彈性大增,因為你不用只為其中一個類別做事,而是很多類別都可以帶進來執行!
所以與其依賴一個非抽象介面(限定參數),依賴抽象介面應該來的更安全且更方便。

好啦~ 可以洗洗睡囉~ 明天我們再來聊聊怎麼設計介面吧!

感謝大家 如有問題,再煩請大家指教!


上一篇
IT 邦鐵人賽 Day 3 - SRP
下一篇
IT 邦鐵人賽 Day 5 - Interfaces
系列文
Ruby OOP to Oops !n 3020
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
Jean_HSU
iT邦新手 5 級 ‧ 2022-09-20 20:26:52

頭香耶~

0
Mike_Lai
iT邦新手 4 級 ‧ 2022-09-21 10:52:10

Hi I'm Mike.

Hi I'm Mike.

0
Mike_Lai
iT邦新手 4 級 ‧ 2022-09-21 11:32:52

依賴反轉:這就代表這是一個非限定類別的參數,而傳入著個參數的地方,就是抽象介面。
這部分是在說,後來的 Gear.new 因為可以把其他經過隔離依賴的類別當成參數帶進去,所以這個 Gear 類別就形成了其他類別的抽象介面?

我要留言

立即登入留言