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:類例項變數
類方法的宣告方式與普通方法相同,只是它們以 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 方法。它們都是隱式私有的。
如果需要,可以透過 public、private、protected 物件方法限制方法的訪問許可權。
有趣的是,這些實際上不是關鍵字,而是真正操作類的方法,動態地改變方法的可見性,因此,這些“關鍵字”會影響所有後續宣告的可見性,直到設定新的可見性或宣告體結束為止。
簡單示例
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 中,“私有”可見性類似於 Java 中的“保護”。Ruby 中的私有方法可以從子類訪問。你無法在 Ruby 中擁有真正的私有方法;你無法完全隱藏一個方法。
保護和私有之間的區別很微妙。如果一個方法是保護的,它可以被定義該類的任何例項或其子類呼叫。如果一個方法是私有的,它只能在呼叫物件的上下文中呼叫——永遠不可能直接訪問另一個物件例項的私有方法,即使該物件與呼叫者是同一個類。對於保護方法,它們可以從同一個類(或子類)的物件訪問。
因此,從物件“a1”(類 A 的例項)內部,你只能呼叫例項“a1”(self)的私有方法。你不能呼叫物件“a2”(也是類 A 的例項)的私有方法——它們對 a2 來說是私有的。但你可以呼叫物件“a2”的保護方法,因為物件 a1 和 a2 都是類 A 的例項。
Ruby FAQ 給出了以下示例——實現一個運算子,將一個內部變數與同一個類中的一個變數進行比較(用於比較物件的目的)
def <=>(other)
self.age <=> other.age
end
如果 age 是私有的,此方法將無法工作,因為 other.age 無法訪問。如果“age”是保護的,這將正常工作,因為 self 和 other 是同一個類,可以訪問彼此的保護方法。
要考慮這一點,保護實際上讓我想起了 C# 中的“內部”訪問修飾符或 Java 中的“預設”訪問修飾符(當沒有訪問關鍵字設定在方法或變數上時):方法的訪問方式與“公共”相同,但僅適用於同一個包內的類。
請注意,物件例項變數不是真正的私有的,你只是看不見它們。要訪問例項變數,你需要建立一個 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

一個類可以從超類繼承功能和變數,有時被稱為父類或基類。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
首先,你需要了解模組 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
模組可以通過幾種不同的方式混合到類中。在深入研究本節內容之前,瞭解物件如何將訊息解析為方法名稱非常重要。
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 時,類提供的方法會在模組提供的方法之前被搜尋。這意味著來自類的函式將首先被找到並執行。
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 時,模組提供的方法會在類提供的方法之前被搜尋。這意味著來自插入的模組的函式將首先被找到並執行。
程式碼展示了使用模組的多重繼承。
為了符合 Ruby 的一切皆物件的原則,類本身是類 Class 的例項。它們儲存在宣告它們的模組的作用域下的常量中。對物件例項上的方法的呼叫被委託給物件內部的一個變數,該變數包含對該物件類的引用。方法實現存在於類例項物件本身。類方法在元類上實現,這些元類以與這些類例項連結到它們相同的方式連結到現有的類例項物件。這些元類對大多數 Ruby 函式隱藏。