October 18, 2023

Viiisit [Ruby] - Class & Module!

#interview#ruby

Viiisit Ruby - Ruby 的物件導向程式語言基礎概念!
有提到 Ruby 是一個物件導向的程式語言,在 Ruby 中,所有的東西幾乎都是物件,為什麼說是幾乎?
因為像之前有一篇 Viiisit Ruby on Rails - Scope 前情提要 Block、Proc 和 Lambda!
裡頭有說明 Block 就不是物件!

那麼在物件導向程式裡的特色就是物件,什麼是物件?

物件(Object)是什麼?

物件(object) = 狀態(state) + 行為(behavior)

在現實生活中,你我他,看得到、摸得到的都可通稱為之物件(Object)。
物件會有狀態跟行為,例如我有「黑色頭髮」、「黃色皮膚」等狀態,也有「吃飯」、「睡覺」等行為。

而,我們要如何在物件導向程式語言定義這些狀態與行為?

就要來開啟認識類別(Class)之路了!

類別(Class)是什麼?

類別是一個程式碼的模板,用來建立具體的物件(實例)。

可以透過關鍵字 class 來定義類別!

初始化

初始化通常是透過一個特殊的方法稱為 initialize 來執行的,
這個方法會在建立該類別的實例時被呼叫。
當一個新的實例被創建時,initialize 方法用於設置實例的初始狀態。

1
2
3
4
5
6
7
8
9
10
class Person
def initialize(name, age)
@name = name
@age = age
end
end

# 建立一個 Person 實例
person1 = Person.new("Alice", 30)
person2 = Person.new("Bob", 25)

initialize 方法接受兩個參數 nameage
並使用這些參數來設置 Person 實例的初始狀態。
當我們建立 Person 實例時,我們傳遞相應的參數給 new 方法,這將觸發 initialize 方法。

一旦實例被初始化,我們可以使用實體變數(instance variables)來存儲和訪問實例的狀態。
在 Ruby 中,實體變數以 @ 符號開頭,並且可以在整個類別的方法中訪問。

如何使用實體變數?

Ruby 的實體變數沒辦法直接從外部取用,
因為實例變數的封裝性以及 Ruby 並沒有「屬性」(property/attribute)這樣的設計,
因此只允許通過方法來取用實體變數!

定義兩個方法 get_nameget_age
允許我們從 Person 實例中訪問 @name@age 實體變數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
def initialize(name, age)
@name = name
@age = age
end

def get_name
@name
end

def get_age
@age
end
end

person = Person.new("Alice", 30)

puts "Name: #{person.get_name}" # 輸出:Name: Alice
puts "Age: #{person.get_age}" # 輸出:Age: 30

attr_ 系列讓你輕鬆建立屬性!

前面提及每次要這樣取用或設定實體變數的值都要建立方法,好像有點麻煩?

別擔心!Ruby 的 attr_ 系列方法可以簡化實例變數的訪問和設置,
這些方法可以自動創建 getter 和 setter 方法,以允許對實例變數的訪問!


實體方法與類別方法


方法的存取控制

類別的方法存取限制常見的主要有三種:publicprotected 以及 private

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Animal
def initialize(name)
@name = name
end

# 公開方法
def speak
puts "#{@name} 發出聲音。"
end

# 保護方法
protected

def sleep
puts "#{@name} 正在休息。"
end

# 私有方法
private

def eat
puts "#{@name} 正在進食。"
end
end

class Dog < Animal
def wag_tail
# 子類別可以訪問保護方法
sleep
puts "#{@name} 開心地搖尾巴。"
end

def feed
# 子類別可以訪問私有方法
eat
puts "#{@name} 吃得很開心。"
end
end

animal = Animal.new("動物")
animal.speak # 動物 發出聲音。

dog = Dog.new("狗")
dog.speak # 狗 發出聲音。
dog.wag_tail # 狗 開心地搖尾巴。
dog.feed # 狗 吃得很開心。

定義一個 Animal 類別,
有一個公開方法 speak、一個保護方法 sleep 和一個私有方法 eat
然後,建立一個 Dog 類別,
他是繼承自 Animal 類別的子類別,並且可以訪問父類別中的保護和私有方法。

公開方法 speak 可以從任何地方訪問,
保護方法 sleep 可以在 Dog 類別中訪問,而私有方法 eat 也可以在 Dog 類別中訪問。

模組(Module)是什麼?

在 Ruby 中,模組(Modules)在某種程度上類似於類別(Classes),
他們都可以包含方法,就像類別一樣。

然而,模組無法被實例化。也就是說,無法從模組建立物件。
因此,模組不像類別一樣具有 new 方法。
除此之外,模組也無法像類別有繼承的效果,也無法建立子類別。

那,模組實際上的作用到底是什麼?

模組的作用

透過 module 關鍵字來定義模組!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Greetable

A = "Hi!"

def greet
"Hello!"
end
end

class Person
include Greetable
end

puts Greetable::A # Hi!

amy = Person.new
puts amy.greet # Hello!

模組(module) 可以透過雙冒號運算符 (::) 來訪問模組中常數的值

1
puts Greetable::A # Hi!

模組(module)使用 def 關鍵字定義一個方法,即 def method_name,則該方法為實例方法。
我們無法直接使用點運算符(.)呼叫實例方法,因為無法建立模組(module)的實例。

為了呼叫模組(module)內定義的實例方法,
必須將該模組(module)包含(include)在一個類別(class)中,
然後使用類別(class)實例來訪問該方法。

1
2
amy = Person.new
puts amy.greet # Hello!

使用 include 關鍵字在類別(class)中包含模組(module),
這種情況下,模組(module)的作用類似於一個 namespace

1
2
3
4
5
6
class Person
include Greetable
end

amy = Person.new
puts amy.greet # Hello!

補充:Namespace 用意

Namespace 是一個在程式設計中常用的概念,用於區分和組織變數、函數、類別和其他命名實體,
以避免命名衝突,確保代碼的結構化和可讀性。

當在類別(class)中使用 include 關鍵字將一個模組(module)包含在其中時,
這個模組(module)的內容變成該類別(class)的一部分,
可以在該類別(class)中訪問模組(module)中的方法和變數,同時避免了命名衝突。
這就是命名空間的一個實際應用場景。

include vs extend

在 Ruby 中,includeextend 是用於引入模組(module)的兩種不同方法。

簡而言之,include 用於向類別(class)加上的實例方法,
extend 用於向類別(class)本身添加方法,這些方法在類別(class)級別上可用。

透過簡單的舉例,來看看 includeextend 的實際應用吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Greeting
def say_hello
puts "Hello!"
end
end

class Person
include Greeting
attr_reader :name

def initialize(name)
@name = name
end
end

person = Person.new("Alice")
puts person.say_hello # 可以在實例上調用 Greeting 模組中的方法 # Hello!

include GreetingGreeting 模組的方法加到 Person 類的實例中。
因此,person 實例可以調用 say_hello 方法。

1
2
3
4
5
6
7
8
9
10
11
12
module MathFunctions
def square(num)
num * num
end
end

class Calculator
extend MathFunctions
end

result = Calculator.square(5) # 可以在類別級別調用 MathFunctions 模組中的方法
puts result # 25

extend MathFunctionsMathFunctions 模組的方法
加上 Calculator 類別的類別方法。
因此,我們可以直接在類別調用 Calculator.square 方法,而不需要建立類別的實例。

Brief Summary


參考資料:
Ruby | Module
類別(Class)與模組(Module)