一、讀書筆記
3.4 剩余部分
當(dāng)對象需要訪問同類的其他對象的內(nèi)部狀態(tài)時(shí)嫌术,使用保護(hù)訪問(protected access)方式逻族。例如,我們希望單個(gè)的Account對象能夠比較它們的原始余額氮块,而對其余所有對象隱藏這些余額(可能因?yàn)槲覀円砸环N不同的形式表現(xiàn)它們)。
class Account
attr_reader :cleared_balance
protected :cleared_balance
def greater_balance_than(other)
return @cleared_balance > other.cleared_balance
end
end
因?yàn)閷傩詁alance是protected诡宗,只有Account的對象才可以訪問它滔蝉。
3.5 變量
現(xiàn)在我們已經(jīng)輾轉(zhuǎn)創(chuàng)建了所有這些對象,讓我們確保沒有丟掉它們塔沃,變量用來保存這些對象的印跡蝠引;每個(gè)變量保存一個(gè)對象的引用。
讓我們通過下面的代碼來驗(yàn)證。
person = "Tim"
person.object_id # 70176096289900
person.class # String
person # "Tim"
第一行代碼螃概,Ruby使用值“Tim”創(chuàng)建了一個(gè)String對象矫夯,這個(gè)對象的一個(gè)引用(reference)被保存在局部變量person,接下來的快速檢查展示了這個(gè)變量具備字符串的特性吊洼,它具有對象的ID训貌、類和值。
那么冒窍,變量是一個(gè)對象嗎递沪?在Ruby,答案是“不”综液,變量只是對象的引用款慨,對象漂浮在某處一個(gè)很大的池中(大多數(shù)時(shí)候是堆,即heap中)谬莹,并由變量指向它們檩奠。
讓我們看一下稍復(fù)雜的例子:
person1 = "Tim"
person2 = person1
person1[0] = "J"
person1 -> "Jim"
person2 -> "Jim"
發(fā)生了什么?
原來的person1是"Tim"届良,然后我們更改了person1的第一個(gè)字母笆凌。結(jié)果person1和person2都從"Tim"變成了"Jim"。
這都?xì)w結(jié)于變量保存的是對象引用士葫,而非對象本身這一事實(shí)乞而。將person1賦值給person2并不會(huì)創(chuàng)建任何新的對象;它只是將person1的對象引用賦值給person2慢显,因此person1和person2都指向同一對象爪模。
復(fù)制別名(alias)對象,潛在地給了你引用同一對象的多個(gè)變量荚藻。但這不會(huì)在你的代碼中導(dǎo)致問題屋灌?它會(huì)的,但是并不向你想象的頻繁(例如Java中的對象应狱,也以相同的方式運(yùn)作)共郭。例如,在插圖3.1的例子中疾呻,你可以通過使用String的dup方法來避免創(chuàng)建別名除嘹,它會(huì)創(chuàng)建一個(gè)新的、具有相同內(nèi)容的String對象岸蜗。
person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
person1 -> "Jim"
person2 -> "Tim"
你可以通過凍結(jié)一個(gè)對象來阻止其他人對其進(jìn)行改動(dòng)尉咕,試圖更改一個(gè)被凍結(jié)的對象,Ruby將引發(fā)(raise)一個(gè)TypeError異常璃岳。
person1 = "Tim"
person2 = person1
person1.freeze -> prevent modifications to the object
person2[0] = "J"
第四章 容器年缎、Blocks和迭代器
只有一首歌曲的點(diǎn)唱機(jī)是不可能受歡迎的悔捶,所以我們應(yīng)該開始考慮建立一個(gè)歌曲目錄和待播放歌曲列表。
它們都是容器(containers)单芜,所謂容器是指含有一個(gè)或多個(gè)對象引用的對象蜕该。
目錄和播放列表需要一組相似的方法:添加一首歌曲,刪除一首歌曲洲鸠,返回歌曲列表等等蛇损。播放列表可能還需要執(zhí)行額外的任務(wù),例如偶爾插播廣告或者記錄累計(jì)的播放時(shí)間坛怪,不過我們在后面才考慮這些問題,現(xiàn)在看來股囊,開發(fā)一個(gè)通用的SongList類袜匿,然后將其特化(specialize)為目錄和播放列表類,似乎是個(gè)好主意稚疹。
4.1 容器(containers)
開始實(shí)現(xiàn)之前居灯,我們需要決定如歌在SongList對象中存儲歌曲列表。目前有3個(gè)明顯的選擇:(1)使用Ruby的Array(數(shù)組)内狗;(2)使用Ruby的Hash(散列表)怪嫌;(3)自定義列表結(jié)構(gòu)。
4.1.1 數(shù)組
數(shù)組類含有一組對象引用柳沙,每個(gè)對象引用占數(shù)組中的一個(gè)位置岩灭,并由一個(gè)非負(fù)的整數(shù)索引來標(biāo)識。
可以通過使用字面量(literal)赂鲤,或顯式地創(chuàng)建Array對象噪径,來創(chuàng)建數(shù)組,字面量數(shù)組(literal array)只不過是處于方括號中的一組對象数初。
b = Array.new
b.lenght -> 0
b.class -> array
b[0] = "second"
數(shù)組由[]操作符來進(jìn)行索引找爱,和Ruby的大多數(shù)操作符一樣,它實(shí)際上是一個(gè)方法(Array類的一個(gè)實(shí)例方法)泡孩,因此可以被子類重載车摄,如上面例子所示,數(shù)組的下標(biāo)從0開始仑鸥,使用非負(fù)整數(shù)訪問數(shù)組吮播,將會(huì)返回出于該整數(shù)位置上的對象,如果此位置上沒有對象锈候,則返回nil薄料,使用負(fù)整數(shù)訪問數(shù)組,則從數(shù)組末端開始計(jì)數(shù)泵琳。
a = [1,2,3]
a[-1] -> 3
a[-2] -> 2
a[-99] -> nil
你也可以使用一對數(shù)字[start, count]來訪問數(shù)組摄职,這將返回一個(gè)包含從start開始的count個(gè)對象引用的新數(shù)組誊役。
a = [1, 2, 3]
a[0..2] -> [1,2,3]
最后,你還可以使用range來對數(shù)組進(jìn)行索引谷市,其開始和結(jié)束位置被兩個(gè)或者3個(gè)點(diǎn)分隔開蛔垢,兩個(gè)點(diǎn)的形式包含結(jié)束位置,而3個(gè)點(diǎn)的形式不包含迫悠。
a = [1, 2, 3]
a[1] = 'bag' -> [1, "bag", 2, 3]
a[5] = 66 -> [1, "bag", 2, 3, nil, 66]
數(shù)組還有大量的其他有用的方法鹏漆,使用這些方法,你可以用數(shù)組來實(shí)現(xiàn)棧(stack)创泄、收集(set)艺玲、隊(duì)列(queue)、雙向隊(duì)列(dequeue)和先進(jìn)先出隊(duì)列(fifo)鞠抑。
4.1.2 散列表
Hashed(也稱關(guān)聯(lián)數(shù)組饭聚、圖或詞典)和數(shù)組的相似之處在于他們都是被索引的對象引用集合,不過數(shù)組只能用整數(shù)來進(jìn)行索引搁拙,而hash可以用任何類型的對象來進(jìn)行索引秒梳,比如字符串、正則表達(dá)式等等箕速。當(dāng)你將一個(gè)值存入hash是酪碘,其實(shí)需要提供兩個(gè)對象,一個(gè)索引(key)盐茎,另一個(gè)值兴垦。隨后,你可以通過鍵去索引hash以獲得其對應(yīng)的值庭呜,hash中的值可以是任意類型的對象滑进。
下面的例子使用了hash字母符表示法:處于花括號之間的key =>value配對的列表。
h = { 'dog' => 'canie', 'cat' => 'feline', 'donkey' => 'asinine' }
h.length ->3
h['dog'] -> "canine"
h['cow'] = "bob dy"
和數(shù)組相比募谎,hashes有一個(gè)突出的優(yōu)點(diǎn):可以用任何對象做索引扶关,然而它也有一個(gè)突出的去缺點(diǎn):它的元素是無序的,因此很難使用hash來實(shí)現(xiàn)棧和隊(duì)列数冬。
你會(huì)發(fā)現(xiàn)hash是ruby最常用的數(shù)據(jù)結(jié)構(gòu)之一节槐。
4.1.3 實(shí)現(xiàn)一個(gè)SongList容器
在簡單介紹了數(shù)組和hash后,該來實(shí)現(xiàn)點(diǎn)唱機(jī)的SongList了拐纱,讓我們來設(shè)計(jì)一組SongList類所需要的基本方法铜异,之后我們逐步擴(kuò)充,但現(xiàn)在這些已經(jīng)足夠了秸架。
添加給定的歌曲列表中揍庄。
delete_first() -> song
刪除列表的第一首歌曲,并返回該歌曲东抹。
delete_last -> song
刪除列表的最后一首歌曲蚂子,并返回該歌曲沃测。
[index] -> song
返回指定名字的歌曲
這個(gè)列表為如何實(shí)現(xiàn)SongList給出了提示。既能在尾部添加歌曲食茎,又能在頭部刪除歌曲蒂破,這提示我們使用雙向隊(duì)列(即有兩個(gè)頭部的隊(duì)列)可以使用Array來實(shí)現(xiàn)它。
同樣别渔,數(shù)組也支持返回列表中整數(shù)位置歌曲附迷。
然而我們也需要使用歌曲名來檢索歌曲,這可以通過以歌曲為鍵哎媚、歌曲為值的hash來實(shí)現(xiàn)喇伯。我們可以用hash嗎?也許可以拨与,但是我們會(huì)發(fā)現(xiàn)艘刚,這樣會(huì)有問題。
首先截珍,hash是無序,所以我們可能需要一個(gè)輔助數(shù)組來跟蹤歌曲列表箩朴。第二岗喉,hash不支持多個(gè)鍵對應(yīng)一個(gè)相同的值,這對實(shí)現(xiàn)播放列表不利炸庞,因?yàn)椴シ帕斜砜赡苄枰シ磐皇赘瓒啻吻玻虼四壳拔覀兿仁褂脭?shù)組來實(shí)現(xiàn),并在需要的時(shí)候搜索歌名埠居,如果這成為瓶頸查牌,我們可以添加一些基于hash的搜索功能。
SongList類的實(shí)現(xiàn)以一個(gè)基本的initialize方法開始滥壕,該方法會(huì)創(chuàng)建容納歌曲的數(shù)組纸颜,并將該數(shù)組的引用存儲到實(shí)例變量@songs中。
Class SongList
def initialize
@songs = Array.new
end
end
SongList#append方法將給定的歌曲添加到@songs數(shù)組的尾部绎橘,它會(huì)返回self胁孙,即當(dāng)前SongList對象的引用。這是一本很有用的慣用法称鳞,可以讓我們把對append的對個(gè)調(diào)用連接在一起涮较。舉個(gè)栗子:
class SongList
def append(song)
@songs.push(song)
end
end
接著我們來添加delete_first和delete_last方法,它們分別用Array#shift和Array#pop來實(shí)現(xiàn)冈止。
class SongList
def delete_first
@songs.shift
end
def delete_last
@songs.pop
end
end
到目前為止狂票,都還不錯(cuò)哦,下一個(gè)要實(shí)現(xiàn)的方法是[]熙暴,用它通過下標(biāo)來訪問元素闺属。這種簡單的代理形式的方法在Ruby代碼中經(jīng)郴哦ⅲ可見:如果你的代碼含有大量一兩行的方法,不用擔(dān)心——那是你設(shè)計(jì)正確的跡象屋剑。
class SongList
def [](index)
@songs[index]
end
end
現(xiàn)在做個(gè)簡單的測試润匙,我們將使用Ruby標(biāo)準(zhǔn)發(fā)行版自帶的一個(gè)稱謂TestUnit的測試框架來做這個(gè)工作。但是不會(huì)在這里詳細(xì)介紹它唉匾。assert_equal方法檢查它的兩個(gè)參數(shù)是否相等孕讳,如果不等則立即報(bào)錯(cuò)。同樣巍膘,assert_nil方法在它的參數(shù)不為nil時(shí)也會(huì)報(bào)錯(cuò)厂财,我們將會(huì)使用這些來保證從列表中刪除適當(dāng)?shù)母枨?/p>
測試需要必要的初始化,以告訴RUby使用TestUnit測試框架峡懈,并告訴該框架我們在寫一些測試代碼璃饱。然后創(chuàng)建一個(gè)SongList對象和4首歌曲對象,并添加歌曲到列表中(趁機(jī)炫耀一下肪康,我們利用了“append”返回SongList對象)這一特點(diǎn)來連接方法調(diào)用荚恶,
接著,我們測試[]方法磷支,驗(yàn)證它是否返回了指定下標(biāo)處的正確的歌曲(或者nil)谒撼。最后,我們從列表的首位刪除歌曲雾狈,并驗(yàn)證返回正確的歌曲廓潜。
require 'test/unit'
class TestSongList < Test::Unit::TestCase
def test_delete
list = SongList.new
s1 = Song.new("title1", "artist1", 1)
s2 = Song.new("title2", "artist2", 2)
s3 = Song.new("title3", "artist3", 3)
s4 = Song.new("title4", "artist4", 4)
list.append(s1).append(s2).append(s3).append(s4)
assert_equal(s1, list[0])
assert_equal(s3, list[2])
assert_nil(list[9])
assert_equal(s1, list.delete_first)
assert_equal(s2, list.delete_first)
assert_equal(s4, list.delete_last)
assert_equal(s3, list.delete_last)
assert_nil(list, delete_last)
end
end
是時(shí)候添加搜索功能了,這要求遍歷列表中的所有歌曲善榛,并檢查每首歌的名字辩蛋,為了實(shí)現(xiàn)這項(xiàng)功能,我們先說一下:## 迭代器
4.2 Blocks 和 迭代器
實(shí)現(xiàn)SongList的下一個(gè)問題是實(shí)現(xiàn)with_title方法移盆,該方法接受一個(gè)字符串參數(shù)悼院,并返回以此為歌名的歌曲,有個(gè)直接實(shí)現(xiàn)的方法:因?yàn)槲覀冇懈枨鷶?shù)組咒循,所以可以遍歷該數(shù)組的所有元素樱蛤,并查找出匹配的元素。
class SongList
def with_title(title)
for i in 0...@songs.length
return @songs[i] if title == @songs[i].name
end
return nil
end
end
這個(gè)方法確實(shí)可行剑鞍,而且也相當(dāng)常見:用for循環(huán)遍歷數(shù)組昨凡,但是有沒有更自然的方式呢?
的確有蚁署,在某種程度上便脊,for循環(huán)和數(shù)組耦合過于緊密:需要知道數(shù)組的長度,然后依次獲得其元素的值光戈,直到找到一個(gè)匹配為止哪痰,為什么不只是請求數(shù)組對它的每一個(gè)元素執(zhí)行一個(gè)測試呢遂赠?這正是數(shù)組的find方法要做的事情。
class SongList
def with_title(title)
@songs.find{|song| title == song.name}
end
end
find方法是一種迭代器晌杰,它反復(fù)調(diào)用block中的代碼跷睦。迭代器和block是Ruby最有趣的特性之一,所以我們花一點(diǎn)時(shí)間來了解它們肋演。
4.2.1 實(shí)現(xiàn)迭代器(implementing lterators)
Ruby的迭代器只不過是可以調(diào)用block的方法而已抑诸,咋看之下,Ruby的代碼區(qū)塊和C爹殊、Java蜕乡、C#、Perl的代碼區(qū)塊很相似梗夸,實(shí)際上這有點(diǎn)蒙蔽人——Ruby的block不是傳統(tǒng)的意義上的层玲、將語句組織在一起的一種方式。
首先反症,block在代碼中只和方法調(diào)用一起出現(xiàn)辛块;block和方法調(diào)用的最后一個(gè)參數(shù)處于同一行,并緊跟在其后(或者參數(shù)列表的右括號的后面)铅碍。其次憨降,在遇到block的時(shí)候,并不立刻執(zhí)行其中的代碼该酗。Ruby會(huì)記住block出現(xiàn)時(shí)的上下文(局部變量、當(dāng)前對象等)然后執(zhí)行方法調(diào)用士嚎。
在方法內(nèi)部呜魄,block可以像方法一樣被yield語句調(diào)用,每執(zhí)行一次yield莱衩,就會(huì)調(diào)用block中的代碼爵嗅,當(dāng)block執(zhí)行結(jié)束時(shí),控制返回到緊隨yield之后的那條語句笨蚁。我們來看個(gè)簡單的例子睹晒。
def three_times
yield
yield
yield
end
three_times { puts "Hello" }
block(花括號內(nèi)的代碼)和對方法three_times的調(diào)用聯(lián)合在一起。該方法內(nèi)部括细,連續(xù)3次調(diào)用了yield伪很。每次調(diào)用時(shí),都會(huì)執(zhí)行block中的代碼奋单,并且打印出一條歡迎信息锉试。更有趣的是,你可以傳遞參數(shù)給block览濒,并獲得返回值呆盖,例如拖云,我們可以寫個(gè)簡單的函數(shù)返回低于某個(gè)值的所有Fibonacci數(shù)列項(xiàng)。
def fib_up_to(max)
i1, i2 = 1, 1 # 并行賦值(i1 = 1 and i2 = 1)
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
fib_up_to(1000) { |f| print f, " " }
輸出結(jié)果:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
注意:程序員迷們會(huì)很樂意看到y(tǒng)ield關(guān)鍵字应又,它模仿了Liskov的CLU語言中的yield函數(shù)宙项,CLU是一個(gè)超過20年之久的語言,其包含的特性還未被完全開發(fā)出來株扛。
基本Fibonnacci數(shù)列是以兩個(gè)1開頭的整數(shù)序列尤筐,其中的每一項(xiàng)都是它的前兩項(xiàng)的和,它常被用在排序算法和自然現(xiàn)象分析中席里。
在這個(gè)例子中叔磷,yield語句帶有一個(gè)參數(shù),參數(shù)值將被傳送給相關(guān)的block奖磁。在block定義中改基,參數(shù)列表位于兩個(gè)豎線(管道符)之間,在這個(gè)例子中咖为,變量f收到y(tǒng)ield的參數(shù)的值秕狰,所以block能夠輸出數(shù)列中的下一個(gè)項(xiàng)(這個(gè)例子也展示了并行賦值的用法)。盡管通常block只有一個(gè)參數(shù)躁染,但這不是必然的鸣哀,block可以有任意數(shù)量的參數(shù)。
如果傳遞給block的參數(shù)是已存在的局部變量吞彤,那么這些變量即為block參數(shù)我衬,它們的值可能會(huì)因?yàn)閎lock的執(zhí)行而改變,同樣的規(guī)則適用于block內(nèi)的變量:如果它們第一次出現(xiàn)在block內(nèi)饰恕,那么它們就是block的局部變量挠羔。相反,如果它們先出現(xiàn)在block外埋嵌,那么block就與外部環(huán)境共享這些變量破加。
在下面這個(gè)(認(rèn)為設(shè)計(jì))的例子中,我們看到了從外部環(huán)境中繼承了變量a和b雹嗦,而c是block的局部變量(defined?方法在其參數(shù)沒有定義時(shí)返回nil)范舀。
a = [1, 2]
b = 'cat'
a.each { |b| c = b * a[1] }
a -> [1, 2]
block 也可以返回值給方法,block內(nèi)執(zhí)行的最后一條表達(dá)式的值被作為yield的值返回給方法了罪,這也是Array類的find方法的工作方式锭环。它的實(shí)現(xiàn)類似于下面的代碼。
class Array
def find
for i in 0...size
value = self[i]
return value if yield(value)
end
return nil
end
end
輸出結(jié)果:
[1, 3, 5, 7, 9].find { |v| v*v > 30 }
上面的代碼把數(shù)組的元素依次傳遞給關(guān)聯(lián)的block泊藕。如果block返回真田藐,那么方法返回相應(yīng)的元素,如果沒有元素匹配,方法返回nil汽久,這個(gè)例子展示了這種迭代器的方式的優(yōu)點(diǎn)鹤竭,數(shù)組類處理它擅長的事情,例如訪問數(shù)組元素景醇,而讓應(yīng)用程序代碼集中精力處理特殊需求(本例的特殊需求是找到滿足某些算術(shù)標(biāo)準(zhǔn)的數(shù)組項(xiàng))臀稚。
一些迭代器是Ruby的許多收集(collection)類型所共有的。我們已經(jīng)看了find方法三痰,另外兩個(gè)是each和collect吧寺。each可能是最簡單的迭代器,它所做的就是連續(xù)訪問收集的所有元素散劫。
[ 1, 3, 5, 7, 9 ].each { |i| puts i }
輸出結(jié)果:
1
3
5
7
9
echo迭代器在Ruby中有獨(dú)特的作用稚机,另一個(gè)常用的迭代器是collect,它從收集中獲得各個(gè)元素并傳遞給block获搏。block返回的結(jié)果被用來生成一個(gè)新的數(shù)組赖条,例如:
["H", "A", "L"].collect { |x| x.succ } -> ["I", "B", "M"]
迭代器并不僅局限于訪問數(shù)組和hash中的已有數(shù)據(jù),從Fibonacci的例子中常熙,我們已經(jīng)看到迭代器可以返回得到的值纬乍。這個(gè)功能被RUby的輸入/輸出類所使用,這些類實(shí)現(xiàn)了一個(gè)迭代器接口以返回得到的值裸卫。這個(gè)功能被Ruby的輸入/輸出類所使用仿贬,這些類實(shí)現(xiàn)了一個(gè)迭代器接口以返回I/OL流中的連續(xù)相繼的行(或字節(jié))。下面的例子使用了do...end來定義block墓贿。這種方式定義block和使用花括號定義block的唯一區(qū)別是優(yōu)先級:do...end的綁定低于{...}茧泪。
f = File.open("testfile")
f.each do |line|
puts line
end
f.close
輸出結(jié)果:
This is line one
This is line two
This is line three
And so on...
讓我們再看一個(gè)有用的迭代器。inject(名字有點(diǎn)難理解)方法(定義在Enumerable模塊中)讓你可以遍歷收集的所有成員以累積出一個(gè)值聋袋。例如队伟,使用下面的代碼你可以將數(shù)組中的所有元素加起來,并獲得它們的累加和舱馅。
[1, 3, 5, 7].inject(0) { |sum, element| sum+element }
[1, 3, 5, 7].inject(1) { |product, element | product*element }
inject是這樣工作的:block第一次被執(zhí)行時(shí),sum被置為inject的參數(shù)刀荒,而element被置為收集的第一個(gè)元素代嗤。接下來的每次執(zhí)行block時(shí),sum被置為上次block被調(diào)用時(shí)的返回值缠借。inject沒有參數(shù)干毅,那么它使用收集的第一個(gè)元素作為初始值,并從第二個(gè)元素開始迭代泼返。這意味著我們可以把前面的例子寫成:
[1, 3, 5, 7].inject { |sum, element| sum+element }
[1, 3, 5, 7].inject { |product, element| product*element }
內(nèi)迭代器和外迭代器
Ruby實(shí)現(xiàn)迭代器的方式與其他如C++何Java等語言實(shí)現(xiàn)迭代器的方式硝逢,值得我們做一下比較。在Ruby中,迭代器集成于收集內(nèi)部——它只不過是一個(gè)方法渠鸽,和其他方法不同的是叫乌,每當(dāng)產(chǎn)生新的值得時(shí)候調(diào)用yield。使用迭代器的不過是和該方法相關(guān)聯(lián)的一個(gè)代碼block而已徽缚。
在其他語言中拳缠,收集本身沒有迭代器含末,他們生成外部輔助對象(例如Java中基于Interator接口的對象)來傳送迭代器狀態(tài)。從這點(diǎn)看來(當(dāng)然還可從很多方面來看),Ruby是一種透明的語言讯沈。你在寫程序的時(shí)候,Ruby語言能使你集中精力在你的工作上卸亮,而不是語言本身上叫榕。
我們值得花點(diǎn)時(shí)間看看為什么Ruby的內(nèi)部迭代器并不總是最好的解決方案。當(dāng)你需要把迭代器本身作為一個(gè)對象時(shí)(例如详炬,將迭代器傳遞給一個(gè)方法盐类,而該方法需要訪問由迭代器返回一個(gè)值),它的表現(xiàn)欠佳了痕寓。另外傲醉,使用RUby內(nèi)建的迭代器模式也難以實(shí)現(xiàn)并行迭代兩個(gè)收集。幸運(yùn)的是呻率,Ruby提供了Generator庫硬毕,該庫為解決這些問題實(shí)現(xiàn)了外部迭代器。
4.2.2 事物 Blocks
盡管block通常和迭代器合用礼仗,但它還有其他用處吐咳。我們來看其中幾個(gè)用法。
block可以用來定義必須運(yùn)行在事務(wù)控制環(huán)境下的代碼元践。比如韭脊,你經(jīng)常需要打開一個(gè)文件,對其內(nèi)容做些處理单旁,然后確保在處理結(jié)束后關(guān)閉文件沪羔。盡管可以用傳統(tǒng)的方式實(shí)現(xiàn),但也存在“應(yīng)該由文件負(fù)責(zé)自身的關(guān)閉”這樣的觀點(diǎn)象浑。我們可以用block來實(shí)現(xiàn)這種需求蔫饰。如下是一個(gè)簡單且忽略了錯(cuò)誤處理的例子:
class File
def File.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.open_and_process("testfile", "r") do |file|
while line = file.gets
puts line
end
end
輸出結(jié)果:
This is line one
This is line two
This line three
And so on...
open_and_process是一個(gè)類方法,它可以獨(dú)立于任何file對象來被使用愉豺。我們希望它接受與傳統(tǒng)的File.open一樣的參數(shù)篓吁,但并不關(guān)心這些參數(shù)到底是什么?所以蚪拦,我們用args表示參數(shù)杖剪,這意味著“把傳遞給這個(gè)方法的實(shí)際參數(shù)收集到名字為args的數(shù)組中冻押。”然后我們調(diào)用File.open盛嘿,并以args作為參數(shù)洛巢。它將把數(shù)組參數(shù)擴(kuò)展成獨(dú)立的參數(shù),最終的結(jié)果是孩擂,open_and_process透明地將它所接收的任意參數(shù)都傳遞給了File.open狼渊。
一旦文件被打開,open_and_process將調(diào)用yield类垦,并傳遞打開的文件對象給block狈邑。當(dāng)block返回時(shí),文件即被關(guān)閉蚤认,通過這種方式米苹,關(guān)閉打開文件的責(zé)任從文件使用者身上轉(zhuǎn)移到了文件本身。
讓文件管理它自己的生命周期的技術(shù)如此重要砰琢,以至于Ruby的File類直接支持了這項(xiàng)技術(shù)蘸嘶,如果File.open有個(gè)關(guān)聯(lián)的block,那么該block將被調(diào)用陪汽,且參數(shù)是該文件對象训唱。當(dāng)block執(zhí)行結(jié)束時(shí),文件會(huì)被關(guān)閉挚冤,這非常有趣况增,因?yàn)樗馕吨鳩ile.open有兩種不同的行為:當(dāng)和block一起調(diào)用時(shí),它會(huì)執(zhí)行該block并關(guān)閉文件训挡;
當(dāng)單獨(dú)調(diào)用時(shí)澳骤,它會(huì)返回文件對象,使得這種行為成為可能的是澜薄,Kernel.block_given?方法为肮,當(dāng)某方法和block關(guān)聯(lián)在一起調(diào)用時(shí),Kernel.block_given?將返回真肤京。使用該方法颊艳,可以用下面的代碼(同樣也忽略了錯(cuò)誤處理)實(shí)現(xiàn)類似于標(biāo)準(zhǔn)的File.open方法。
class File
def File.my_open(*args)
result = file = File.new(*args)
if block_given?
result = yield file
file.close
end
return result
end
end
還有一點(diǎn)不足:前面的例子在使用block來控制資源時(shí)忘分,我們還沒有解決錯(cuò)誤處理問題棋枕,如果想完整實(shí)現(xiàn)這些方法,那么即使處理文件的代碼由于某種原因異常中斷饭庞,我們也需要確保文件被關(guān)閉戒悠。后面談到的異常處理可以解決這個(gè)問題熬荆。
4.2.3 Blocks可以作為閉包
讓我們再回到點(diǎn)唱機(jī)上(還記得點(diǎn)唱機(jī)的例子吧)舟山。在某些時(shí)候,我們需要處理用戶界面——用戶用來選擇歌曲和控制點(diǎn)唱機(jī)的按鈕。我們需要將行為關(guān)聯(lián)到這些按鈕上:當(dāng)按“開始”按鈕時(shí)累盗,開始播放音樂寒矿。事實(shí)證明,Ruby語言的block是實(shí)現(xiàn)這種需求的合適方式若债,假設(shè)點(diǎn)唱機(jī)的硬件制造商實(shí)現(xiàn)了一個(gè)Ruby擴(kuò)展符相,該擴(kuò)展提供了一個(gè)基本的按鈕類。
start_button = Button.new("Start")
pause_button = Button.new("Pause")
當(dāng)用戶按其中一個(gè)按鈕時(shí)會(huì)發(fā)生什么呢蠢琳?硬件開發(fā)人員做了埋伏啊终,使得按鈕按下時(shí),調(diào)用Button類的回調(diào)函數(shù)button_pressed傲须。向按鈕類中添加功能的一個(gè)顯而易見的方式是穿件Button類的子類蓝牲,并讓每個(gè)子類實(shí)現(xiàn)自己的button_pressed方法。
class StartButton < Button
def initialize
super("Start")
end
def button_pressed
# do start actions
end
end
start_button = StartButton.new
這樣做有兩個(gè)問題泰讽,首先例衍,這會(huì)導(dǎo)致大量的子類,如果Button類的接口發(fā)生變化已卸,維護(hù)代價(jià)將會(huì)提高佛玄,其次,按下按鈕引發(fā)的動(dòng)作所處層次不當(dāng):它們不是按鈕的功能累澡,而是使用按鈕的點(diǎn)唱機(jī)的功能梦抢。使用Block可以解決這些問題。
songlist = SongList.new
class JukeboxButton < Button
def initialize(label, &action)
super(label)
@action = action
end
def button_pressed
@action.call(self)
end
end
start_button = JukeboxButton.new("Start") { songlist.start }
pause_button = JukeboxButton.new("pause") { songlist.pause }
上面代碼的關(guān)鍵之處在于JukeboxButton#initialize的第二個(gè)參數(shù)永乌,如果定義方法時(shí)在最后一個(gè)參數(shù)前加一個(gè)&(例如&action)惑申,那么當(dāng)調(diào)用該方法時(shí),Ruby會(huì)尋找一個(gè)block翅雏。block將會(huì)轉(zhuǎn)化成Proc類的一個(gè)對象圈驼,并賦值給了實(shí)例變量@action。這樣當(dāng)回調(diào)函數(shù)button_pressed被調(diào)用時(shí)望几,我們可以Proc#call方法去調(diào)用相應(yīng)的Block绩脆。
但是,當(dāng)我們創(chuàng)建Proc對象時(shí)橄抹,到底獲得了什么靴迫?有趣的是,我們得到的不僅僅是一堆代碼楼誓。和block(以及Proc對象)關(guān)聯(lián)在一起的還有定義block時(shí)的上下文玉锌,即self的值、作用域內(nèi)的方法疟羹、變量和常量主守。Ruby的神奇之處是禀倔,即使block被定義時(shí)的環(huán)境早已消失了,block仍然可以使用其原始作用域中的信息参淫。在其他語言中救湖,這種特性稱之為閉包。
讓我們來看一個(gè)故意設(shè)計(jì)的例子涎才,該例使用了lambda方法鞋既,該方法將一個(gè)block轉(zhuǎn)換成了Proc對象。
def n_times(thing)
return lambda { |n| thing * n }
end
p1 = n_times(23)
p1.call(3)
p1.call(4)
p2 = n_times("Hello ")
p2.call(3)
n_times方法返回引用了其參數(shù)thing的Proc對象耍铜。盡管block被調(diào)用時(shí)邑闺,這個(gè)參數(shù)已經(jīng)出了其作用域,但是block仍然可以訪問它棕兼。
4.3 處處皆是容器
容器检吆、block和迭代器是Ruby的核心概念,用Ruby寫的代碼越多程储,你就會(huì)發(fā)現(xiàn)自己對傳統(tǒng)循環(huán)結(jié)構(gòu)使用的越少蹭沛。你會(huì)更多的寫支持迭代自身內(nèi)容的類,而且你會(huì)發(fā)現(xiàn)這些代碼精簡并易于維護(hù)章鲤。
第5章 標(biāo)準(zhǔn)類型
到目前為止摊灭,我們已經(jīng)對點(diǎn)唱機(jī)有了好玩的實(shí)現(xiàn),但同時(shí)有所取舍败徊,前面我們降到了數(shù)組帚呼、散列、proc皱蹦。但還沒有真正談到Ruby中的其他一些基本類型:數(shù)字(number)煤杀、字符串、區(qū)間(range)和正則表達(dá)式沪哺。
5.1 數(shù)字
Ruby支持整數(shù)和浮點(diǎn)數(shù)沈自。整數(shù)可以是任何長度(其最大值取決于系統(tǒng)可用內(nèi)存的大小)辜妓。一定范圍內(nèi)的整數(shù)(通常是-230到230-1或-262到262-1)在內(nèi)部以二進(jìn)制形似存儲枯途,它們是Fixnum類的對象。這個(gè)范圍之外的整數(shù)存儲在Bignum類的對象中(目前實(shí)現(xiàn)為一個(gè)可變長度的短整型集合)籍滴。這個(gè)處理是透明的酪夷,Ruby會(huì)自動(dòng)管理它們之間的來回轉(zhuǎn)換。
num = 81
6.times do
puts "#{num.class}: #{num}"
num *=num
end
輸出結(jié)果:
Fixnum:81
Fixnum:6561
在書寫整數(shù)時(shí)孽惰,你可以使用一個(gè)可選的前導(dǎo)符號晚岭,可選的進(jìn)制指示符(0表示八進(jìn)制, 0d表示十進(jìn)制[默認(rèn)]),0x表示十六進(jìn)制或者0b表示二進(jìn)制)勋功,后面跟一串符合適當(dāng)進(jìn)制的數(shù)字坦报。下劃線在數(shù)字串中被忽略(一些人在更大的數(shù)值中使用它們來代替逗號)辅甥。
控制字符的整數(shù)值可以使用?\C-x和cx(x的control版本燎竖,是x&0x9f)生成,元字符(x | 0x80^2)可以使用?\M-x生成要销。元字符和控制字符的組合可以使用?\M-\C-x生成构回。可以使用?\序列得到反斜線字符的整數(shù)值疏咐。
?a => 97 # ASCII character
?\n => 10 # code for a newline (0x0a)
?\C-a => 1 # control a = ?A & 0x9f = 0x01
?\M-a => 225 #meta sets bit 7
?\m-\C-a =>129
?\C-? => 127
與原聲體系結(jié)構(gòu)的double數(shù)據(jù)類型相對應(yīng)纤掸,帶有小數(shù)點(diǎn)和/或冪的數(shù)字字面量被轉(zhuǎn)換成浮點(diǎn)對象。你必須在小數(shù)點(diǎn)之前和之后都給出數(shù)字(如果把1.0e3寫成1.e3浑塞,Ruby會(huì)試圖調(diào)用Fixnum類的e3方法)
二借跪、心得體會(huì)
今天完成了什么?
- 今天主要看了《PR》的4.1酌壕、4.2掏愁、4.3節(jié)
- 看了auth目錄下的代碼
今天的收獲?
- 驗(yàn)證功能
- rescue 拋出異常 http://rails-weekly.group.iteye.com/group/wiki/1821-rails-questions-weekly-12-ruby-exception-mechanism
- perform_later 異步通信
- 發(fā)送消息的gem:HTTParty
- 這個(gè)系統(tǒng)本地是不能發(fā)送手機(jī)驗(yàn)證碼的
- 管理員登錄后臺就簡單點(diǎn)卵牍,可以修改手機(jī)號
其他收獲
- 今天聽了得到訂閱專欄《硅谷來信》里面提到果港,工作時(shí)要既看得見樹木糊昙,也要看得見森林辛掠,意思就是既要把本職工作做好,也要考慮大局释牺,不能只顧著自己的一畝三分地萝衩。
- 同時(shí),又講到做什么事情前没咙,先跟團(tuán)隊(duì)成員打個(gè)招呼總是好的猩谊。