?
本文檔使用 php中文網(wǎng)手冊(cè) 發(fā)布
??? 目錄和播放列表都有類似的操作,增加歌曲,刪除歌曲,返回歌曲列表等等。播放列表可能還需要?jiǎng)e的方法,比如插入廣告,記錄累計(jì)播放時(shí)間等,我們將在后面考率這些問題。現(xiàn)在,我們需要建立一個(gè)SongList類,以便在目錄和播放列表中使用。
??? 在開始實(shí)現(xiàn)之前,我們要知道怎么在SongList中存儲(chǔ)歌曲列表。我們有三個(gè)明顯得選擇:Ruby的提供的Array,Ruby提供的Hash,或者我們自己創(chuàng)建一個(gè)列表結(jié)構(gòu)。下面我們來看看數(shù)組合和哈希,然后選擇一個(gè)來在我們的類中使用。
數(shù)組類包含了若干個(gè)對(duì)其它對(duì)象的引用,每個(gè)引用在數(shù)組的一個(gè)位置上,用一個(gè)正整數(shù)來索引。
你可以直接指定一組值來創(chuàng)建一個(gè)數(shù)組,或者用數(shù)組類的new方法。用第一種方法創(chuàng)建數(shù)組要用中括號(hào)把各個(gè)元素括起來,每個(gè)元素之間用逗號(hào)隔開。
a?=?[?3.14159,?"pie",?99?] | ||
a.type |
? |
Array |
a.length |
? |
3 |
a[0] |
? |
3.14159 |
a[1] |
? |
"pie" |
a[2] |
? |
99 |
a[3] |
? |
nil |
| ||
b?=?Array.new | ||
b.type |
? |
Array |
b.length |
? |
0 |
b[0]?=?"second" | ||
b[1]?=?"array" | ||
b |
? |
["second",?"array"] |
數(shù)組指定索引的下標(biāo)用[ ]操作符,這實(shí)際上也是一個(gè)方法,可以在子類中被重載。數(shù)組索引從0開始,從左向右技術(shù),如果指定的索引為負(fù)數(shù),則表示從右邊開始計(jì)數(shù)(這時(shí)候最右邊的索引下標(biāo)為-1,不是0)。
a?=?[?1,?3,?5,?7,?9?] | ||
a[-1] |
? |
9 |
a[-2] |
? |
7 |
a[-99] |
? |
nil |
你也可以一對(duì)數(shù)字[start,?count]
作為下標(biāo),這將返回一個(gè)數(shù)組,包含原數(shù)組中從start開始的count個(gè)元素。
a?=?[?1,?3,?5,?7,?9?] | ||
a[1,?3] |
? |
[3,?5,?7] |
a[3,?1] |
? |
[7] |
a[-3,?2] |
? |
[5,?7] |
??? 最后,你也可以在數(shù)組下標(biāo)中使用Range作為索引。Range可參看下一章。Range的起始值之間如果是2個(gè)點(diǎn)號(hào),則包括最后的結(jié)束值,如果是3個(gè)點(diǎn)號(hào),則Range的值不包括最后的那個(gè)邊界值。
a?=?[?1,?3,?5,?7,?9?] | ||
a[1..3] |
? |
[3,?5,?7] |
a[1...3] |
? |
[3,?5] |
a[3..3] |
? |
[7] |
a[-3..-1] |
? |
[5,?7,?9] |
[ ]方法有一個(gè)對(duì)應(yīng)的 []=
方法,這個(gè)方法是用來給指定的位置設(shè)置新的值,如果數(shù)組的下標(biāo)位置超過了數(shù)組的大小,那么指定的下標(biāo)將被設(shè)為指定的值,而中間沒有涉及到的位置為設(shè)為nil。如下面例子數(shù)組最大下標(biāo)為4,而對(duì)a[6]設(shè)置新值,a[5]則被設(shè)為nil。
a = [ 1, 3, 5, 7, 9 ] | ? | [1, 3, 5, 7, 9] |
a[1] = 'bat' | ? | [1, "bat", 5, 7, 9] |
a[-3] = 'cat' | ? | [1, "bat", "cat", 7, 9] |
a[3] = [ 9, 8 ] | ? | [1, "bat", "cat", [9, 8], 9] |
a[6] = 99 | ? | [1, "bat", "cat", [9, 8], 9, nil, 99] |
如果在[]=
方法中下標(biāo)索引為兩個(gè)數(shù)字(一個(gè)開始位置,一個(gè)長(zhǎng)度)或者一個(gè)Range,則對(duì)應(yīng)的一系列值將被重新設(shè)置。如果第二個(gè)參數(shù)即長(zhǎng)度為0,則右邊的值將被插入當(dāng)前位置,沒有值會(huì)被刪除。如果[ ]=右邊的長(zhǎng)度和左邊下標(biāo)中指定的長(zhǎng)度不一樣,則原數(shù)組自動(dòng)進(jìn)行調(diào)整。
a = [ 1, 3, 5, 7, 9 ] | ? | [1, 3, 5, 7, 9] |
a[2, 2] = 'cat' | ? | [1, 3, "cat", 9] |
a[2, 0] = 'dog' | ? | [1, 3, "dog", "cat", 9] |
a[1, 1] = [ 9, 8, 7 ] | ? | [1, 9, 8, 7, "dog", "cat", 9] |
a[0..3] = [] | ? | ["dog", "cat", 9] |
a[5] = 99 | ? | ["dog", "cat", 9, nil, nil, 99] |
數(shù)組有很多有用的方法,使用這些方法你能用數(shù)組實(shí)現(xiàn)隊(duì)列,堆棧,列表等各種數(shù)據(jù)結(jié)構(gòu)。
?
=>
?value pairs between braces.
h?=?{?'dog'?=>?'canine',?'cat'?=>?'feline',?'donkey'?=>?'asinine'?} | ||
| ||
h.length |
? |
3 |
h['dog'] |
? |
"canine" |
h['cow']?=?'bovine' | ||
h[12]????=?'dodecine' | ||
h['cat']?=?99 | ||
h |
? |
{"cow"=>"bovine",?"cat"=>99,?12=>"dodecine",?"donkey"=>"asinine",?"dog"=>"canine"} |
Hash
starts on page 317.
SongList
. Let's invent a basic list of methods we need in our SongList
. We'll want to add to it as we go along, but it will do for now.
Array
. Similarly, the ability to return a song at an integer position in the list is supported by arrays.
However, there's also the need to be able to retrieve songs by title, which might suggest using a hash, with the title as a key and the song as a value. Could we use a hash? Well, possibly, but there are problems. First a hash is unordered, so we'd probably need to use an ancillary array to keep track of the list. A bigger problem is that a hash does not support multiple keys with the same value. That would be a problem for our playlist, where the same song might be queued up for playing multiple times. So, for now we'll stick with an array of songs, searching it for titles when needed. If this becomes a performance bottleneck, we can always add some kind of hash-based lookup later.
We'll start our class with a basic initialize
method, which creates the Array
we'll use to hold the songs and stores a reference to it in the instance variable @songs
.
class?SongList ??def?initialize ????@songs?=?Array.new ??end end |
SongList#append
method adds the given song to the end of the @songs
array. It also returns self, a reference to the current SongList
object. This is a useful convention, as it lets us chain together multiple calls to append
. We'll see an example of this later.
class?SongList ??def?append(aSong) ????@songs.push(aSong) ????self ??end end |
deleteFirst
and deleteLast
methods, trivially implemented using
Array#shift
and
Array#pop
, respectively.
class?SongList ??def?deleteFirst ????@songs.shift ??end ??def?deleteLast ????@songs.pop ??end end |
append
returns the SongList
object to chain together these method calls.
list?=?SongList.new list. ??append(Song.new('title1',?'artist1',?1)). ??append(Song.new('title2',?'artist2',?2)). ??append(Song.new('title3',?'artist3',?3)). ??append(Song.new('title4',?'artist4',?4)) |
nil
is returned when the list becomes empty.
list.deleteFirst |
? |
Song:?title1--artist1?(1) |
list.deleteFirst |
? |
Song:?title2--artist2?(2) |
list.deleteLast |
? |
Song:?title4--artist4?(4) |
list.deleteLast |
? |
Song:?title3--artist3?(3) |
list.deleteLast |
? |
nil |
[]
, which accesses elements by index. If the index is a number (which we check using
Object#kind_of?
), we just return the element at that position.
class?SongList ??def?[](key) ????if?key.kind_of?(Integer) ??????@songs[key] ????else ??????#?... ????end ??end end |
list[0] |
? |
Song:?title1--artist1?(1) |
list[2] |
? |
Song:?title3--artist3?(3) |
list[9] |
? |
nil |
下面,我們要在SongList中修改[ ] 方法,使它能接受一個(gè)字符串參數(shù),返回以此為標(biāo)題的歌曲的??雌饋砦覀兒苋菀卓梢詫?shí)現(xiàn):我們有一個(gè)包含了很多Song對(duì)象的對(duì)象的數(shù)組,我們只需循環(huán)遍歷整個(gè)數(shù)組,找到匹配的那個(gè)就可以了。
class?SongList ??def?[](key) ????if?key.kind_of?(Integer) ??????return?@songs[key] ????else ??????for?i?in?0...@songs.length ????????return?@songs[i]?if?key?==?@songs[i].name ??????end ????end ????return?nil ??end end |
這樣已經(jīng)可以工作了,而且看上去很符合常規(guī):用一個(gè)for循環(huán)來遍歷數(shù)組。
有沒有更自然的方法呢?
當(dāng)然有,我們可以用Array的find方法。
class?SongList ??def?[](key) ????if?key.kind_of?(Integer) ??????result?=?@songs[key] ????else ??????result?=?@songs.find?{?|aSong|?key?==?aSong.name?} ????end ????return?result ??end end |
我們可以用if修飾符來使代碼更簡(jiǎn)短一些。
class?SongList ??def?[](key) ????return?@songs[key]?if?key.kind_of?(Integer) ????return?@songs.find?{?|aSong|?aSong.name?==?key?} ??end end |
方法find就是一個(gè)迭代器,這個(gè)方法重復(fù)不斷地執(zhí)行一個(gè)給定的block。塊和迭代器都是Ruby中比較有趣的特點(diǎn)。我們后面會(huì)進(jìn)一步來討論這些特點(diǎn)。
一個(gè)Ruby迭代器就是一個(gè)簡(jiǎn)單的能接收代碼塊的方法。第一眼看上去,Ruby中的block像C,Java,Perl中的一樣,但是實(shí)際上是有不同的。
首先,塊在源代碼中緊挨著方法調(diào)用,并且和這個(gè)方法的最后一個(gè)參數(shù)寫在同一行上。其次,這個(gè)塊不會(huì)立即被執(zhí)行,Ruby首先會(huì)記住這個(gè)塊出現(xiàn)的上下文(局部變量,當(dāng)前對(duì)象等),然后進(jìn)入方法,這里也是魔術(shù)開始的地方。
在方法里面,這個(gè)塊才會(huì)用yield來調(diào)用執(zhí)行,就像這個(gè)塊是方法本身一樣,每當(dāng)yield在方法中被執(zhí)行,這個(gè)塊就會(huì)被調(diào)用。當(dāng)這個(gè)塊執(zhí)行完退出后,控制將交給yield后面的語(yǔ)句(yield來自一個(gè)有20多年歷史的語(yǔ)言:CLU)。我們來看一個(gè)小例子。
def?threeTimes ??yield ??yield ??yield end threeTimes?{?puts?"Hello"?} |
Hello Hello Hello |
這個(gè)塊(用兩個(gè)大括號(hào)定義)賦給了一個(gè)方法threeTimes,在這個(gè)方法里面,yield執(zhí)行了3次,每次執(zhí)行它都會(huì)調(diào)用給定的block,即打印一個(gè)歡迎語(yǔ)句。使塊變得有趣的是你可以給塊傳遞參數(shù),并且從塊中得到結(jié)果。下面例子,我們將會(huì)得到小于一個(gè)指定值得Fibonacci 數(shù)列。
def?fibUpTo(max) ??i1,?i2?=?1,?1????????#?parallel?assignment ??while?i1?<=?max ????yield?i1 ????i1,?i2?=?i2,?i1+i2 ??end end fibUpTo(1000)?{?|f|?print?f,?"?"?} |
1?1?2?3?5?8?13?21?34?55?89?144?233?377?610?987 |
在這個(gè)例子中,yield接收一個(gè)參數(shù),這個(gè)參數(shù)將會(huì)在執(zhí)行的時(shí)候傳遞給指定的塊。在塊的定義中,參數(shù)用兩個(gè)豎線括起來,放在最前面。在這個(gè)例子中f用來接收yield傳遞的參數(shù),所以,這個(gè)塊才能打印這個(gè)序列。一個(gè)塊可以接受任意個(gè)參數(shù)。如果一個(gè)塊的參數(shù)和yield中傳遞的參數(shù)個(gè)數(shù)不一樣,將會(huì)怎樣呢?很巧合,這和我們?cè)诓⑿匈x值(parallel assignment)中談到的原則一樣(如果一個(gè)block只接收一個(gè)參數(shù),而yield提供的參數(shù)多于1個(gè),那么這些參數(shù)將被轉(zhuǎn)化為一個(gè)數(shù)組。)
傳遞給一個(gè)塊的參數(shù)可以是存在地局部變量,如果是這樣的話,那么這個(gè)局部變量的新值(如果在塊中被修改了)在塊退出后將會(huì)保留,這可能會(huì)有一定的副作用,但是這樣做有一個(gè)性能方面的考率。一個(gè)塊也可以返回一個(gè)結(jié)果給調(diào)用它的方法。這個(gè)塊中的最后一個(gè)表達(dá)式的值將會(huì)返回給方法,Array中的find方法就是這樣工作的。(find在Enumerable
中定義,被插入到了類Array
)
class?Array | ||
??def?find | ||
????for?i?in?0...size | ||
??????value?=?self[i] | ||
??????return?value?if?yield(value) | ||
????end | ||
????return?nil | ||
??end | ||
end | ||
| ||
[1,?3,?5,?7,?9].find?{|v|?v*v?>?30?} |
? |
7 |
這個(gè)用法中數(shù)組將連續(xù)的元素傳遞給指定的塊,如果這個(gè)塊返回true,則這個(gè)方法返回當(dāng)前對(duì)應(yīng)的元素值,如果沒有符合的值,則返回nil。這個(gè)方法顯示了迭代器的好處,Array類只作自己應(yīng)該做的,訪問數(shù)組元素,而應(yīng)用代碼只關(guān)注于特殊的需求。
Ruby中的集合對(duì)象中也包含其它一些常用迭代器,其中之二是each和collect。each可以認(rèn)為是最簡(jiǎn)單的迭代器,它們都會(huì)對(duì)集合的每個(gè)元素來調(diào)用塊。
[?1,?3,?5?].each?{?|i|?puts?i?} |
1 3 5 |
另一個(gè)是collect,它跟each類似,它將集合中的元素傳遞給一個(gè)塊,在塊中處理后返回一個(gè)包含處理結(jié)果的新數(shù)組。
["H",?"A",?"L"].collect?{?|x|?x.succ?} |
? |
["I",?"B",?"M"] |
這值得我們?cè)倩ㄒ恍r(shí)間來比較一下Ruby,C++,JAVA中的迭代器。在Ruby中,迭代器是一個(gè)簡(jiǎn)單的方法,每當(dāng)它產(chǎn)生一個(gè)新值,都會(huì)調(diào)用yield方法。使用迭代器只需要給這個(gè)迭代器傳遞一個(gè)塊,不需要像C++,Java中那樣創(chuàng)建輔助類來處理迭代器的狀態(tài)。從這一點(diǎn)和其它一些特點(diǎn)來說,Ruby是一種透明語(yǔ)言,當(dāng)你寫程序的時(shí)候,你只需關(guān)注于讓功能能夠?qū)崿F(xiàn),而不必編寫腳手架來支持語(yǔ)言的一些功能。
迭代器不僅僅用在數(shù)組和哈希等集合結(jié)構(gòu)上,它也能返回上面Fibonacci 例子中那樣的序列值,Ruby中的輸入輸出類也用到了迭代器,這些類實(shí)現(xiàn)了迭代器接口,每次返回一個(gè)I/O流的下一行。f?=?File.open("testfile") f.each?do?|line| ??print?line end f.close |
This?is?line?one This?is?line?two This?is?line?three And?so?on... |
讓我們?cè)倏纯戳硗庖粋€(gè)迭代器實(shí)現(xiàn)。Smalltalk也支持迭代器,如果你用smalltalk語(yǔ)言來計(jì)算一個(gè)數(shù)組中元素的和,可以這樣:
sumOfValues??????????????"Smalltalk?method" ????^self?values ??????????inject:?0 ??????????into:?[?:sum?:element?|?sum?+?element?value] |
inject
這樣工作:第一次指定的block被執(zhí)行的時(shí)候,sum被設(shè)成inject的參數(shù)(本例為0),element被設(shè)為數(shù)組的第一個(gè)元素。以后block被執(zhí)行的時(shí)候,sum的值設(shè)為上次block執(zhí)行后返回的值。這樣,sum就可以記錄總數(shù)了最后的inject的值是block最后一次執(zhí)行后返回的值。
Ruby 沒有inject
方法,但我們可以很容易的寫一個(gè),在這個(gè)例子中我們把它加入Array類。
class?Array | ||
??def?inject(n) | ||
?????each?{?|value|?n?=?yield(n,?value)?} | ||
?????n | ||
??end | ||
??def?sum | ||
????inject(0)?{?|n,?value|?n?+?value?} | ||
??end | ||
??def?product | ||
????inject(1)?{?|n,?value|?n?*?value?} | ||
??end | ||
end | ||
[?1,?2,?3,?4,?5?].sum |
? |
15 |
[?1,?2,?3,?4,?5?].product |
? |
120 |
盡管block經(jīng)常是用在迭代器中,但是,塊也有其它一些有用的用處。
block也可以用作定義一塊代碼,這些代碼必須在一定的事務(wù)控制下運(yùn)行。比如,你經(jīng)常會(huì)打開一個(gè)文件,對(duì)內(nèi)容作一些處理,然后需要確保文件在最后會(huì)被關(guān)閉。盡管我們可以用常規(guī)的方法實(shí)現(xiàn),但是,我們可以讓文件對(duì)象自己負(fù)責(zé)關(guān)閉它。一個(gè)簡(jiǎn)單例子如下(忽略了錯(cuò)誤處理等):
class?File ??def?File.openAndProcess(*args) ????f?=?File.open(*args) ????yield?f ????f.close() ??end end File.openAndProcess("testfile",?"r")?do?|aFile| ??print?while?aFile.gets end |
This?is?line?one This?is?line?two This?is?line?three And?so?on... |
這個(gè)小例子闡述了幾個(gè)技術(shù)點(diǎn)。方法openAndProcess
是一個(gè)類方法,這個(gè)方法可以獨(dú)立于任何File對(duì)象來單獨(dú)調(diào)用,即不需要生成類的實(shí)例。我們?cè)谶@個(gè)方法的參數(shù)列表中使用了 *args
,這表示所有調(diào)用時(shí)候的參數(shù)將作為數(shù)組傳遞到這個(gè)方法。而這個(gè)參數(shù)是傳遞給File.open方法的。
一旦這個(gè)文件被打開,openAndProcess
將調(diào)用yield,然后將打開的文件對(duì)象傳遞給這個(gè)block。當(dāng)block返回后,這個(gè)文件將被關(guān)閉。這種情況下,關(guān)閉文件的任務(wù)就從使用文件對(duì)象的用戶轉(zhuǎn)變?yōu)槲募旧砹恕?/p>
最后,這個(gè)例子用do..end來定義一個(gè)塊,這和用兩個(gè)打括號(hào)定義一個(gè)塊只是有優(yōu)先級(jí)別的區(qū)別,將在后面討論。
讓文件自己管理自己的生命周期十分有用,Ruby自帶的File類就提供了這樣的支持。如果File.open
調(diào)用時(shí)指定了一個(gè)block,那么這個(gè)塊就會(huì)用傳遞過來文件對(duì)象作為參數(shù)執(zhí)行,然后,當(dāng)塊結(jié)束后,這個(gè)文件會(huì)被文件對(duì)象關(guān)閉。這很有趣,也就是說File.open
方法有兩個(gè)版本,一個(gè)接受block,一個(gè)不接受block。當(dāng)沒有指定block調(diào)用這個(gè)方法的時(shí)候,這個(gè)方法只會(huì)返回一個(gè)打開的文件對(duì)象。方法Kernel::block_given?
提供了實(shí)現(xiàn)這種功能的可能,如果調(diào)用的時(shí)候給了一個(gè)block,這個(gè)方法將返回true,這樣,你也可以實(shí)現(xiàn)自己open方法:
class?File ??def?File.myOpen(*args) ????aFile?=?File.new(*args) ????#?If?there's?a?block,?pass?in?the?file?and?close ????#?the?file?when?it?returns ????if?block_given? ??????yield?aFile ??????aFile.close ??????aFile?=?nil ????end ????return?aFile ??end end |
讓我們?cè)倩貋砜纯醋詣?dòng)點(diǎn)唱機(jī),某些時(shí)候,我們需要處理點(diǎn)唱機(jī)和用戶的界面:很多按鈕,供用戶選擇歌曲和控制播放,我們需要給這些按鈕指定相應(yīng)的時(shí)間處理代碼,而Ruby中的block就很適合干這樣的事情。假設(shè)點(diǎn)唱機(jī)的設(shè)計(jì)者通過Ruby擴(kuò)展為我們提供了一個(gè)基本的按鈕類。
bStart?=?Button.new("Start") bPause?=?Button.new("Pause") #?... |
當(dāng)用戶按下按鈕之后如何處理呢?Button類提供了一個(gè)buttonPressed
方法,在按鈕按下時(shí)能被回調(diào)。所以最簡(jiǎn)單的方法創(chuàng)建Button的子類,然后在每個(gè)子類中實(shí)現(xiàn)buttonPressed
方法。
class?StartButton?<?Button ??def?initialize ????super("Start")???????#?invoke?Button's?initialize ??end ??def?buttonPressed ????#?do?start?actions... ??end endbStart?=?StartButton.new |
這樣做有兩個(gè)問題,首先這樣將產(chǎn)生大量子類,如果Button變化了,所有子類都需要維護(hù);第二,按鈕按下之后需要執(zhí)行的行為不應(yīng)該在按鈕上表示,這是點(diǎn)唱機(jī)的責(zé)任。我們可以用塊來消除這些問題
class?JukeboxButton?<?Button ??def?initialize(label,?&action) ????super(label) ????@action?=?action ??end ??def?buttonPressed ????@action.call(self) ??end end bStart?=?JukeboxButton.new("Start")?{?songList.start?} bPause?=?JukeboxButton.new("Pause")?{?songList.pause?} |
上面代碼的關(guān)鍵點(diǎn)在JukeboxButton#initialize
的第二個(gè)參數(shù),這個(gè)參數(shù)前面帶有一個(gè)&符號(hào),在Ruby中代表這個(gè)參數(shù)是一個(gè)塊,這個(gè)塊將會(huì)被轉(zhuǎn)化為一個(gè)Proc對(duì)象,并關(guān)聯(lián)道相應(yīng)變量。在本例中我們把它賦給實(shí)例變量@action 。
當(dāng)回調(diào)方法buttonPressed
執(zhí)行時(shí),我們用方法Proc#call
來調(diào)用相應(yīng)的block。
我們來看一個(gè)例子,這個(gè)例子用了方法proc,這個(gè)方法把一個(gè)block變?yōu)榱艘粋€(gè)Proc對(duì)象。
def?nTimes(aThing) | ||
??return?proc?{?|n|?aThing?*?n?} | ||
end | ||
| ||
p1?=?nTimes(23) | ||
p1.call(3) |
? |
69 |
p1.call(4) |
? |
92 |
p2?=?nTimes("Hello?") | ||
p2.call(3) |
? |
"Hello?Hello?Hello?" |
方法nTimes
返回一個(gè)Proc對(duì)象,這個(gè)對(duì)象引用了這個(gè)函數(shù)的參數(shù):aThing
。
即使這個(gè)參數(shù)在塊被調(diào)用時(shí)已經(jīng)不在自己的作用域里了,這個(gè)塊還是可以訪問這個(gè)參數(shù)。(比如p1.call(4),
這個(gè)時(shí)候,雖然對(duì)p1的賦值語(yǔ)句中的參數(shù)23已經(jīng)超出了作用域范圍,但是它仍然保存著23×4=92。)