Day6 讀書筆記&心得體會(huì)

一、讀書筆記
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目錄下的代碼

今天的收獲?

其他收獲

  • 今天聽了得到訂閱專欄《硅谷來信》里面提到果港,工作時(shí)要既看得見樹木糊昙,也要看得見森林辛掠,意思就是既要把本職工作做好,也要考慮大局释牺,不能只顧著自己的一畝三分地萝衩。
  • 同時(shí),又講到做什么事情前没咙,先跟團(tuán)隊(duì)成員打個(gè)招呼總是好的猩谊。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祭刚,隨后出現(xiàn)的幾起案子预柒,更是在濱河造成了極大的恐慌,老刑警劉巖袁梗,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宜鸯,死亡現(xiàn)場離奇詭異,居然都是意外死亡遮怜,警方通過查閱死者的電腦和手機(jī)淋袖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锯梁,“玉大人即碗,你說我怎么就攤上這事焰情。” “怎么了剥懒?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵内舟,是天一觀的道長。 經(jīng)常有香客問我初橘,道長验游,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任保檐,我火速辦了婚禮耕蝉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夜只。我一直安慰自己垒在,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布扔亥。 她就那樣靜靜地躺著场躯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旅挤。 梳的紋絲不亂的頭發(fā)上推盛,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音谦铃,去河邊找鬼耘成。 笑死,一個(gè)胖子當(dāng)著我的面吹牛驹闰,可吹牛的內(nèi)容都是我干的瘪菌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼嘹朗,長吁一口氣:“原來是場噩夢啊……” “哼师妙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屹培,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤默穴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后褪秀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓄诽,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年媒吗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仑氛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖锯岖,靈堂內(nèi)的尸體忽然破棺而出介袜,到底是詐尸還是另有隱情,我是刑警寧澤出吹,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布遇伞,位于F島的核電站,受9級特大地震影響捶牢,放射性物質(zhì)發(fā)生泄漏鸠珠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一叫确、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芍锦,春花似錦竹勉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孽水,卻和暖如春票腰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背女气。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工杏慰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炼鞠。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓缘滥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谒主。 傳聞我的和親對象是個(gè)殘疾皇子朝扼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 一、心得體會(huì)1霎肯、今天完成了什么擎颖? 今天看了20頁的鎬頭書(139-160) 看了10個(gè)controller 2、今...
    柳輝閱讀 324評論 0 0
  • 一观游、心得體會(huì) 今天完成了什么 看了20頁的鎬頭書 學(xué)了bag的10個(gè)controller 收獲什么搂捧? 新增、編輯都...
    柳輝閱讀 341評論 0 0
  • 一懂缕、心得體會(huì)1异旧、今天完成了什么? 20頁鎬頭書 10個(gè)controller 回顧以前什么是block提佣?為什么要有b...
    柳輝閱讀 460評論 0 0
  • 一吮蛹、心得體會(huì)1荤崇、完成了什么? 看了20頁鎬頭書 看了10個(gè)controller 2潮针、收獲了什么术荤? sub與gsub...
    柳輝閱讀 226評論 0 0
  • 一、讀書筆記回顧昨天的收獲:什么是block每篷、proc瓣戚? block和proc是兩種不同的東西, block有形無...
    柳輝閱讀 375評論 0 0