Ruby 的類
我們在之前的章節(jié)講了 Ruby 的很多對象,學(xué)會了如何使用簡單的對象(例如:數(shù)字和字符串)以及數(shù)據(jù)結(jié)構(gòu)數(shù)組和哈希來完成一些工作,了解如何使用方法,做好了充足的準(zhǔn)備。本章中,我會為大家講解 Ruby 的類,如何創(chuàng)建一個類以及類的實例,以及類的實例方法如何創(chuàng)建。
1. 什么是 Ruby 的類
當(dāng) Ruby 運(yùn)行程序的時候,會創(chuàng)建一個空間,我們使用具體的事物對這些空間進(jìn)行填充,我們可以調(diào)用這些事物的方法去做某些事情。同時,每個具體的事物(對象)都是一般思想或類型的實例,這些思想稱為類。
在終端中我們可以通過class
方法來查看對象所屬的類。
實例:
"Hello World".class
# ---- 輸出結(jié)果 ----
String
對象是類的具體實例(表現(xiàn))。
我們還可以使用is_a?
的方法來具體詢問對象是否屬于某個類:
實例:
"Hello World".is_a?(String)
# ---- 輸出結(jié)果 ----
true
類是用于對象的藍(lán)圖。類具有很多特性,每個類定義了許多方法,這些方法特定用于此類事物(例如:字符串),每次從類創(chuàng)建對象的時候,每個對象都會擁有類給他們的這些方法,也可以說,對象從類繼承了方法。
2. 一步一步創(chuàng)建類
2.1 定義一個類
首先讓我們創(chuàng)建一個Calculator
的類,并逐步為它添加方法。
實例:
class Calculator
end
解釋:我們使用關(guān)鍵字class
,名稱和關(guān)鍵字end
定義一個類。
注意事項:我們定義類的時候,首字母一定要大寫開頭,否則會出現(xiàn)報錯。同樣對于由幾個單詞命名的類,我們應(yīng)該使用大寫字母分隔這些單詞,例如:RubyStudyGroup
,而對于變量名和方法名,我們要使用下劃線,且所有內(nèi)容都應(yīng)該小寫,例如:local_variable
,method_name
。
2.2 創(chuàng)建一個實例
因為我們定義了一個完整的有效類,所以我們可以創(chuàng)建一個Calculator
實例,下面是創(chuàng)建一個Calculator
實例的方法。
Calculator.new
# ---- 輸出結(jié)果 ----
#<Calculator:0x00007fb4132c0af0>
解釋:new
方法是在類Calculator
上定義的(Calculator
它本身既是一個類也是一個對象,還記得我們說Ruby中所有東西都是對象嗎。所以它也可以擁有方法)。此方法創(chuàng)建了一個新的實例,并返回它。
格式#<...>
告訴您此對象不是簡單的東西,比如數(shù)字,字符串或數(shù)組。它只是告訴您類的名稱,Calculator
以及Ruby分配給該對象的內(nèi)部ID。
每個對象都有自己唯一的內(nèi)部對象ID,當(dāng)我在計算機(jī)上運(yùn)行此代碼時,Ruby分配的ID為0x00007fb4132c0af0
。如果您運(yùn)行它,將會得到不同的結(jié)果。實際上,在大多數(shù)情況下,您可以忽略此ID。另外,我們可以檢查我們的新計算器實例確實是類Calculator
的實例。
實例:
class Calculator
end
calculator = Calculator.new
puts calculator.class
puts calculator.is_a?(Calculator)
# ---- 輸出結(jié)果 ----
Calculator
true
2.3 定義實例方法
我們可以在類和對象上定義和調(diào)用方法。類上可用的方法稱為類方法,實例上可用的方法稱為實例方法。剛才我們定義了一個類,現(xiàn)在讓我們?yōu)樗黾右粋€sum
方法。
實例:
class Calculator
def sum(number, other)
number + other
end
end
解釋:我們在類中定義一個方法,即為這個類的實例方法。
注意事項:我們在定義實例方法的時候記得要縮進(jìn) 2 個空格,表明sum
屬于Calculator
類。這是一種規(guī)范。
那么我們?nèi)绾问褂眠@個定義的sum
實例方法呢?
我們可以實例化一個Calculator
,然后調(diào)用這個方法。
實例:
calculator = Calculator.new
puts calculator.sum(2, 3)
# ---- 輸出結(jié)果 ----
5
2.4 初始化對象
在我們向類添加任何行為(方法)之前,我們希望能夠為其提供一些初始數(shù)據(jù)。
讓我們重新定義一個Person
類。
實例:
class Person
end
在我們的情況下,我們希望該人知道自己的名字。
實例:
class Person
def initialize(name)
end
end
解釋:您會看到我們?yōu)檫@個類增加了一個名為initialize
的方法,并且可以接收一個name
參數(shù)。當(dāng)類方法new
創(chuàng)建對象時,將在內(nèi)部調(diào)用特殊的initialize
方法。
我們可以通過以下這種方式將名稱傳遞給類的內(nèi)部:
p Person.new("Andrew")
# ---- 輸出結(jié)果 ----
#<Person:0x00007fb41326b118>
2.5 使用實例變量記錄初始化屬性
繼續(xù)我們剛才做的事,我們想在實例化對象的時候,讓對象保留自己初始化的名稱,這時我們用到了實例變量(instance variable)
實例:
class Person
def initialize(name)
@name = name
end
end
解釋:我們將name
的值賦予了實例變量@name
,它的作用域為整個在對象范圍內(nèi)的任何位置。
此時我們再次實例化Person
類:
p Person.new("Andrew")
#---- 輸出結(jié)果 ----
#<Person:0x00007fb41321add0 @name="Andrew">
此時,我們創(chuàng)建的Person
對象中有了一個實例變量@name
,它的值為Andrew
。
2.6 屬性讀取器(getter)
我們已經(jīng)創(chuàng)建了一個名為 Andrew 的Person
對象,那么如何獲取它的名字呢。
實例:
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
此時我們可以通過向?qū)ο蟀l(fā)送name
的消息,獲取對應(yīng)的信息。
實例:
person = Person.new("Andrew")
person.name
#---- 輸出結(jié)果 ----
"Andrew"
解釋:我們定義了一個方法name
,它返回了實例變量@name
,由此創(chuàng)建了一個屬性讀取器。屬性讀取器返回實例變量的值,也可以說,屬性讀取器公開了實例變量,讓所有的人都可以讀取它。
除此之外我們還有一種簡單的寫法,實現(xiàn)@name
的讀?。?/p>
class Person
attr_reader :name
def initialize(name)
@name = name
end
end
和上面的操作是等效的。
2.7 屬性設(shè)置器(setter)
現(xiàn)在我們添加一個功能,一個人不僅要有名稱,也要能設(shè)置密碼,這個密碼我們希望在Person
對象被實例后才被告知,讓我們進(jìn)一步改變這個類。
class Person
def initialize(name)
@name = name
end
def name
@name
end
def password=(password)
@password = password
end
end
解釋:如您所見,方法password =
只能執(zhí)行一個參數(shù)(稱為password
),并將此局部變量的值分配給實例變量@password
,其他什么也不做。
現(xiàn)在讓我們?yōu)橐粋€Person
對象添加密碼。
實例:
person = Person.new("Andrew")
person.password=("super password")
p person
#<Person:0x00007fb413154810 @name="Andrew", @password="super password">
解釋:在幕后,Ruby 在運(yùn)行代碼時將person.password ="something"
行轉(zhuǎn)換為person.password =("something")
,這僅調(diào)用方法password=
,在右側(cè)傳遞的值作為參數(shù),這只是另一種方法。
同樣我們也有一種簡寫方法給屬性設(shè)置器:
class Person
attr_writer :password
def initialize(name)
@name = name
end
def name
@name
end
end
2.8 對象作用域
您可以通過對象上的任何方法訪問:
-
所有局部變量;
-
所有實例變量;
-
所有對象的方法。
讓我們寫一個greet
方法來為您展示。
實例:
class Person
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + name + "! My name is " + @name + "."
end
end
boy = Person.new("Andrew")
girl = Person.new("Alice")
boy.greet(girl)
#---- 輸出結(jié)果 ----
Hi Alice! My name is Andrew.
解釋:這是一個對象交互的例子,我們定義了一個greet
方法,這個里面調(diào)用了@name
實例變量,other
是一個參數(shù),代表Person
的對象。
注意事項:當(dāng) Ruby 找到一個標(biāo)識符時,Ruby 首先尋找一個局部變量,然后尋找一個方法。上述代碼name
沒有調(diào)用實例方法name
而是取的other.name
的值就是因為這個原因。
2.9 self
每個對象都通過調(diào)用 self 的方式以每種方法認(rèn)識自己。這是 Ruby 中的一個特殊關(guān)鍵字,它的意思是對象本身。
class Person
def initialize(name)
@name = name
p self
end
end
person = Person.new("Anja")
p person
#---- 輸出結(jié)果 ----
#<Person:0x007f9994972428 @name="Anja">
#<Person:0x007f9994972428 @name="Anja">
解釋:如您所見,我們兩次輸出相同的對象。一次在初始化方法中使用p self
,一次在外部范圍中使用p person
。您還可以看到,這兩個實例的神秘對象 ID 相同。因此我們可以知道它確實是同一對象。
所以,之前的方法我們可以這樣修改:
class Person
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + name + "! My name is " + self.name + "."
end
end
boy = Person.new("Andrew")
girl = Person.new("Alice")
boy.greet(girl)
#---- 輸出結(jié)果 ----
Hi Alice! My name is Andrew.
解釋:現(xiàn)在,我們再次在兩個不同的對象上調(diào)用方法name
。當(dāng) Ruby 看到self
時,它知道我們正在引用當(dāng)前的Person
對象,并在其上調(diào)用方法name
。
注意事項:self
是一個關(guān)鍵字并不是一個方法,我們從下面的代碼可以證明。
person = Person.new("Andrew")
p person.self
#---- 輸出結(jié)果 ----
NoMethodError (undefined method `self' for #<Person:0x00007fb413290bc0 @name="Andrew">)
關(guān)鍵字是在 Ruby 中具有特殊含義的單詞,例如class
,def
,end
和self
。
2.10 類方法(Class Method)
類方法的格式為:類名.方法名()
,通俗來說就是類名調(diào)用的方法,在其他語言中也稱為靜態(tài)方法(Static Method)。
現(xiàn)在我們要輸出Person
類英語和中文名稱,對Person
類要這樣修改。
實例:
class Person
def self.cn_name
'人'
end
def self.en_name
'Person'
end
end
puts Person.cn_name
puts Person.en_name
#---- 輸出結(jié)果 ----
人
Person
解釋: 上述我們定義了兩個類方法,一個為cn_name
,一個為en_name
,我們使用Person.cn_name
和Person.en_name
來調(diào)用這兩個方法。
除此之外,我們還可以使用另外一種寫法:
class Person
class << self
def cn_name
'人'
end
def en_name
'Person'
end
end
end
puts Person.cn_name
puts Person.en_name
解釋:這種寫法在單件類章節(jié)中會詳細(xì)講解,可以將它看成在一個類中定義多個類方法的一種便捷辦法。
假如我們要在類的外面定義類方法還可以這樣寫:
class Person
end
def Person.cn_name
'人'
end
def Person.en_name
'Person'
end
puts Person.cn_name
puts Person.en_name
#---- 輸出結(jié)果 ----
人
Person
Tips:創(chuàng)建了一個類的時候,類方法和類的作用域內(nèi)(非實例方法部分),self表示的是類本身。
實例:
class Person
puts "in class: #{self}"
puts self == Person
def self.cn_name
puts "in class method: #{self}"
puts self == Person
end
end
#---- 輸出結(jié)果 ----
in class: Person
true
in class method: Person
true
3. 小結(jié)
本章中我們學(xué)習(xí)了如何創(chuàng)建一個 Ruby 的類,如何定義一個類、創(chuàng)建一個實例、定義實例方法、初始化對象、使用實例變量記錄、初始化屬性、屬性讀取器、屬性設(shè)置器、對象作用域以及了解了 self 的含義。