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