開放類別 <> 這個星期的我很可以!不管裡面有什麼都給我來一點吧!

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

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

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


類別—合體!

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

1
2
3
4
5
6
7
8
9
10
11
class Robot
def eye
puts "發射超強激光!"
end
end

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

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

1
franky = Robot.new

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

1
2
3
4
5
6
franky.eye
franky.arm

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

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

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

1
2
3
4
5
6
7
8
9
class Robot
def eye
puts "發射超強激光!"
end

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

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


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

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

1
2
3
4
class Robot
end

franky = Robot.new

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

還是來確認一下好了:

1
2
3
4
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 是什麼類別?

1
2
franky.class
=> Robot

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

Q2: franky 是整數嗎?

1
2
franky.is_a?(Integer)
=> false

好我知道很明顯不是…

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

1
franky.methods

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

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


讓我們來加點料

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

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

1
2
3
4
5
6
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 方法:

1
2
3
4
5
6
7
8
9
10
11
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
2
1 + 1
=> 2

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

1
2
1.+(1)
=> 2

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

1
2
3
4
5
6
7
8
1.class
=> Integer

class Integer
def +(n)
return n
end
end

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

1
2
3
4
5
6
7
8
puts 1 + 1
puts 2 + 1
puts 3 + 1

#印出
1
1
1

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


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

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

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