作為一個 OOP 的程式語言,Class 可說是構成 Ruby 的核心,所有的 objects 都是由 Class new 出來的 instance,這篇就來介紹和整理 Ruby class 的特性吧。
1. 宣告 Ruby Class 及 Object Instance
1.1 命名規則
Ruby class 的命名必須是常數,也就是說第一個字母須為大寫,否則會報錯:
class teamMember
end
# class/module name must be CONSTANT class teamMember
class TeamMember
end
# OK
1.2 Class.new
宣告完 class 後,可以用 class.new 建立一個新的 object instance,每個 object instance 的記憶體位置都不同:
class TeamMember
end
member1 = TeamMember.new
member2 = TeamMember.new
member3 = TeamMember.new
p member1
# #<TeamMember:0x0000000102e7ed68>
p member2
# #<TeamMember:0x0000000102e7ec50>
p member3
# #<TeamMember:0x0000000102e7eb10>
p member1.object_id
# 60
p member2.object_id
# 80
p member3.object_id
# 100
1.3 Initialize Method
Initialize 是 class 中一個特殊的 method,當有 object 是透過這個 class new 出來的時候,會執行這個 method:
class TeamMember
def initialize
p "This is a object instance from TeamMember class"
end
end
member1 = TeamMember.new
member2 = TeamMember.new
member3 = TeamMember.new
# "This is a object instance from TeamMember class"
# "This is a object instance from TeamMember class"
# "This is a object instance from TeamMember class"
2. Variables
2.1 Instance Variables
Instance variables 顧名思義只存在於 object instance,也就是每個 object instance 的 instance variables 都是互相獨立的,instance variables 會在開頭加上 @ ( sigil symbol ) 表示 ( 相當於 JavaScript 的 property ),並且在 initialize method 裡宣告。
class TeamMember
def initialize(name, age)
@name = name
@age = age
p "Name of this member is #{@name}, age is #{@age}"
end
end
member1 = TeamMember.new("Jimmy", 25)
# "Name of this member is Jimmy, age is 25"
member2 = TeamMember.new("Sam", 20)
# "Name of this member is Sam, age is 20"
member3 = TeamMember.new("Eric", 20)
# "Name of this member is Eric, age is 20"
然而,宣告完 instance variables 後會發現沒辦法直接從 instance object 存取到 instance variable:
member1 = TeamMember.new("Jimmy", 25)
p member1.name
# undefined method `name' for #<TeamMember:0x0000000101056a20 @name="Jimmy", @age=25>
這是因為 Ruby 嚴格遵守 OOP 的一個特性 – Encapsulation ( 封裝 ),需要另外宣告 getter method 來存取 instance variables。
2.1.1 getter method
如同上述提到的,因為無法直接存取 instance object 的 instance variables,因此需要宣告 getter method:
class TeamMember
def initialize(name, age)
@name = name
@age = age
end
# getter method
def name
@name
end
def age
@age
end
end
member1 = TeamMember.new("Jimmy", 25)
p member1.name
# "Jimmy"
p member1.age
# 25
2.1.2 setter method
因為 Encapsulation 的特性,當 instance object 已經被建立後,要修改 instance variables 時則需要宣告 setter method:
class TeamMember
def initialize(name, age)
@name = name
@age = age
end
# getter method
def name
@name
end
def age
@age
end
# setter method
def name=(name)
@name = name
end
def age=(age)
@age = age
end
end
member1 = TeamMember.new("Jimmy", 25)
member1.name = "Sam"
member1.age = 20
p member1.name
# "Sam"
p member1.age
# 20
2.1.3 attr_accessor, attr_reader, attr_writer
Ruby 提供了 getter method 和 setter method 的簡寫:
- attr_accessor:代表這些 instance variables 同時宣告了 getter 和 setter
- attr_reader:代表這些 instance variables 只有宣告 getter
- attr_writer:代表這些 instance variables 只有宣告 setter
當有多個 instance variables 要放在 attr 裡時,可以用 , 隔開,ex: attr_accessor :name, :age
class TeamMember
attr_accessor :name
attr_reader :age
attr_writer :city
def initialize(name, age, city)
@name = name
@age = age
@city = city
end
end
member1 = TeamMember.new("Jimmy", 25, "Taipei")
p member1.name
# "Jimmy"
member1.name = "Sam"
p member1.age
# 25
member1.age = 30
# undefined method `age=' for #<TeamMember:0x0000000102a39d20 @name="Sam", @age=25, @city="Taipei">
p member1.city
# undefined method `city' for #<TeamMember:0x0000000100d5de40 @name="Sam", @age=25, @city="Taipei">
member1.city = "Yilan"
2.2 Class Variables
Class variables 存在於 class 上,也就是說所有從這個 class 建立的 object instance 共用 class variables。
class variables 用 @@ 表示:
class TeamMember
@@count = 0
def initialize(name)
@name = name
@@count += 1
end
# getter method of class variables
def count
@@count
end
end
member1 = TeamMember.new("Jimmy")
member2 = TeamMember.new("Sam")
member3 = TeamMember.new("Eric")
p member1.count
# 3
attr_accessor, attr_reader, attr_writer 只適用於 instance variables,不適用於 class variables,因此要存取或修改 class variables 需要用一般 getter 和 setter 的寫法
3. Methods
3.1 Instance methods
只要在 class 中用一般的方式宣告 methods,即為 instance methods。Instance methods 有兩個特性:
- 只能在已經建立的 object instance 上執行
- 無法直接在 class 上執行
只有被建立的 object instance 可以執行,無法直接在 class 上執行:
class TeamMember
def initialize(name, age)
@name = name
@age = age
end
def description
p "#{@name} is #{@age} years old"
end
end
member1 = TeamMember.new("Jimmy", 25)
member1.description
# "Jimmy is 25 years old"
TeamMember.description
# undefined method `description' for TeamMember:Class
3.2 Class Methods
只要在一般 method 的名字前面加上 self.,即為 class methods。Class methods 的特性和 instance methods 相反:
- 無法在已經建立的 object instance 上執行
- 只能在 class 上執行
class TeamMember
def initialize(name, age)
@name = name
@age = age
end
def self.description
p "this is the class method"
end
end
member1 = TeamMember.new("Jimmy", 25)
member1.description
# undefined method `description' for #<TeamMember:0x0000000104cc6550 @name="Jimmy", @age=25>
TeamMember.description
# "this is the class method"
4. Public, Private, Protected
Public, Private, Protected 是用來決定 class 裡 methods 的存取權限,如果沒有特別指定的話,定義的 methods default 為 public。
4.1 Public
只要沒有特別指定的話,在 class 內宣告的 methods 都是 public,可以在 class 內部或是外部 ( 建立 instance object 後 ) 被執行:
class DemoClass
def public_method
p "I am a public method"
end
# 可以在 class 內部執行 public methods
def public_method_call_by_class
public_method
end
end
demoInstance = DemoClass.new
# 可以在 class 外部執行 public methods
demoInstance.public_method
# "I am a public method"
demoInstance.public_method_call_by_class
# "I am a public method"
4.2 Private
4.2.1 宣告 private methods
private methods 和 public methods 最大的差別在於 private methods 只能在 class 內部執行,在這之前先來説說 private methods 的宣告方式:
- 寫在 methods 前面:在 class 內部一旦出現 private,則 private 後所有宣告的 methods 皆為 private methods:
class DemoClass
private
def private_method_1
p "I am private method 1"
end
def private_method_2
p "I am private method 2"
end
end
demoInstance = DemoClass.new
demoInstance.private_method_1
# NoMethodError
demoInstance.private_method_2
# NoMethodError
- 寫在 class 內部的最後
class DemoClass
def private_method_1
p "I am private method 1"
end
def private_method_2
p "I am private method 2"
end
private :private_method_1, :private_method_2
end
demoInstance = DemoClass.new
demoInstance.private_method_1
# NoMethodError
demoInstance.private_method_2
# NoMethodError
4.2.2 可以在 class 內部被其他 methods 執行
誠如上面提到的,private methods 只能在 class 內部被其他 methods 執行,無法在 object instance 上直接執行:
class DemoClass
def private_method_call_by_class
private_method
end
private
def private_method
p "I am a private method"
end
end
demoInstance = DemoClass.new
demoInstance.private_method
# NoMethodError
demoInstance.private_method_call_by_class
# "I am a private method"
4.2.3 可以被繼承的 class 內部執行
Ruby 的 private methods 和其他程式語言比較不一樣,可以被繼承的 class 所執行:
class DemoClass
def private_method_call_by_class
private_method
end
private
def private_method
p "I am a private method"
end
end
# < 符號為繼承,會繼承 < 右邊的 class
class ChildClass < DemoClass
def private_method_call_by_child_class
private_method
end
end
childInstance = ChildClass.new
childInstance.private_method_call_by_child_class
# "I am a private method"
4.3 Protected
4.3.1 宣告 Protected Methods
Protected methods 的宣告方法和 private methods 一樣:
class DemoClass
protected
def protected_method_1
p "I am protected method 1"
end
def protected_method_2
p "I am protected method 2"
end
end
或是:
class DemoClass
def protected_method_1
p "I am protected method 1"
end
def protected_method_2
p "I am protected method 2"
end
protected :protected_method_1, :protected_method_2
end
4.3.2 可以在 class 內部被其他 methods 執行
class DemoClass
def protected_method_call_by_class
protected_method
end
protected
def protected_method
p "I am a protected method"
end
end
demoInstance = DemoClass.new
demoInstance.protected_method
# NoMethodError
demoInstance.protected_method_call_by_class
# "I am a protected method"
4.3.3 可以被繼承的 class 內部執行
class DemoClass
def protected_method_call_by_class
protected_method
end
protected
def protected_method
p "I am a protected method"
end
end
class ChildClass < DemoClass
def protected_method_call_by_child_class
protected_method
end
end
classInstance = ChildClass.new
classInstance.protected_method_call_by_child_class
# "I am a protected method"
4.4 Private v.s. Protected
在其他的程式語言中,即使是在繼承的 class 內部中,也沒辦法執行 superclass 的 private methods,但在 Ruby 中卻可以。在我目前查到的資料顯示:這個特別的設計讓 private methods 和 protected methods 在 Ruby 中只有一個差異:在 call methods 時有沒有 receiver。
然而可能是在某個 Ruby 主版本更新的時候把這個差異拿掉了,目前我測試,即便在 private methods 執行時加上 receiver 仍不會報錯:
class DemoClass
def protected_method_call_by_class
self.protected_method
end
def private_method_call_by_class
self.private_method
end
protected
def protected_method
p "I am a protected method"
end
private
def private_method
p "I am a private method"
end
end
class ChildClass < DemoClass
def protected_method_call_by_child_class
self.protected_method
end
def private_method_call_by_child_class
self.private_method
end
end
demoInstance = DemoClass.new
childInstance = ChildClass.new
demoInstance.protected_method_call_by_class
# "I am a protected method"
demoInstance.private_method_call_by_class
# "I am a private method"
childInstance.protected_method_call_by_child_class
# "I am a protected method"
childInstance.private_method_call_by_child_class
# "I am a private method"
目前的結論是:在 Ruby 中 private methods 和 protected methods 沒有差別,至於是從哪個版本開始的,等我有空再來查看看。
5. 參考資料
Ruby – Variables, Constants and Literals – Tutorialspoint
What are Getter and Setter methods in Ruby – Full Stack Heroes
Class (Ruby 3.1.2) – Ruby-Doc.org
Public, Protected and Private Method in Ruby | 高見龍
如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!