在 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 | class Person |
initialize
方法接受兩個參數 name
和 age
,
並使用這些參數來設置 Person
實例的初始狀態。
當我們建立 Person
實例時,我們傳遞相應的參數給 new
方法,這將觸發 initialize
方法。
一旦實例被初始化,我們可以使用實體變數(instance variables)來存儲和訪問實例的狀態。
在 Ruby 中,實體變數以 @
符號開頭,並且可以在整個類別的方法中訪問。
如何使用實體變數?
Ruby 的實體變數沒辦法直接從外部取用,
因為實例變數的封裝性以及 Ruby 並沒有「屬性」(property/attribute)這樣的設計,
因此只允許通過方法來取用實體變數!
定義兩個方法 get_name
和 get_age
,
允許我們從 Person
實例中訪問 @name
和 @age
實體變數。
1 | class Person |
attr_ 系列讓你輕鬆建立屬性!
前面提及每次要這樣取用或設定實體變數的值都要建立方法,好像有點麻煩?
別擔心!Ruby 的 attr_
系列方法可以簡化實例變數的訪問和設置,
這些方法可以自動創建 getter 和 setter 方法,以允許對實例變數的訪問!
attr_reader
:用於建立 getter 方法,允許訪問實例變數的值。1
2
3
4
5
6
7
8
9
10class Person
attr_reader :name
def initialize(name)
@name = name
end
end
person = Person.new("Alice")
puts person.name # 可以訪問實例變數 @nameattr_writer
:用於建立 setter 方法,允許設置實例變數的值。1
2
3
4
5
6
7
8
9
10class Person
attr_writer :name
def initialize(name)
@name = name
end
end
person = Person.new("Alice")
person.name = "Bob" # 可以設置實例變數 @name 的值attr_accessor
:用於同時建立 getter 和 setter 方法。1
2
3
4
5
6
7
8
9
10
11class Person
attr_accessor :name
def initialize(name)
@name = name
end
end
person = Person.new("Alice")
puts person.name # 可以訪問實例變數 @name
person.name = "Bob" # 可以設置實例變數 @name 的值
實體方法與類別方法
實體方法(Instance Methods):
- 實體方法是與類別的實例(物件)關聯的方法,可以訪問和操作實例變數,並且通常用於對特定實例執行操作或訪問其狀態。
- 實體方法通常定義在類別中,並且通過類別的實例來呼叫。這些方法在操作實例時具有存取實例變數的能力,因為有關聯到特定的實例。
- 在定義實體方法時,你可以使用
def
關鍵字,而不需要特殊的修飾詞。
1
2
3
4
5
6
7
8
9
10
11
12class Person
def initialize(name)
@name = name
end
def greet
"Hello, my name is #{@name}."
end
end
person = Person.new("Alice")
puts person.greet # 輸出:Hello, my name is Alice.類別方法(Class Methods):
- 類別方法是與類別本身關聯的方法,而不是與實例相關聯。通常用於執行與整個類別相關的操作,而不是與單個實例相關。
- 類別方法在類別級別上定義,通常使用
self.
關鍵字,或者類別名稱來定義。 - 類別方法可以在不需要實例的情況下呼叫,因為它們與類別本身關聯,而不是與實例變數關聯。
1
2
3
4
5
6
7
8
9
10
11
12class MathUtility
def self.square(x)
x * x
end
def self.cube(x)
x * x * x
end
end
puts MathUtility.square(2) # 輸出:4
puts MathUtility.cube(3) # 輸出:27
方法的存取控制
類別的方法存取限制常見的主要有三種:public
、protected
以及 private
。
public
方法:在類別內部和外部都可訪問,通常用於定義對外部世界可見的介面,以便外部程式碼可以直接訪問這些方法。private
方法:只能在定義他們的類別內部訪問,通常用於實現類別的內部邏輯,以避免外部程式碼干擾或誤用這些方法。protected
方法:類似於私有方法,但可以在同一個類別的不同實例之間訪問,通常用於實現特定的類別內部協作,例如當不同實例需要互相訪問彼此的某些方法時。
1 | class Animal |
定義一個 Animal
類別,
有一個公開方法 speak
、一個保護方法 sleep
和一個私有方法 eat
。
然後,建立一個 Dog
類別,
他是繼承自 Animal
類別的子類別,並且可以訪問父類別中的保護和私有方法。
公開方法 speak
可以從任何地方訪問,
保護方法 sleep
可以在 Dog
類別中訪問,而私有方法 eat
也可以在 Dog
類別中訪問。
模組(Module)是什麼?
在 Ruby 中,模組(Modules)在某種程度上類似於類別(Classes),
他們都可以包含方法,就像類別一樣。
然而,模組無法被實例化。也就是說,無法從模組建立物件。
因此,模組不像類別一樣具有 new
方法。
除此之外,模組也無法像類別有繼承的效果,也無法建立子類別。
那,模組實際上的作用到底是什麼?
模組的作用
透過 module
關鍵字來定義模組!
1 | module Greetable |
模組(module) 可以透過雙冒號運算符 (::) 來訪問模組中常數的值
1 | puts Greetable::A # Hi! |
模組(module)使用 def
關鍵字定義一個方法,即 def method_name
,則該方法為實例方法。
我們無法直接使用點運算符(.)呼叫實例方法,因為無法建立模組(module)的實例。
為了呼叫模組(module)內定義的實例方法,
必須將該模組(module)包含(include)在一個類別(class)中,
然後使用類別(class)實例來訪問該方法。
1 | amy = Person.new |
使用 include
關鍵字在類別(class)中包含模組(module),
這種情況下,模組(module)的作用類似於一個 namespace
。
1 | class Person |
補充:Namespace 用意
Namespace
是一個在程式設計中常用的概念,用於區分和組織變數、函數、類別和其他命名實體,
以避免命名衝突,確保代碼的結構化和可讀性。
當在類別(class)中使用
include
關鍵字將一個模組(module)包含在其中時,
這個模組(module)的內容變成該類別(class)的一部分,
可以在該類別(class)中訪問模組(module)中的方法和變數,同時避免了命名衝突。
這就是命名空間的一個實際應用場景。
include vs extend
在 Ruby 中,include
和 extend
是用於引入模組(module)的兩種不同方法。
include
:- 使用
include
關鍵字時,模組(module)的方法被引入到類別(class)的實例中。 - 引入模組(module)的方法可以直接在類別(class)的實例上調用,
並且這些方法對該實例的每個對象都有效。
- 使用
extend
:- 使用
extend
關鍵字時,模組(module)的方法被引入為類別方法,而不是實例方法。 - 引入模組(module)的方法只能在類別(class)本身上調用,
而無法在該類別(class)的實例上調用。
- 使用
簡而言之,include
用於向類別(class)加上的實例方法,
而 extend
用於向類別(class)本身添加方法,這些方法在類別(class)級別上可用。
透過簡單的舉例,來看看 include
與 extend
的實際應用吧!
- 使用
include
:
1 | module Greeting |
include Greeting
將 Greeting
模組的方法加到 Person
類的實例中。
因此,person
實例可以調用 say_hello
方法。
- 使用
extend
:
1 | module MathFunctions |
extend MathFunctions
將 MathFunctions
模組的方法
加上 Calculator
類別的類別方法。
因此,我們可以直接在類別調用 Calculator.square
方法,而不需要建立類別的實例。
Brief Summary
物件(Object)
- Ruby 是一個物件導向的程式語言,其中幾乎所有東西都是物件,但 Block 不是物件。
- 物件(Object)在 Ruby 中被定義為擁有狀態(state)和行為(behavior)的實體,
就像生活中的事物一樣。
類別(Class)
- 類別(Class)是一個建立具體的物件(實例)模板。
可以透過class
關鍵字來定義類別,並使用initialize
方法來初始化物件的狀態。 - 在 Ruby 中,有不同的方法來訪問和設置實例變數:
attr_reader
、attr_writer
和attr_accessor
。 - 方法可分為實體方法和類別方法,實體方法是與類別的實例相關聯的,
而類別方法是與類別本身相關聯的。 - 方法具有不同的存取控制,包括
public
、protected
和private
,
以控制它們的可見性和存取權限。
- 類別(Class)是一個建立具體的物件(實例)模板。
模組(Module)
- 模組(Module)類似於類別,但不能被實例化,
並且通常用於將方法和常數組織在一個命名空間(namespace)中,以避免命名衝突。 include
用於將模組的方法添加到類別的實例中,
而extend
用於將模組的方法添加為類別方法。
- 模組(Module)類似於類別,但不能被實例化,
參考資料:
➫ Ruby | Module
➫ 類別(Class)與模組(Module)