Preface
如果你為物件定義單例方法,實際上該方法定義,會存在於物件的匿名單例類別中(Anonymous singleton class),你可以使用 class << object 語法,開啟 object 的單例類別。例如:
- o = Object.new
- def o.some
- puts "some"
- end
- o = Object.new
- class << o
- def some
- puts "some"
- end
- end
這兩種方式的效果幾乎是相等的,目前就暫且看作是相等。你可以使用 class << object 語法,開啟 object 的單例類別,也就可以在其中作任何類別可以進行的定義,包括了 include 模組。例如臨時為某個物件增加 Enumerable 模組的作用:
- class Pond
- def initialize(list = [])
- @list = list
- end
- def <<(obj)
- @list << obj
- end
- end
- pond = Pond.new([2, 1, 5, 3, 6, 4])
- class << pond
- include Enumerable
- def each
- @list.each do |obj|
- yield(obj)
- end
- end
- end
- print "#{pond.sort}\n" # [1, 2, 3, 4, 5, 6]
- puts "Max: #{pond.max}" # Max: 6
- puts "Min: #{pond.min}" # Min: 1
- pond.each_with_index do |ball, index|
- puts "#{index} - #{ball}"
- end
模組中定義方法時,self 與 super 是動態綁定的,被包括到某個類別時,模組定義方法中的 self 代表該類別的實例,而 super 代表模組被 include 前同名的模組方法或類別方法(如果是類別繼承,super 代表父類別中的同名方法)。
由於模組中的 super 是動態綁定,而一個類別可以 include 多個模組,因此可執行所謂 堆疊擴充(Stackable extension)的功能。
舉個例子來說,你打算設計一個點餐程式,目前主餐有炸雞、漢堡,你打算讓點了主餐的客入選擇附餐時可以有優惠,如果使用繼承的方式來達到這個目的,例如:
- class FriedChicken
- def content
- "不黑心炸雞"
- end
- def price
- 49.0
- end
- end
- class SideDishOne < FriedChicken
- def content
- super + " | 可樂 | 薯條"
- end
- def price
- super + 30.0
- end
- end
- module SideDishOne
- def content
- super + " | 可樂 | 薯條"
- end
- def price
- super + 30.0
- end
- end
- module SideDishTwo
- def content
- super + " | 玉米濃湯 | 咖啡"
- end
- def price
- super + 50.0
- end
- end
- class FriedChicken
- def content
- "不黑心炸雞"
- end
- def price
- 49.0
- end
- end
- class FriedChickenSideDishOne < FriedChicken
- include SideDishOne
- end
- meal = FriedChickenSideDishOne.new
- puts meal.content # 顯示 不黑心炸雞 | 可樂 | 薯條
- puts meal.price # 顯示 79.0
特別定義出 FriedChickenSideDishOne 類別看來奇怪,實際上,你可以這麼使用:
- meal = FriedChicken.new
- class << meal
- include SideDishOne
- end
- puts meal.content # 顯示 不黑心炸雞 | 可樂 | 薯條
- puts meal.price # 顯示 79.0
- meal = FriedChicken.new.extend(SideDishOne)
- puts meal.content # 顯示 不黑心炸雞 | 可樂 | 薯條
- puts meal.price # 顯示 79.0
- meal1 = FriedChicken.new.extend(SideDishOne)
- meal2 = FriedChicken.new.extend(SideDishTwo)
- meal3 = FriedChicken.new.extend(SideDishOne).extend(SideDishTwo)
- meal4 = FriedChicken.new.extend(SideDishTwo).extend(SideDishOne)
- puts meal1.content # 不黑心炸雞 | 可樂 | 薯條
- puts meal1.price # 79.0
- puts meal2.content # 不黑心炸雞 | 玉米濃湯 | 咖啡
- puts meal2.price # 99.0
- puts meal3.content # 不黑心炸雞 | 可樂 | 薯條 | 玉米濃湯 | 咖啡
- puts meal3.price # 129.0
- puts meal4.content # 不黑心炸雞 | 玉米濃湯 | 咖啡 | 可樂 | 薯條
- puts meal4.price # 129.0
同時也注意到,meal3 是先擴充 SideDishOne 模組再擴充 SideDishTwo,而 meal4 則是先擴充 SideDishTwo 再擴充 SideDishOne,擴充模組的順序不同,則呼叫的順序不同,則結果就有所不同。事實上,這是 Ruby 版本的 Decorator 模式 之實現,雖然語法不同,不過其在不改變被修飾物件功能的情況下,動態地為物件的操作結果作修飾,這樣的精神是相同的。
沒有留言:
張貼留言