跳轉到內容

Ruby 程式設計/語法/類

來自華夏公益教科書,開放的書籍,為一個開放的世界

是用於建立 物件 例項的基本模板。一個類由表示內部狀態的變數集合和提供對該狀態進行操作的行為的方法組成。

類定義

[編輯 | 編輯原始碼]

在 Ruby 中,類是使用 class 關鍵字後跟一個名稱來定義的。名稱必須以大寫字母開頭,並且按照慣例,包含多個單詞的名稱將合併在一起,每個單詞首字母大寫,沒有分隔符 (駝峰式命名法)。類定義可能包含方法、類變數和例項變數宣告,以及在讀時在類上下文中執行的方法呼叫,例如 attr_accessor。類宣告以 end 關鍵字結束。

示例

  class MyClass
    def some_method
    end
  end

例項變數

[編輯 | 編輯原始碼]

例項變數為每個類例項建立,並且只能在該例項中訪問。它們使用 @ 運算子訪問。在類定義之外,只能透過該例項的公有方法讀取或修改例項變數的值。

示例

 
  class MyClass
    @one = 1
    def do_something
      @one = 2
    end
    def output
      puts @one
    end
  end
  instance = MyClass.new
  instance.output
  instance.do_something
  instance.output

令人驚訝的是,這將輸出

 nil
 2


這是因為(第一個輸出行中的 nil)在 class MyClass 之後定義的 @one 是屬於類物件的例項變數(注意,這與類變數不同,不能稱為 @@one),而定義在 do_something 方法內的 @one 是屬於 MyClass 例項的例項變數。它們是兩個不同的變數,第一個變數只能在類方法中訪問。

訪問器方法

[編輯 | 編輯原始碼]

如上一節所述,只能在例項方法定義中直接訪問或修改例項變數。如果您想從外部提供訪問許可權,則需要定義公有訪問器方法,例如

  class MyClass
    def initialize
      @foo = 28
    end

    def foo
      return @foo
    end

    def foo=(value)
      @foo = value
    end
  end

  instance = MyClass.new
  puts instance.foo
  instance.foo = 496
  puts instance.foo

請注意,ruby 提供了一些語法糖,使它看起來像您正在直接獲取和設定變數;在幕後

  a = instance.foo
  instance.foo = b

是對 foo 和 foo= 方法的呼叫

  a = instance.foo()
  instance.foo=(b)

由於這是一個非常常見的用例,因此還有一個方便的方法來自動生成這些 getter 和 setter

  class MyClass
    attr_accessor :foo

    def initialize
      @foo = 28
    end
  end

  instance = MyClass.new
  puts instance.foo
  instance.foo = 496
  puts instance.foo

與上面的程式執行相同操作。attr_accessor 方法在讀取時執行,當 ruby 正在構建類物件時,它會生成 foo 和 foo= 方法。

但是,訪問器方法沒有必要簡單地透明地訪問例項變數。例如,我們可以確保在將所有值儲存在 foo 中之前將它們舍入

  class MyClass
    def initialize
      @foo = 28
    end

    def foo
      return @foo
    end

    def foo=(value)
      @foo = value.round
    end
  end

  instance = MyClass.new
  puts instance.foo
  instance.foo = 496.2
  puts instance.foo #=> 496

類變數

[編輯 | 編輯原始碼]

類變數使用 @@ 運算子訪問。這些變數與類層次結構而不是類的任何物件例項相關聯,並且在所有物件例項中都是相同的。(這類似於 Java 或 C++ 中的類“靜態”變數)。

示例

  class MyClass
    @@value = 1
    def add_one
      @@value= @@value + 1
    end
    
    def value
      @@value
    end
  end
  instanceOne = MyClass.new
  instanceTwo = MyClass.new
  puts instanceOne.value
  instanceOne.add_one
  puts instanceOne.value
  puts instanceTwo.value

輸出

 1
 2
 2

類例項變數

[編輯 | 編輯原始碼]

類可以具有例項變數。這為每個類提供了一個不與繼承鏈中的其他類共享的變數。

  class Employee
    class << self; attr_accessor :instances; end
    def store
      self.class.instances ||= []
      self.class.instances << self
    end
    def initialize name
      @name = name
    end
  end
  class Overhead < Employee; end
  class Programmer < Employee; end
  Overhead.new('Martin').store
  Overhead.new('Roy').store
  Programmer.new('Erik').store
  puts Overhead.instances.size    # => 2
  puts Programmer.instances.size  # => 1

有關更多詳細資訊,請參閱 MF Bliki: ClassInstanceVariables

類方法

[編輯 | 編輯原始碼]

類方法的宣告方式與普通方法相同,只是它們前面有 self 或類名,後跟一個句號。這些方法在類級別執行,可以在沒有物件例項的情況下呼叫。它們不能訪問例項變數,但可以訪問類變數。

示例

  class MyClass
    def self.some_method
      puts 'something'
    end
  end
  MyClass.some_method

輸出

 something

例項化

[編輯 | 編輯原始碼]

透過稱為例項化的過程,從類建立物件例項。在 Ruby 中,這透過類方法 new 來實現。

