Ruby Class

Ruby 教學 10 – Ruby Class 介紹

作為一個 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 的宣告方式:

  1. 寫在 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
  1. 寫在 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 | 高見龍

如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top