模組 <> 想得到夢寐以求的惡魔果實能力嗎?先裝一個吧!

https://ithelp.ithome.com.tw/upload/images/20200921/20128363w0Jr8PDGKz.jpg

[Day12] 啊你說這個不能吃是嗎?


哈囉!今天要跟大家介紹的是**模組(Module)**。

在 Ruby 的世界裡,模組是一個特殊又好用的東西,它就跟大名鼎鼎「惡魔果實」一樣,只要吃下去就能獲得那顆果實的特殊能力(完全是個不用靠爸就能成為主角的神奇道具啊!)

在理解模組時,可以想成是它加裝了一個東西原本沒有的功能,大概就像這樣:

這真是太神奇了傑克!

有了模組,我們就不需要使用昨天介紹到的類別繼承,那麼,要如何做到這件事呢?


include

請先詳閱以下使用說明書:

先定義一個模組 Floatable
模組的命名規則就和類別一樣,也必須是大字英文字母開頭的常數。

1
2
3
4
5
module Floatable
def float
puts "浮起來了!"
end
end

再定義一個 Bicycle 的類別:

1
2
class Bicycle
end

產生一個 your_bike 實體,然後使用 float 方法試試看:

1
2
3
4
your_bike = Bicycle.new
your_bike.float

=> NoMethodError (undefined method `float' for #<Bicycle:0x00007fd90901ba28>)

居然失敗了!為什麼?
因為沒有在腳踏車上裝上可以浮起來的氣墊啊!怎麼可能會成功!

— 需要在類別裡使用 include 來裝模組!—

Bicycle 類別裝上剛剛做好的 Floatable 模組吧!

1
2
3
class Bicycle
include Floatable # 引入模組
end

重新產生一個 my_bike 實體,然後使用 float 方法:

1
2
3
4
5
my_bike = Bicycle.new
my_bike.float

# 印出
浮起來了!

嘿嘿~現在我的腳踏車可以浮在水面上了!


extend

接著,我們再來看看更神奇的 extend

假如你今天有一顆引擎的模組,然後把它裝在腳踏車上,腳踏車從此以後就可以用引擎運轉前進了!不過,如果想把腳踏車改造成氮氣噴射的功能,變成阿姆斯特朗旋風噴射阿姆斯特朗砲 的話呢?

這時候就會用上讓模組擴充模組的 extend 了!
廢話說完了,接著來看 code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module Engine
def run
puts "全速啟動!"
end
end

module Acceleration
def accelerate
puts "氮氣噴射!!!"
end
end

class Bike
include Engine
extend Acceleration
end

max = Bike.new

max.run

# 印出
全速啟動!

Bike.accelerate
# 印出
氮氣噴射!!!

看出來了嗎? 原本的 Engine 模組在被 include 後,只是讓腳踏車可以用引擎運轉,但 Acceleration 模組被 extend 後則徹底改造了腳踏車這個類別,讓它完全升級為一種不同的交通工具了(誤)

但如果讓 max 呼叫 accelerate 方法或是讓 Bike 呼叫 run

1
2
3
4
5
6
7
max.accelerate
# 印出
NoMethodError (undefined method `accelerate' for #<Bike:0x00007fec3f2aaf70>)

Bike.run
# 印出
NoMethodError (undefined method `run' for Bike:Class)

就都找不到方法了,所以…

說人話版本 =>

1
2
Bike.accelerate  # 類別方法 / 用 extend 引入
Bike.new.run # 實體方法 / 用 include 引入

明天會再介紹什麼是類別方法和實體方法!(挖坑)


聽說模組和類別很像?

是的,他們很像。
譬如名字都是大寫字母開頭、裡面會放一些方法。在專案裡,如果只看到一個大寫開頭的常數,並沒有辦法確定現在看到的是模組還是類別!不過,它們還是有一些明顯的差異。

舉個例子,可能很多人在純真的童年時會夢想像鳥類一樣在天上飛,在 20 世紀初,萊特兄弟發明了可以載人的飛機,但直到今天,人們仍然不算是學會了飛行。

如果真的要讓人類可以飛,該怎麼做?

首先,鳥類這個類別應該會有一個 fly 的方法,

1
2
3
4
5
class Bird
def fly
# 可以飛行的秘密
end
end

然後繼承它?
class Human < Bird
end
等等!我們的祖先又不是鳥類,所以不應該使用類別繼承吧!

那掛載一個會可以讓人飛行的模組?

1
2
3
4
5
6
7
8
9
module Flyable
def fly
# 可以飛行的秘密
end
end

class Human
intend Flayable
end

我們終於成功裝上了 Flyable !從此以後,人類就可以飛了!


ClassModule 的方法數量

瞎扯了一堆,接著我們直接進程式裡看看:

1
2
3
4
5
6
7
# 查詢 Class 和 Module 的方法和數量

p Module.instance_methods.count
109

p Class.instance_methods.count
112

instance_methods.count 可以幫我們計算一個類別能使用的方法有多少,可以發現雖然兩者的方法都很多,但 ClassModule 硬是多出了 3 個方法!

來看看多出的是哪幾個:

1
2
p Class.instance_methods(false)
[:new, :allocate, :superclass]

由於 Module 少了 :new, :allocate, :superclass 這些方法 ,所以:

  1. 模組沒辦法 new 一個新的實體。
  2. 模組也 無法繼承 別的模組。
1
2
3
4
m = Module.new

m
=> #<Module:0x00007f97268b6310>
1
2
3
m.new

=> NoMethodError (undefined method `new' for #<Module:0x00007f97268b6310>)

Namespace::

namespace?這又是什麼?想像一下,我的車有一顆引擎,你的車也有一顆引擎,但你開的是 BMW 旗艦 V12 雙門跑車,我開的是 Toyota Camry (Camry 錯了嗎?),那這兩輛車會是同樣的車嗎?很明顯不是!(怒)

那要怎麼解決這種問題呢,簡單來說,namespace 就是為了避免叫到同名類別,導致程式無法判讀的問題。我們在呼叫類別時,可以連名帶姓地呼叫:

譬如專案裡常常會看到的:

1
2
3
4
5
6
7
8
9
# 類別(模組)::類別

class User < ActiveRecord::Base
end

class A
module B
end
end

如此一來,我們就能很清楚地知道 User 類別是繼承自 Base 類別,而這個 Base 類別是屬於 ActiveRecord 這個模組(或類別)裡的,其他地方如果有叫 Base 的類別都不是!

有興趣的朋友可以看看龍哥的這篇大作:
Ruby 語法放大鏡之「有時候會看到有兩個冒號寫法是什麼意思?」


說了這麼多,今天就先到這邊囉!
相信大家應該都認識模組了!明天見囉!

https://ithelp.ithome.com.tw/upload/images/20200921/20128363XrK019kUJO.jpg
旗木卡卡西:是不是忘了介紹我?寫輪眼才是最強的模組啊!