示例

  anObject = MyClass.new(parameters)

此函式在記憶體中設定物件,然後如果存在,將控制權委託給類的 initialize 函式。傳遞給 new 函式的引數將傳遞到 initialize 函式中。

  class MyClass
    def initialize(parameters)
    end
  end

宣告可見性

[編輯 | 編輯原始碼]

預設情況下,Ruby 類中的所有方法都是公有的,任何人都可以訪問。但是,對於此規則,只有兩個例外:在 Object 類下定義的全域性方法,以及任何類的 initialize 方法。它們都是隱式私有的。

如果需要,可以透過公有、私有、受保護的物件方法來限制方法的訪問許可權。

有趣的是,這些實際上不是關鍵字,而是對類進行操作的實際方法,它們動態地更改方法的可見性,因此,這些“關鍵字”會影響所有後續宣告的可見性,直到設定新的可見性或宣告主體結束。

簡單示例

  class Example
    def methodA
    end
    
    private # all following methods in this class are private, so are inaccessible to outside objects
    
    def methodP
    end
  end

如果 private 在沒有引數的情況下呼叫,它會將所有後續方法的訪問許可權設定為私有。它也可以使用命名引數呼叫。

命名私有方法示例

  class Example
    def methodA
    end
    
    def methodP
    end
    
    private :methodP
  end

這裡 private 使用引數呼叫,並將 methodP 的可見性設定為私有。

請注意,類方法(使用 def ClassName.method_name 宣告)必須使用 private_class_method 函式設定為私有。

private_class_method 的常見用法是使建構函式方法 new 不可訪問,從而強制透過某些 getter 函式訪問物件。典型的 Singleton 實現就是一個明顯的例子

  class SingletonLike
    private_class_method :new
    
    def SingletonLike.create(*args, &block)
      @@inst = new(*args, &block) unless @@inst
      return @@inst
    end
  end

以下是編寫相同宣告的另一種流行方法

  class SingletonLike
    private_class_method :new
    
    def SingletonLike.create(*args, &block)
      @@inst ||= new(*args, &block)
    end
  end

雖然在 C++ 中 private 表示“對該類私有”,但在 Ruby 中它表示“對該例項私有”。這意味著 C++ 允許任何也在該類中的程式碼訪問給定類中任何物件的私有方法。另一方面,在 Ruby 中,私有方法是它們所屬的例項化的物件的本地方法。

private 方法不能使用顯式接收者呼叫的事實可以透過以下程式碼說明。

  class AccessPrivate
    def a
    end
    private :a # a is private method
        
    def accessing_private
      a              # sure! 
      self.a         # nope! private methods cannot be called with an explicit receiver, even if that receiver is "self"
      other_object.a # nope, a is private, so you can't get it (but if it was protected, you could!)
    end
  end

這裡,other_object 是方法 a 被呼叫的“接收者”。對於私有方法,它不起作用。但是,這就是“受保護”可見性允許的。

方法的預設可見性級別是“公有”,可以使用 public 方法手動指定此可見性級別。

我不確定為什麼指定了這一點 - 也許是為了完整性,也許是為了在某個時候可以動態地使某個方法私有,然後 - 公有。

在 Ruby 中,可見性是完全動態的。您可以在執行時更改方法可見性!

受保護

[編輯 | 編輯原始碼]

現在,“受保護”需要更多討論。那些來自 Java 或 C++ 的人已經瞭解到,在這些語言中,如果一個方法是“私有”,它的可見性被限制為宣告類,如果方法是“受保護”,它將可訪問類的子類(繼承自父類的類)或該包中的其他類。

在 Ruby 中,“private” 可見性類似於 Java 中的“protected”。Ruby 中的私有方法可以在子類中訪問。在 Ruby 中,您無法擁有真正私有的方法;您無法完全隱藏方法。

受保護和私有的區別很細微。如果一個方法是受保護的,它可以被定義類或其子類的任何例項呼叫。如果一個方法是私有的,它只能在呼叫物件的上下文中被呼叫——從不可以透過直接訪問另一個物件例項的私有方法,即使該物件與呼叫者是同一個類。對於受保護的方法,它們可以從同一個類(或子類)的物件訪問。

因此,從一個物件“a1”(A 類的一個例項)內部,您只能呼叫“a1”(self)例項的私有方法。並且您不能呼叫物件“a2”(它也是 A 類)的私有方法——它們對 a2 是私有的。但您可以呼叫物件“a2”的受保護方法,因為物件 a1 和 a2 都是 A 類。

Ruby 常見問題解答 給出了以下示例——實現一個運算子來比較一個內部變數與同一個類中的變數(用於比較物件的目的)

  def <=>(other)
    self.age <=> other.age
  end

如果 age 是私有的,此方法將不起作用,因為 other.age 不可訪問。如果“age”是受保護的,這將正常工作,因為 self 和 other 是同一個類,並且可以訪問彼此的受保護方法。

要理解這一點,受保護實際上讓我想起了 C# 中的“internal”訪問修飾符或 Java 中的“default”訪問修飾符(當沒有在方法或變數上設定訪問關鍵字時):方法可以像“public”一樣訪問,但僅限於同一個包內的類。

