iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
0
自我挑戰組

滿滿的紅寶石不拿嗎?-- 去吧!我把世界上的一切都放在那裡了! 系列 第 20

開放類別 <> 這個星期的我很可以!不管裡面有什麼都給我來一點吧!- 滿滿的紅寶石不拿嗎?

[Day20] 接下來要隆重登場的是,鋼!鐵!將!軍!

嗨大家好!這篇要介紹的是開放類別(Open Class)!

過去幾天,我已經學會了類別、繼承、模組、實體方法與類別方法的差異,以及方法存取的限制。而今天再來看一些有趣的東西吧!


類別---合體!

身為一隻金魚,我常常在 coding 時忘記自己有沒有寫過這個類別,一不小心就做出了兩個相同名稱的類別,但裡面各自定義的方法不同:

class Robot
  def eye
    puts "發射超強激光!"
  end
end

class Robot
  def arm
    puts "發射拳頭砲彈!"
  end
end

這時候如果又做了一個實體 franky 出來:

franky = Robot.new

那這個實體 franky 到底是從哪個類別產生的呢? 不是很確定,
那讓它呼叫實體方法看看:

franky.eye
franky.arm

# 印出
發射超強激光!
發射拳頭砲彈!

咦!franky 居然兩招都可以用!
難道是...?

事實上,Ruby 會把我們寫的東西看成這樣:

class Robot
  def eye
    puts "發射超強激光!"
  end

  def arm
    puts "發射拳頭砲彈!"
  end
end

居然合體啦!!!只要是同名的類別,各自定義的方法也會融合在一起!
(同名方法自然另當別論)


類別裡面什麼都不用寫也有方法可以用

之前介紹時好像沒提到 Ruby 這個有趣的特性,就拿剛剛的 Robot 做例子吧!

class Robot
end

franky = Robot.new

現在我自己定義了一個新的類別 Robot ,可是裡面真的是超~空~一個方法都沒有。
然後 franky 能呼叫什麼方法嗎?應該不行吧?

還是來確認一下好了:

franky.methods

# 印出
[:dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :clone, :display, :hash, :class, :singleton_class, :method, :public_send, :public_method, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :nil?, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :__send__, :!, :==, :__id__, :!=, :equal?, :instance_eval, :instance_exec] 

哇啊啊一堆方法!這些是哪裡來的?是誰偷駭進了我的電腦嗎?
不對不對這一定是在做夢,我要來測試一下!

Q1: franky 是什麼類別?

franky.class
 => Robot 

通過第一關...沒關係再來!

Q2: franky 是整數嗎?

franky.is_a?(Integer)
=> false

好我知道很明顯不是...

啊!等等!就連一開始找方法的:

franky.methods

這個 methods 也不是我自己定義的啊!虧我還用得這麼順手!

不管是原生類別還是自定義類別,其實 Ruby 都已經先準備好很多方法著大家去使用了!這部分的原因有興趣的朋友可以先上網查 Ruby 的 Kernel 模組,有時間我會再來仔細說明!


讓我們來加點料

由於這個特性,我們就能為原生類別再添加一些自定義的方法,這個技巧也是 開放類別(Open Class) 的精髓。

譬如 Ruby 原生的 String 類別,裡面已經定義好很多給字串使用的方法:

a = String.new

a.methods

# 印出
[:unicode_normalize, :unicode_normalize!, :ascii_only?, :to_r, :unpack, :unpack1, :encode!, :%, :include?, :*, :+, :count, :partition, :+@, :-@, :<=>, :<<, :to_c, :==, :===, :sum, :=~, :next, :[], :casecmp, :casecmp?, :insert, :[]=, :match, :match?, :bytesize, :empty?, :eql?, :succ!, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :scrub!, :scrub, :undump, :byteslice, :freeze, :inspect, :capitalize, :upcase, :dump, :downcase!, :swapcase, :downcase, :hex, :capitalize!, :upcase!, :lines, :length, :size, :codepoints, :succ, :split, :swapcase!, :bytes, :oct, :prepend, :grapheme_clusters, :concat, :start_with?, :reverse, :reverse!, :to_str, :to_sym, :crypt, :ord, :strip, :end_with?, :to_s, :to_i, :to_f, :center, :intern, :gsub, :ljust, :chars, :delete_suffix, :sub, :rstrip, :scan, :chomp, :rjust, :lstrip, :chop!, :delete_prefix, :chop, :sub!, :gsub!, :delete_prefix!, :chomp!, :strip!, :lstrip!, :rstrip!, :squeeze, :delete_suffix!, :tr, :tr_s, :delete, :each_line, :tr!, :tr_s!, :delete!, :squeeze!, :slice, :each_byte, :each_char, :each_codepoint, :each_grapheme_cluster, :b, :slice!, :rpartition, :encoding, :force_encoding, :valid_encoding?, :hash, :unicode_normalized?, :encode, :clamp, :between?, :<=, :>=, :>, :<, :dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :clone, :display, :class, :singleton_class, :method, :public_send, :public_method, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :!~, :nil?, :respond_to?, :object_id, :send, :__send__, :!, :__id__, :!=, :equal?, :instance_eval, :instance_exec] 

現在我們來把幫 String 類別加上一個 say_hello 方法:

class String
  def say_hello
    "晚安, 接下來由#{self}來帶您學習 Ruby~"
  end
end

"娜美".say_hello 
=> "晚安, 接下來由娜美來帶您學習 Ruby~" 

"布魯克".say_hello 
=> "晚安, 接下來由布魯克來帶您學習 Ruby~" 

這樣的話,剛剛 a.methods 列出來的方法裡,就會多一個我們剛剛定義的 sayhello 方法囉!

另外,在這裡的 self 指的就是呼叫方法的 receiver 本身。


Monkey Patch! 既有的方法居然也能改!

除了像剛剛那樣幫類別添加方法,我們甚至也能覆蓋掉 Ruby 原先寫好的方法,這樣的行為通常被稱作 "Monkey Patch",這就是 Ruby 語言的一大特點:可動態修改

1 + 1 = 2 是數學上顛撲不破的真理,但我們來看個例子:

1 + 1
=> 2

上次有提過在 Ruby 裡,其實是這樣運作:

1.+(1)
=> 2

當我們知道其實是 1 這個物件呼叫了 + 方法,再帶入 1 這個參數後,就可以自己來改寫 + 方法:

1.class
=> Integer

class Integer
  def +(n)
    return n
  end
end

我讓 + 這個方法變成只會回傳加上去的數字,所以這時候再使用 + 的話,就會變成:

puts 1 + 1
puts 2 + 1
puts 3 + 1

#印出
1
1
1

得到的通通都是 1 了!數學已死!!!


今天終於來到 20 天了!開放類別就先介紹到這邊啦!後面會再提到類似的概念可能就要到淺談 Ruby 物件導向的篇章了!希望能順利完賽!

最後送大家一句金句:
投資一定有風險基金投資有賺有賠使用 Monkey Patch 前應詳閱 Ruby 官方文件說明書

話已帶到,使用時請小心、保重!


上一篇
Proc & Lambda <> 通通都拿出來變成實用的武器吧! - 滿滿的紅寶石不拿嗎?
下一篇
實體變數 <> 一流強者都要會的武裝色霸氣 - 滿滿的紅寶石不拿嗎?
系列文
滿滿的紅寶石不拿嗎?-- 去吧!我把世界上的一切都放在那裡了! 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言