iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0
自我挑戰組

Ruby OOP to Oops !n 30系列 第 6

IT 邦鐵人賽 Day 6 - Duck Typing

  • 分享至 

  • xImage
  •  

終於來到呱呱的主題啦!

鴨子型別(Duck Typing)

今天導演就不拍情境,直接進入主題囉!

首先我們先來談談 鴨子型別(Duck Typing) 的定義是什麼

當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。

好! 今天可以結束了... /images/emoticon/emoticon29.gif


當然不可能這麼簡單用一句話結束這回合囉!
不知道大家還記得我們前幾天其實都有提到鴨子型別這關鍵字,有碰到一點程式碼,也點到一點概念。
所以我想今天就從課本中的例子,來解釋會更為完整且詳細~

沒有使用鴨子型別前:

class Mechanic

  def prepare_bicycles(bicycle)
    #do something
  end
  
end

class TripCoordinator

  def buy_food(customers)
    #do something
  end
  
end

class Driver

  def gas_up(vehicle)
    #do something
  end
  
  def fill_water_tank(vehicle)
    #do something
  end
  
end

class Trip
  arrt_reader :bicycles, :customers, :vehicle
  
  def prepare(prepares)
    prepares.each do |prepare|
      if prepare.kind_of?(Mechanic)
        prepare.prepare_bicycles(bicycle)
      elsif prepare.kind_of?(TripCoordinator)
        prepare.buy_food(customers)
      elsif prepare.kind_of?(Driver)
        prepare.gas_up(vehicle)
        prepare.fill_water_tank(vehicle)
      end
    end
  end
  
end

就目前為止,一切或許都還很正常,但如果有天增加新的準備事項時,就又要再寫入更多的elsif,又或者說類別名稱更改呢? 像是把Driver改成Chauffeur時,又要回頭修改程式碼。
那有沒有個辦法是prepares內所擁有的還都是物件Mechanic TripCoordinator Driver,然後跑迴圈時可以不經過判斷,自動幫我做好所有的準備呢?
或許我這樣解釋太複雜,但白話文就是我不管你是誰,當我要你準備,你就是準備好所有東西
反觀現在的程式碼,則是在Trip內,去一個個跟每個類別說,你應該要準備什麼,這概念是完全不同的!

鴨子型別:

class Mechanic

  def prepare_trip(trip)
    trip.bicycles.each { |bicycle| prepare_bicycle(bicycle) }
  end

end

class TripCoordinator

  def prepare_trip(trip)
    buy_food(trip.customers)
  end
  
end

class Driver
  
  def prepare_trip(trip)
    vechicle = trip.vehicle
    gus_up(vehicle)
    fill_water_tank(vehicle)
  end
  
end

class Trip
  arrt_reader :bicycles, :customers, :vehicle
  
  def prepare(prepares)
    prepares.each do |prepare|
      prepare.prepare_trip(self)
    end
  end
  
end

使用鴨子型別後,Trip不用去管其他類別需要準備什麼了,而是當我呼叫到其中一個類別時(例如Mechanic),那他就可以完全的把腳踏車準備好!
而整個流程的細節在於,使用抽象介面(prepare,因為這個介面不會去管你丟入什麼樣的物件,只要確保丟入的物件可以實現prepare_trip的方法,這也就是為什麼~需要去準備內容的類別內,也會開放prepare_trip的公共介面來給Trip使用。
再來就是丟入的參數是self,這裡之所以可以作為參數,是因為使用prepare方法時,一定會new出一個Trip物件(trip = Trip.new(bicycles, customers, vehicle))再來執行方法(trip.prepare(prepares)),所以這時prepare方法內的self就會是Trip.new(bicycles, customers, vehicle)或者也可以說呼叫prepare方法就是Trip.new(bicycles, customers, vehicle)而誰呼叫,誰就是那一個self。理所當然的~ self參數帶入每個類別時,就可以呼叫裡面的各個數值(例如trip.customers)!

多態性(Polymorphism)

這個抽象介面其實有一個更為精確的字可以形容:多態性(Polymorphism)
其中定義其實很好理解

在OOP中,多態性是指許多不同物件回應相同訊息的能力。訊息的傳送者不需要關心接收者類別,接收者會提供自己所擁有的特定行為版本

以我們例子來說,prepare方法具有多態性,可以輸入不同物件(Mechanic TripCoordinator Driver)來提供自己擁有的特地行為(例如Mechanic提供prepare_bicycle(bicycle))。

至於怎麼找出鴨子型別然後加以設計呢... 我覺得就是出現判斷式時,可能就要多加注意是否藏著可以抽離的抽象介面!

好吧! 今天又過一天啦~ 接下來要討論的會是繼承等相關內容,一起加油吧!

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


上一篇
IT 邦鐵人賽 Day 5 - Interfaces
下一篇
IT 邦鐵人賽 Day 7 - Inheritance
系列文
Ruby OOP to Oops !n 3020
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Jean_HSU
iT邦新手 5 級 ‧ 2022-09-22 20:17:02

鴨子呱呱

我要留言

立即登入留言