例項變數

[edit | edit source]

請注意,物件例項變數並不是真正私有的,只是您看不見它們。要訪問例項變數,您需要建立一個 getter 和 setter。

像這樣(不,不要手動這樣做!見下文)

  class GotAccessor
    def initialize(size)
      @size = size
    end
    
    def size
      @size
    end
    def size=(val)
      @size = val
    end
  end
  
  # you could access the @size variable as
  # a = GotAccessor.new(5)
  # x = a.size 
  # a.size = y

幸運的是,我們有專門的函式來做到這一點:attr_accessor、attr_reader、attr_writer。attr_accessor 會為您提供 get/set 功能,reader 僅提供 getter,writer 僅提供 setter。

現在減少到

  class GotAccessor
    def initialize(size)
      @size = size
    end
    
    attr_accessor :size
  end
  
  # attr_accessor generates variable @size accessor methods automatically:
  # a = GotAccessor.new(5)
  # x = a.size 
  # a.size = y

繼承

[edit | edit source]
super 關鍵字只訪問直接父類的方法。不過有一個解決方法。

一個類可以從一個超類繼承功能和變數,有時被稱為父類基類。Ruby 不支援多重繼承,因此 Ruby 中的一個類只能有一個超類。語法如下

  
  class ParentClass
    def a_method
      puts 'b'
    end
  end
  
  class SomeClass < ParentClass  # < means inherit (or "extends" if you are from Java background)
    def another_method
      puts 'a'
    end
  end
  
  instance = SomeClass.new
  instance.another_method
  instance.a_method

輸出

  a
  b

所有非私有變數和方法都由子類從超類繼承。

如果您的類覆蓋了父類(超類)中的方法,您仍然可以使用“super”關鍵字訪問父類的方法。

  class ParentClass
    def a_method
      puts 'b'
    end
  end
  
  class SomeClass < ParentClass  
    def a_method
      super
      puts 'a'
    end
  end
  
  instance = SomeClass.new
  instance.a_method

輸出

  b
  a

(因為 a_method 也呼叫了父類的方法)。

如果您有一個深層繼承線,並且仍然想直接訪問某些父類(超類)方法,您無法做到。super 只能為您提供直接父類的方法。但有一個解決方法!在從一個類繼承時,您可以將父類方法別名為另一個名稱。然後您可以透過別名訪問方法。

  class X
    def foo
      "hello"
    end
  end
  
  class Y < X
    alias xFoo foo
    def foo
      xFoo + "y"
    end
  end

  class Z < Y
    def foo
      xFoo + "z"
    end
  end
  
  puts X.new.foo
  puts Y.new.foo
  puts Z.new.foo

輸出

 hello
 helloy
 helloz

混合模組

[edit | edit source]

首先,您需要閱讀關於模組的知識 Ruby 模組。模組是一種將一些函式和變數以及類分組在一起的方式,有點像類,但更像是名稱空間。因此,模組並不是真正的類。您不能例項化模組,並且模組不能使用 Self 來引用自身。模組可以擁有模組方法(就像類可以擁有類方法)和例項方法。

但是,我們可以將模組包含到一個類中。將它混合進去,可以說是這樣。

module A
  def a1
    puts 'a1 is called'
  end
end

module B
  def b1
    puts 'b1 is called'
  end
end

module C
  def c1
    puts 'c1 is called'
  end
end

class Test
  include A
  include B
  include C

  def display
    puts 'Modules are included'
  end
end

object=Test.new
object.display
object.a1
object.b1
object.c1

輸出

Modules are included
a1 is called
b1 is called
c1 is called

模組可以通過幾種不同的方式混合到類中。在深入研究本節內容之前,瞭解物件如何將訊息解析為方法名稱非常重要。

包含

[edit | edit source]
module A
  def a1
    puts "a1 from module"
  end
end

class Test
  include A

  def a1
    puts "a1 from class"
  end
end

test = Test.new
test.a1

輸出

a1 from class

使用include時,類提供的 method 會在模組提供的 method 之前進行搜尋。這意味著來自類的 method 會先被找到並執行。

預先插入

[edit | edit source]
module A
  def a1
    puts "a1 from module"
  end
end

class Test
  prepend A

  def a1
    puts "a1 from class"
  end
end

test = Test.new
test.a1

輸出

a1 from module

使用prepend時,模組提供的 method 會在類提供的 method 之前進行搜尋。這意味著來自預先插入模組的 method 會先被找到並執行。

程式碼展示了使用模組進行多重繼承。

Ruby 類元模型

[edit | edit source]

為了符合 Ruby 的一切皆物件的原則,類本身是 Class 類的例項。它們儲存在宣告它們的模組作用域下的常量中。對物件例項上方法的呼叫被委託給物件內部的一個變數,該變數包含對該物件類的引用。方法實現存在於 Class 例項物件本身。類方法是在元類上實現的,元類以與這些類例項連結到它們相同的方式連結到現有的類例項物件。這些元類對大多數 Ruby 函式是隱藏的。

華夏公益教科書