類與執行個體
2016年9月5日 下午7:35 ***
一個類裡面會定義一些方法,類存在的理由就是要被執行個體化,也就是去建立一個類的執行個體得到一個對象。一個執行個體化的動作,像這樣:
obj = Object.new
Object 是 Ruby 內建的一個類,在類上使用點形式,就是 Object 與 new 之間的那個點。你就是發送了一個資訊給類。類會對這個資訊做出響應,就像對象可以響應資訊一樣。類也是對象。new 方法是一個構造器,也就是類裡面的可以加工與返回新執行個體的方法。
使用 class 關鍵詞可以去定義類,類的名字用的是常量,使用大寫字母開頭。常量可以儲存資訊,常量的值是可以改變的,但是你不能為常量去分配一個新的值,這樣 Ruby 會警告你。最好的方法是避免為已經有值的常量分配新的值。
來建立一個 Ticket 類,它裡面有一個簡單的方法:
class Ticket
def event
'can not really be specified yet...'
end
end
現在可以建立一個新的 ticket 對象,然後問一下它的 event:
ticket = Ticket.new
puts ticket.event
調用方法 ticket.event ,會執行 event 方法,也就會輸出這個方法裡要輸出的東西:
can not really be specified yet...
在上面的例子裡我們建立與執行了一個執行個體方法。
下午7:47 ****
執行個體方法
之前我們直接在一個對象的上面定義了一個方法,是這樣做的:
def ticket.event
剛才我們在 Ticket 類裡又定義一下 event 這個方法:
def event
這種在類裡定義的方法會出現在所有的這個類的執行個體上,這種方法就是執行個體方法(instance methods)。它們不會只屬於一個對象,所有的執行個體都可以調用它們。
我們在某個特定的對象上面定義的方法叫做獨立方法(singleton methods),比如像 def ticket.price 。一個對象有一個 price 方法,這個對象不會在乎這個方法到底是一個獨立方法還是一個執行個體方法,不過作為程式員我們應該知道它們的區別。
下午7:54 ****
覆蓋方法
下午7:54 ****
在類裡面定義了方法,我們可以重新再定義它,在下面的例子裡重複定義了兩次 m 這個方法:
class C
def m
puts '第一次定義方法 m'
end
def m
puts '第二次定義方法 m'
end
end
來看一下我們在 C 的一個執行個體上調用 m 這個方法會發生什麼:
C.new.m
輸出的結果是 “第二次定義方法 m ”,第二次的定義勝利了,因為我們看到的是第二次定義 m 這個方法的時候要輸出的東西。當我們覆蓋一個方法的時候,新的版本會勝利。
下午8:02 ***
重新開啟類
下午8:02 ***
大部分情況下,定義一個類,你就建立了一個類定義的區塊:
class C
# 這裡是類的代碼
end
重新再開啟這個類添加額外的東西或者修改是可以做到的。像這樣:
class C
def x
end
end
class C
def y
end
end
我們開啟了類的定義主體,添加了一個 x 方法,關掉了定義主體。然後,我們又重新開啟了定義主體,添加了第二個方法 y ,又關掉了定義主體。跟下面這麼做是一樣的效果:
class C
def x
end
def y
end
end
下午8:07 ****
執行個體變數與對象狀態
下午8:09 ****
跟一個對象相關的資訊與資料是這個對象的狀態(state)。我們需要做到:
設定與重設一個對象的狀態(一張票說,你花了 10 塊錢)
讀回狀態(問一張票,你需要花多少錢?)
執行個體變數(instance variables),就是 Ruby 對象使用的儲存與取回值的機制。執行個體變數讓每個獨立的對象可以記住它們的狀態。執行個體變數跟其它的變數差不多,你分配值給它們,你可以讀回這些值,你可以把它們加到一塊兒,輸出它們等等。不過有幾個不一樣的地方:
執行個體變數的名字要用 @ 符號開頭,這樣你很容易知道哪些是執行個體變數。
執行個體變數只能在它們所屬的那個對象的內部使用。
在類的內部的某個方法裡面初始化一個執行個體變數,這個執行個體變數就可以在類的任何方法裡面使用。
來看個例子:
class Person
def set_name(string)
puts '設定人名...'
@name = string
end
def get_name
puts '返回人名...'
@name
end
end
joe = Person.new
joe.set_name('Joe')
puts joe.get_name
調用 set_name 方法的時候我們在這個方法裡分配了一個執行個體變數,名字是 @name,這個變數也可以在類裡的其它的方法上使用,比如我們在 get_name 這個方法裡就用了一個 @name 這個執行個體變數。
下午8:25 ****
初始化帶狀態的對象
下午8:27 ****
在類裡面可以定義一個名字是 initialize 的方法,每次建立新的執行個體的時候,都會自動調用這個方法。來看個例子:
class Ticket
def initialize
puts '建立新票'
end
end
調用 Ticket.new 的時候,你就會看到一個 “建立新票” 的資訊出現。
你可以利用這個自動初始化的方法,在建立對象的時候去設定對象裡的狀態。比如你想在建立票的時候讓票擁有會場(venue)與日期(date)這兩個狀態,你可以在建立執行個體的時候讓它們作為參數的值,這些參數的值也會傳遞給 initialize 方法,這樣你就可以把這些參數的值儲存成執行個體變數:
class Ticket
def initialize(venue,date)
@venue = venue
@date = date
end
關閉定義類的區塊之前,再做點別的事,就是添加一種可以讀回 venue 與 date 值的方法。繼續再添加下面這段代碼:
def venue
@venue
end
def date
@date
end
end
這兩個方法裡面用到了執行個體變數,而且它們都是方法裡的最後一個運算式,也就是它們會作為方法返回的值。
下午8:40 ****
設定器方法
下午8:51 ***
名字裡帶等號的方法
class Ticket
def initialize(venue,date,price)
@venue = venue
@date = date
@price = price
end
def price
@price
end
end
初始化的那個方法會變得很長,而且我們還得記住參數的順序。可以改進一下,比如我們用一個 set_price 方法,可以用來設定或重設票的價格,這樣再改一下:
class Ticket
def initialize(venue,date,price)
@venue = venue
@date = date
end
def set_price(amount)
@price = amount
end
def price
@price
end
end
上面的 set_price 也可以寫成這樣:
def price=(amount)
@price = amount
end
方法是用 = 號結尾的,使用它的時候可以這樣:
ticket.price=(63.00)
或者直接這樣用:
ticket.price = 63.00
上面其實是一個方法的調用,不過它看起來像是一個分配的運算式。這種形式是 Syntactic sugar,它表示的是一種使用特別規則的寫法。
下午9:32 ****
屬性與 attr* 方法家族
2016年9月6日 上午7:38 ***
attribute 與 property 翻譯成中文都可以是 “屬性”。屬性的值可以通過對象讀與寫。比如之前我們見過的那個 票 對象,每張票都有 price,date,venue 屬性(attribute)。price= 這個方法可以看成是屬性的寫的方法,date,venue,還有 price 這些讀取屬性用的方法。write/read 跟 get/set 是一個意思。
自動建立屬性
再看一下之前的這段代碼:
class Ticket
def initialize(venue, date)
@venue = venue
@date = date
end
def price=(price)
@price = price
end
def venue
@venue
end
def date
@date
end
def price
@price
end
end
從屬性的讀寫方面來看,上面有一個讀/寫屬性(price),還有兩個讀屬性(venue 和 date)。像上面這麼幹也可以,不過就是有點重複,比如這三個方法的形式都是這樣的:
def something
@something
end
Ruby 提供了自動建立讀取與返回執行個體變數值的方法,像這樣:
class Ticket
attr_reader :venue, :date, :price
end
上面有幾個東西是用冒號開頭的,這些是符號(symbols),符號是命名或打標記用的東西。以後再具體解釋它跟字串的區別。
self 是預設的接受者
你看到了一些調用的方法沒有明顯的接受者,比如調用 attr_reader 的時候。在這種情況下,資訊會發送給 self ,它是預設的對象。在定義類的主體的頂級,self 是類對象本身,也就是接受 attr_reader 資訊的其實是 Ticket 這個類對象。
還有個 attr_writer 方法,像這樣用:
class Ticket
attr_writer :price
end
再用這兩個 attr_* 方法改進一下 Ticket 類:
class Ticket
attr_reader :venue, :date, :price
attr_writer :price
def initialize(venue, date)
@venue = venue
@date = date
end
end
這樣一個 ticket 對象會有 venue,date,price 這幾個屬性,venue,date 是可讀屬性,price 是可讀也可寫的屬性。
941E615E-5ECF-4567-8217-DDD888F33DE5
用 attr_accessor 建立讀寫屬性
使用 attr_accessor 可以為屬性建立讀與寫的方法。它相當於是 attr_reader 加上 attr_writer 。
class Ticket
attr_reader :venue, :date
attr_accessor :price
# ... etc.
end
還有個 attr ,這樣用:
attr :price, true
第二個參數 true 表示使用讀與寫的方法。
attr* 方法的總結
attr_reader
代碼:
attr_reader :price
相當於:
def price
@price
end
attr_writer
代碼:
attr_writer :price
相當於:
def price=(price)
@price = price
end
attr_accessor
代碼:
attr_accessor :price
相當於:
def price=(price)
@price = price
end
def price
@price
end
attr
代碼:
# 相當於 attr_reader
attr :price
# 相當於 attr_accessor
attr :price, true
上午8:26 ***
繼承
上午9:00 ***
繼承是兩個類之間的一種關係,一個類從另一個類裡繼承了一些行為(subclass,superclass)。看一個例子,Magazine 繼承了 Publication ,Magazine 是 Publication 類的 subclass,Publication 是 Magazine 類的 superclass:
class Publication
attr_accessor :publisher
end
class Magazine < Publication
attr_accessor :editor
end
Magzine 繼承的時候用了一個 < 符號,它是一個 subclass,它繼承的是 Publication 這個類,這個類對於 Magzine 這個類來說是一個 superclass。
做個實驗:
mag = Magazine.new
mag.publisher = 'ninghao.net'
mag.editor = '張三'
puts '#{mag.publisher} 是雜誌的發行者,#{mag.editor} 是雜誌的編輯'
因為 Magzine 類繼承了 Publication 類,所以 Magzine 的對象裡面也會包含 Publication 裡面的屬性與方法。
我們可以繼續:
class Ezine < Magzine
end
這樣 Ezine 的對象裡面就會有 publisher 和 editor 這兩個屬性。每個 Ruby 類只能繼承一個類。
做個實驗:
class C
end
class D < C
end
puts D.superclass
puts D.superclass.superclass
輸出的是:
C
Object
C 是 D 的 superclass,Object 是 C 的 superclass 。
上午9:12 ***
作為對象與資訊接受者的類
上午9:12 ***
類是一種特別的對象,它是唯一的能生對象的對象。建立了一個類,比如 Ticket ,你可以發送資訊給它,添加方法給它,把它作為方法的參數傳遞給其它的對象,你可以像處理其它的對象一樣處理類對象。
建立類對象
所有的者,比如 Object,Person,Ticket 都是 Class 這個類的執行個體。使用 class 關鍵詞建立一個類對象:
class Ticket
# 代碼
end
使用上面這種方法建立類對象非常容易讀懂,你也可以這樣來建立類對象:
my_class = Class.new
上面建立的這個類對象可以建立它自己的執行個體:
instance_of_my_class = my_class.new
如果你想使用 Class.new 去建立一個匿名的類,而且想在建立它的時候給它添加執行個體方法,可以在 Class.new 的後面添加一個代碼塊(code block),像這樣:
c = Class.new do
def say_hello
puts 'hello'
end
end
上午10:17 ****
Ruby 對象的天性與培育
is_a? 方法可以告訴你一個執行個體是否屬於某個類,或者它所屬類的祖先類:
>> mag = Magazine.new
=> #
>> mag.is_a?(Magazine)
=> true
>> mag.is_a?(Publication)
=> true
一個執行個體不完全受它所屬的類的影響,你可以為單獨的執行個體對象添加方法,比如讓 magzine 長出翅膀:
mag = Magazine.new
def mag.wings
puts 'I can fly!'
end
mag.wings
常量
上午10:17 ****
常量的名字是大寫字母開頭的。類的名字是常量,常量也經常會用在類裡面來儲存重要的資料。
一個例子:
class Ticket
VENUES = ["Convention Center", "Fairgrounds", "Town Hall"]
如果你想讓每張票都來自 VENUES 裡定義的場地,可以在類的 initialize 方法裡這樣做:
def initialize(venue, date)
if VENUES.include?(venue)
@venue = venue
else
raise ArgumentError, "Unknown venue #{venue}"
end
@date = date
end
在定義類的外面可以使用雙冒號得到常量的值:
class Ticket
VENUES = ["Convention Center", "Fairgrounds", "Town Hall"]
end
puts Ticket::VENUES
Ruby 的預定義常量
>> Math::PI
=> 3.141592653589793
>> RUBY_VERSION
=> "2.3.1"
>> RUBY_PATCHLEVEL
=> 112
>> RUBY_RELEASE_DATE
=> "2016-04-26"
>> RUBY_REVISION
=> 54768
>> RUBY_COPYRIGHT
=> "ruby - Copyright (C) 1993-2016 Yukihiro Matsumoto"
>>
重新分配與修改常量
給一個已經分配了值的常量重新分配值,會收到 Ruby 的警告,試一下:
>> A = 1
=> 1
>> A = 2
(irb):36: warning: already initialized constant A
(irb):35: warning: previous definition of A was here
=> 2
不過你可以修改常量的值:
venues = Ticket:VENUES
venues << '體育館'