一、心得體會(huì)
1缠导、今天完成了什么?
- 今天看了20頁(yè)的鎬頭書(139-160)
- 看了10個(gè)controller
2溉痢、今天收獲了什么僻造?
- Ruby中的循環(huán)有哪些?
- 捕獲孩饼、異常和拋出是怎么工作的髓削?
- 含有異常信息的數(shù)據(jù)包(package)是Exception類或其子類的一個(gè)對(duì)象。
- Ruby將相關(guān)的Exception對(duì)象的引用放在全局變量$!中镀娶,這與任何隨后的異常處理不相干立膛。不帶任何參數(shù)的調(diào)用raise,它會(huì)重新引發(fā)$!中的異常梯码。舉個(gè)栗子:
op_file = File.open(opfile_name, "w")
begin
# 這段代碼引發(fā)異常會(huì)被下面的rescue語(yǔ)句捕獲
while data = socket.read(512)
op_file.write(data)
end
rescue SystemCallError
$stderr.print "IO failed: " + $!
op_file.close
File.delete(opfile_name)
raise
end
- 10個(gè)controller:取送宝泵、取送日志、取送跟進(jìn)轩娶、取送跟進(jìn)儿奶、疑難原因、區(qū)縣(districts)鳄抒、禮物(gifts)闯捎、催單(hastens)搅窿、盤點(diǎn)(inventories)、盤點(diǎn)訂單
3隙券、今天犯了哪些錯(cuò)誤男应?
4、明天需要做哪些工作娱仔?
二沐飘、讀書筆記
昨天我學(xué)到的最重要的是什么?
Ruby有幾種循環(huán):
- while
- until
- 迭代器循環(huán)牲迫,如times耐朴、upto(遞增)、downto(遞減)盹憎、each筛峭、step
- For..in
7.6.3 Break、Redo和Next
循環(huán)控制結(jié)構(gòu)break陪每,redo和next可以讓你改變循環(huán)或者迭代的正常流程影晓。
- break終止最接近的封閉循環(huán)體,然后繼續(xù)執(zhí)行block后面的語(yǔ)句檩禾。
- redo從循環(huán)頭重新執(zhí)行循環(huán)挂签,但不重計(jì)算循環(huán)條件表達(dá)式或者獲得迭代中的下一個(gè)元素。
- next跳到本次循環(huán)的末尾盼产,并開始下一次迭代
while line = gets
next if line =~ /^\s*#/ # skip comments
break if line =~/^END/ # stop at end
redo if line.gsub!(/'(.*?)'/) { eval($1) }
# process line ...
end
這些關(guān)鍵字還可以和任何基于迭代的循環(huán)機(jī)制一起使用饵婆。
i = 0
loop do
i += 1
next if i <3
print i
break if i > 4
end
輸出結(jié)果:
345
7.6.4 Retry
redo語(yǔ)句使得一個(gè)循環(huán)重新執(zhí)行當(dāng)前的迭代,但是有時(shí)你需要從頭重新執(zhí)行一個(gè)循環(huán)戏售,retry語(yǔ)句就是做這件事的侨核,它重新執(zhí)行任意類型的迭代式循環(huán)。
for i in 1..100
print "Now at #{i}. Restart? "
retry if gets =~ /^y/i
end
交互式地運(yùn)行這段代碼灌灾,你會(huì)看到:
Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
retry在重新執(zhí)行之前會(huì)重新計(jì)算傳遞給迭代的所有參數(shù)搓译。下面是一個(gè)DIY的until循環(huán)的例子。
def do_until(cond)
break if cond
yield
retry
end
i = 0
do_until(i>10) do
print i, " "
i += 1
end
7.7 變量作用域紧卒、循環(huán)和Blocks
while侥衬、until和for循環(huán)內(nèi)建到了RUby語(yǔ)言中诗祸,但沒(méi)有引入新的作用域跑芳,前面已經(jīng)存在的局部變量可以在循環(huán)中使用,而循環(huán)中新創(chuàng)建的局部變量也可以在循環(huán)后使用直颅。
被迭代器使用的block(比如loop和each)與此略有不同博个,通常,在這些block中創(chuàng)建的局部變量無(wú)法在block外訪問(wèn)功偿。
[1, 2, 3 ].each do |x|
y = x + 1
end
[x, y]
輸出結(jié)果:
NameError: undefined local variable or method `y' for main:Object
from (irb#1):126
然而盆佣,如果執(zhí)行block的時(shí)候往堡,一個(gè)局部變量已經(jīng)存在且與block中的變量同名,那么block將使用此已有的局部變量共耍,因而虑灰,它的值在塊后面仍然可以使用。
如下面的例子所示痹兜,此即適用于block中的普通變量也適用于block的參數(shù)穆咐。
x = nil
y = nil
[1, 2, 3].each do |x|
y = x + 1
end
[x, y] -> [3, 4]
為什么終端結(jié)果是這樣的:
[nil, 4]
注意在外部作用域中變量不必有值:Ruby解釋器只需要看到它即可。
if false
a = 1
end
3.times {|i| a = i}
第8章
異常字旭、捕獲和拋出(Exceptions对湃,Catch and Throw)
到目前為止,我們都是在歡樂(lè)谷中開發(fā)代碼遗淳,每個(gè)程序庫(kù)的調(diào)用都是成功的拍柒,用戶從來(lái)沒(méi)有輸入不準(zhǔn)確的數(shù)據(jù)。但是屈暗,在現(xiàn)實(shí)世界中拆讯,錯(cuò)誤會(huì)發(fā)生,好的程序可以預(yù)見它們的發(fā)生养叛,然后優(yōu)雅地處理這些錯(cuò)誤往果,但這并非總像聽起來(lái)那么簡(jiǎn)單,通常檢測(cè)到錯(cuò)誤出現(xiàn)的那部分代碼一铅,缺少有關(guān)如何處理它的上下文信息陕贮。
舉個(gè)栗子:
試圖去打開一個(gè)并不存在的文件,在一些情況下是可行的的潘飘,但在別的情況下卻是致命的錯(cuò)誤肮之,文件處理模塊要如何做呢?
傳統(tǒng)的做法是使用返回碼卜录。open方法在失敗時(shí)會(huì)返回一些特定值戈擒,然后這個(gè)值會(huì)沿著調(diào)用例程的層次往回傳播,直到有函數(shù)想要處理它艰毒。
這種做法問(wèn)題是筐高,管理所有這些錯(cuò)誤碼是一件痛苦的事情。如果函數(shù)首先調(diào)用open丑瞧,然后調(diào)用read柑土,最后調(diào)用close方法,而且每個(gè)方法都有可能返回錯(cuò)誤標(biāo)識(shí)绊汹,那么當(dāng)函數(shù)將返回碼返回給調(diào)用者時(shí)稽屏,該如何區(qū)分這些錯(cuò)誤碼呢?
異常在很大程度上解決了這個(gè)問(wèn)題西乖,異常允許把錯(cuò)誤信息打包到一個(gè)對(duì)象中狐榔,然后該異常對(duì)象被自動(dòng)傳播調(diào)用棧(calling stack)坛增,直到運(yùn)行系統(tǒng)找到明確聲明直到如何處理這類異常的代碼為止。
8.1 異常類(The Ecxeption Class)
含有異常信息的數(shù)據(jù)包(package)是Exception類薄腻、或其子類的一個(gè)對(duì)象收捣。Ruby預(yù)定義了一個(gè)簡(jiǎn)潔的異常層次結(jié)構(gòu),這個(gè)層次結(jié)構(gòu)使得處理異常變得相當(dāng)簡(jiǎn)單庵楷。
當(dāng)需要引發(fā)(raise)異常時(shí)坏晦, 可以使用某個(gè)內(nèi)建的Exception類,或者創(chuàng)建自己異常類嫁乘。如果創(chuàng)建自己的異常類昆婿,可能你希望它從StandardError類或其子類派生,否則蜓斧,你的異常在默認(rèn)情況下不會(huì)被捕獲仓蛆。
每個(gè)Exception都關(guān)聯(lián)有一個(gè)消息字符串和棧回溯信息(backtrace)挎春。如果定義自己的異常看疙,可以添加額外的信息。
8.2 處理異常(Handing Exception)
我們的點(diǎn)唱機(jī)用TCP套接字從互聯(lián)網(wǎng)下載歌曲直奋。它的基本代碼很簡(jiǎn)單(假設(shè)文件名個(gè)套接字都已經(jīng)創(chuàng)建好)能庆。
op_file = File.open(opfile_name, "w")
while data = socket.read(512)
op_file.write(data)
end
如果下載過(guò)程中得到一個(gè)致命錯(cuò)誤浦马,會(huì)發(fā)生什么呢嘀趟?我們肯定不想在歌曲列表中存儲(chǔ)一首不完整的歌曲∩啵“I Did it My...”
讓我們添加一些處理異常的代碼邮绿,看看它是如何幫助處理異常的渠旁。在一個(gè)beigin/end塊中,使用一個(gè)或多個(gè)rescue語(yǔ)句告訴Ruby希望處理的異常類型船逮。
在這個(gè)特定的例子中顾腊,我們感興趣的是捕獲SystemCallError異常(同時(shí)暗含著任何SystemCallError子類的異常),所以它就是出現(xiàn)在resuce行的異常類型挖胃,在這個(gè)錯(cuò)誤處理block中杂靶,我們報(bào)告了錯(cuò)誤,關(guān)閉和刪除了輸出文件酱鸭,同時(shí)重新引起異常吗垮。
op_file = File.open(opfile_name, "w")
begin
#這段代碼引發(fā)異常會(huì)被
#下面的rescue語(yǔ)句捕獲
while data = socket.read(512)
op_file.write(data)
end
rescue SystemCallError
$stderr.print "IO failed: " + $!
op_file.close
File.delete(opfile_name)
raise
end
當(dāng)異常被引發(fā)時(shí),Ruby將關(guān)于Exception對(duì)象的引用放在全局變量$!中凛辣,這與任何隨后的異常處理不相干抱既。
Exception
fatal(used internally by Ruby)
NoMemoryError
-
ScriptError
- LoadError
- NotImplementError
- SyntaxError
-
SignalException
- Interrupt
-
StandardError
- ArgumentError
- IOError
- EOFError
- IndexError
- LocalJumpError
- NameError
- NoMethodError
- RangeError
- FloatDomainError
- RegexpError
- RuntimeError
- SecurityError
- SystemCallError
- system-dependent exception(Errno::XXX)
- ThreadError
- TypeError
- ZeroDivisionError
SystemExit
SystemStackError
這個(gè)感嘆號(hào)大概反映出了我們的驚訝职烧,我們的代碼竟然會(huì)導(dǎo)致錯(cuò)誤扁誓!在前面的例子中防泵,我們用$!變量去格式化錯(cuò)誤信息。
關(guān)閉和刪除文件后蝗敢,我們可以不帶任何參數(shù)來(lái)調(diào)用raise捷泞,它會(huì)重新引導(dǎo)$!中的異常,這是一個(gè)有用的技術(shù)寿谴,它允許我們先編寫代碼過(guò)濾掉一些異常锁右,再把不能處理的異常傳遞到更高的層次。這幾乎就像實(shí)現(xiàn)了一個(gè)錯(cuò)誤處理的繼承層次結(jié)構(gòu)讶泰。
在begin塊中可以有多個(gè)rescue子句(clause)咏瑟,每個(gè)rescue子句可以指示捕獲多個(gè)異常,在rescue子句的結(jié)束處痪署,你可以提供一個(gè)Ruby的局部變量名來(lái)接收匹配的異常码泞,許多人發(fā)現(xiàn)這比導(dǎo)出使用$!有更好的可讀性。
begin
eval string
rescue SyntaxError, NameError => boom
print "String doesn't compileL: " + boom
rescue Standard => bang
print "Error running script: " + bang
end
RUby如何決定執(zhí)行哪個(gè)rescue子句呢狼犯?
這個(gè)處理非常類似于對(duì)case語(yǔ)句的處理余寥,RUby用引發(fā)的異常依次比較begin塊中每個(gè)rescue子句的每個(gè)參數(shù)。如果引發(fā)的異常匹配了一個(gè)參數(shù)悯森,Ruby就執(zhí)行rescue的程序體宋舷,同時(shí)停止比較。
匹配是用parameter ===$!完成的瓢姻。對(duì)于大多數(shù)異常來(lái)說(shuō)祝蝠,如果rescue子句給出的類型,與當(dāng)前引發(fā)的異常的類型相同幻碱,或者它是引發(fā)異常的超類(superclass)续膳,這意味著匹配是成功的。如果編寫一個(gè)不帶參數(shù)表的rescue子句收班,它的默認(rèn)參數(shù)是StandardError坟岔。
注意:可以這樣進(jìn)行比較的原因是:異常是類,而類進(jìn)而是某種Module摔桦。===方法是為模塊定義的社付,如果操作數(shù)的類與接收者相同或者接收者的祖先,這個(gè)方法返回true邻耕。
如果沒(méi)有任何rescue子句與之匹配鸥咖,或者異常在begin/end塊外面被引發(fā),Ruby就沿著調(diào)用棧向上查找兄世,在調(diào)用者上尋找異常的處理者啼辣,接著在調(diào)用者的調(diào)用者上尋找,依次類推御滩。
盡管鸥拧,rescue子句的參數(shù)通常是Exception類的名稱党远,實(shí)際上它們可以是任何返回的Exception類的表達(dá)式(包括方法調(diào)用)。
8.2.1 系統(tǒng)錯(cuò)誤(System Errors)
當(dāng)對(duì)操作系統(tǒng)的調(diào)用返回錯(cuò)誤碼時(shí)富弦,會(huì)引發(fā)系統(tǒng)錯(cuò)誤沟娱,在POSIX系統(tǒng)上,這些錯(cuò)誤名稱有諸如EAGAIN和EPERM等(在Unix機(jī)器上腕柜,鍵入man error济似,你會(huì)得到這些錯(cuò)誤的列表)。
Ruby得到這些錯(cuò)誤盏缤,把每個(gè)錯(cuò)誤裝(wrap)到特定的對(duì)象中砰蠢,每個(gè)錯(cuò)誤都是SystemCallError的子類,定義在Errno模塊中唉铜,這意味著娩脾,你會(huì)發(fā)現(xiàn)類名如Errno::EAGIN, ERRno::EIO和Errno::EPERM等的異常,如果想得到底層的系統(tǒng)錯(cuò)誤碼打毛,則把每個(gè)Errno異常對(duì)象有一個(gè)Errno(有點(diǎn)令人疑惑)的類常量(class constant)柿赊,它包含相應(yīng)的系統(tǒng)錯(cuò)誤。
Errno::EAGAIN::Errno -> 35
Errno::EPERM::Errno -> 1
注意到EWOULDBLOCK和EAGAIN有相同的錯(cuò)誤碼幻枉,這是我電腦上的操作系統(tǒng)的一個(gè)特性——兩個(gè)常量映射到相同的錯(cuò)誤碼碰声。為了處理這種情況,Ruby做出了安排熬甫,讓Errno::EAGAIN和Errno::EWOULDBOCK在rescue子句中被等同對(duì)待胰挑,如果你要求rescue其中一個(gè)錯(cuò)誤,那么另一個(gè)也會(huì)被rescue椿肩。通過(guò)定義SystemCallError#===可以做到這點(diǎn)瞻颂,因此,如果要比較SystemCallError的兩個(gè)子類郑象,是比較它們的錯(cuò)誤碼而不是它們?cè)趯哟谓Y(jié)構(gòu)中的位置贡这。
8..2.2 善后(Tidying Up)
有時(shí)候你需要寶恒一些處理在block結(jié)束時(shí)能夠被執(zhí)行,而不管是否有異常引發(fā)厂榛。比如盖矫,也許在block的入口處(entry)打開一個(gè)文件,需要確保當(dāng)block退出時(shí)它會(huì)被關(guān)閉击奶。
ensure子句就是做這個(gè)的辈双。ensure跟在最后的rescue子句后面,它包含一段block退出時(shí)總是被執(zhí)行的代碼柜砾。不管block是否正常退出湃望,是否引發(fā)并rescue異常,或者是否被捕獲的異常終止——這個(gè)ensure塊總會(huì)得到運(yùn)行。
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
盡管不是那么有用证芭,else子句是一個(gè)類似ensure子句的構(gòu)造瞳浦,如果存在的話,它會(huì)出現(xiàn)在rescue子句之后和任何一個(gè)ensure子句之前檩帐。else子句的程序體术幔,只有當(dāng)主代碼體沒(méi)有引發(fā)任何異常時(shí)才會(huì)被執(zhí)行另萤。
f = File.open("testfile")
begin
# ..
rescue
# ..
else
# ..
puts "congratulations -- no errors!"
ensure
f.close unless f.nil
end
8.2.3 再次執(zhí)行(Play It Again)
有時(shí)候也許可以糾正異常的原因湃密。在這些例子中,你可以在rescue子句中使用retry語(yǔ)句去重復(fù)執(zhí)行整個(gè)begin/end區(qū)塊四敞。顯然這很可能導(dǎo)致無(wú)線循環(huán)泛源,所以使用這個(gè)特性應(yīng)該倍加小心(同時(shí)把一根手指輕輕放在鍵盤的中斷鍵上,隨時(shí)準(zhǔn)備著)忿危。
下面的例子再出現(xiàn)異常時(shí)會(huì)重新執(zhí)行达箍,它來(lái)自Minero Aoki的net/stmp.rb。
@esmtp = true
begin
#首先嘗試擴(kuò)展登錄铺厨,如果因?yàn)榉?wù)器不支持而失敗
#則使用正常登陸
if @esmtp then
@command.ehlo(helodom)
else
@command.helo(helodom)
end
rescue ProtocolError
if @esmtp then
@esmtp = false
retry
else
raise
end
end
這段代碼首先使用EHLO命令試圖連接SMTP服務(wù)器缎玫,而這個(gè)命令并沒(méi)有被廣泛支持,如果連接嘗試失敗了解滓,則設(shè)置@esmtp變量為false赃磨,同時(shí)重試連接。如果第二次連接也失敗了洼裤,則引發(fā)異常給它的調(diào)用者邻辉。
8.3 引發(fā)異常(Raising Exceptions)
到目前為止,我們一直都處于守勢(shì)腮鞍,處理那些被別人引發(fā)的異常值骇,該是輪到我們進(jìn)攻的時(shí)候了(有些人說(shuō)本書這些溫和的作者總是咄咄逼人,但那是另外一本書)移国。
可是使用Kernel.raise方法在代碼中引發(fā)異常(或者它有點(diǎn)判決意味的同義詞吱瘩,Kernel.fail)
raise
raise "bad mp3 encoding"
raise InterfaceException, "Keyboard failure", caller
第一種形式只是簡(jiǎn)單地重新引發(fā)當(dāng)前異常(如果沒(méi)有當(dāng)前異常的話,已發(fā)RuntimeError)迹缀。這種形式用于首先截獲異常再將其繼續(xù)傳遞的異常處理方法中搅裙。
第二種形式創(chuàng)建新的RuntimeError異常,把它的消息設(shè)置為指定的字符串裹芝,然后異常隨著調(diào)用棧向上引發(fā)部逮。
第三種形式使用第一個(gè)參數(shù)創(chuàng)建異常,然后把相關(guān)聯(lián)的消息設(shè)置給第二個(gè)參數(shù)嫂易,同時(shí)把棧信息(trace)設(shè)置給第三個(gè)參數(shù)兄朋。通常,第一個(gè)參數(shù)是Exception層次結(jié)構(gòu)中某個(gè)類的名稱,或者是某個(gè)異常類的對(duì)象實(shí)例的引用颅和,通常使用Kernel.caller方法產(chǎn)生棧信息傅事。
下面是使用raise的典型例子。
raise
raise "Missing name" if name.nil?
if i >= names.size
raise IndexError, "#{i} >= size (#{names.size})"
end
raise ArgumentError, "Name too big", caller
最后這個(gè)例子從椣坷回溯信息刪除當(dāng)前函數(shù)蹭越,這在程序庫(kù)模塊中十分有用〗探欤可以更進(jìn)一步:下面的代碼通過(guò)只將調(diào)用棧的子集傳遞給新異常响鹃,從而達(dá)到從棧回溯信息中刪除兩個(gè)函數(shù)的目的案训。
raise ArgumentError, "name too big", caller[1..-1]
8.3.1 添加信息到異常(Adding Information to Exceptions)
你可以定義自己的異常买置,保存任何需要從錯(cuò)誤發(fā)生地傳遞出去的信息。
例如强霎,取決于外部環(huán)境忿项,某種類型的網(wǎng)絡(luò)錯(cuò)誤可能是暫時(shí)的。如果這種錯(cuò)誤發(fā)生了城舞,而環(huán)境是適宜的轩触,則可以在異常中設(shè)置一個(gè)標(biāo)志,告訴異常處理程序重試這個(gè)操作可能是值得家夺。
class RetryExceptipn < RuntimeError
attr :ok_to_retry
def initialize(ok_to_retry)
@ok_to_retry = ok_to_retry
end
end
注意:從技術(shù)層面講脱柱,這個(gè)參數(shù)可以是任何對(duì)象,只要它能響應(yīng)消息Exception秦踪,且這個(gè)消息返回一個(gè)能夠滿足object.kind_of?(Exception)為真的對(duì)象褐捻。
在下面的代碼里面,發(fā)生了一個(gè)暫時(shí)的錯(cuò)誤椅邓。
def read_data(socket)
data = socket.read(512)
if data.nil?
raise RetryException.new(true), "transient read error"
end
# .. 正常處理
end
在上一級(jí)的調(diào)用棧處理了異常柠逞。
begin
stuff = read_data(socket)
# .. process stuff
resuce RetryException => detail
retry of detail.ok_to_retry
raise
end
8.4 捕獲和拋出(Catch and Throw)
盡管raise和rescue的異常機(jī)制對(duì)程序出錯(cuò)時(shí)終止執(zhí)行已經(jīng)夠用了,但是如果在正常處理過(guò)程期間能夠從一些深度嵌套的結(jié)構(gòu)中跳轉(zhuǎn)出來(lái)景馁,則是很棒的板壮,catch和throw應(yīng)運(yùn)而生,可以方便地做到這點(diǎn)合住。
catch (:done) do
while line = gets
throw :done unless fields = line.split(/\t/)
songlist.add(Song.new(*fields))
end
songlist.play
end
catch定義了以給定名稱(可能符號(hào)或字符串)為標(biāo)簽的block绰精,這個(gè)block會(huì)正常執(zhí)行直到暈倒throw為止。
當(dāng)Ruby碰到throw透葛,它迅速回溯(zip back)調(diào)用棧笨使,用匹配的符號(hào)尋找catch代碼塊,當(dāng)發(fā)現(xiàn)它之后僚害,Ruby將棧清退(unwind)到這個(gè)為止并終止該block硫椰。所以,在前面的例子中,如果輸入沒(méi)有包含正確格式化的行靶草,throw會(huì)跳到相應(yīng)的catch代碼塊的結(jié)束處蹄胰,不僅終止了while循環(huán),而且跳出了歌曲列表的播放奕翔。
如果調(diào)用throw時(shí)制定了可選的第二個(gè)參數(shù)裕寨,這個(gè)值會(huì)作為catch的值返回。
在下面的例子中派继,如果響應(yīng)任意提示符時(shí)鍵入宾袜!,使用throw終止與用戶的交互互艾。
def prompt_and_get(prompt)
print prompt
res = readline.chomp
throw :quit_requested if res == "!"
res
end
catch :quit_requested do
name = prompt_and_get("Name: ")
age = prompt_and_get("Age:")
sex = prompt_and_get("Sex:")
end
這個(gè)例子說(shuō)明了throw沒(méi)必要出現(xiàn)在catch的靜態(tài)作用域中试和。
第9章 Modules
模塊一種將方法讯泣、類與常量組織在一起的方式纫普,模塊給你提供了兩個(gè)主要的好處:
1.模塊提供了命名空間(namespace)來(lái)防止命令沖突
2.模塊實(shí)現(xiàn)了mixin功能
9.1 命名空間(Namespace)
當(dāng)你開始編寫越來(lái)越大的Ruby程序時(shí),你自然會(huì)發(fā)現(xiàn)自己編寫了許多可重用的代碼——將先關(guān)的例程(routine)組成一個(gè)庫(kù)通常是合適的好渠。你會(huì)希望將這些代碼分解不同的文件昨稼,使其內(nèi)容可以被其他不同的Ruby程序共享。
通常代碼會(huì)被組織為類拳锚,你可能會(huì)讓一個(gè)類(或一組相關(guān)的類)對(duì)應(yīng)一個(gè)文件假栓。
不過(guò),有時(shí)你想要把那些無(wú)法自然構(gòu)成類的部分集合到一起霍掺。
一種初步的方法是將所有內(nèi)容放到一個(gè)文件中匾荆,然后簡(jiǎn)單地在任何需要它的程序中加載(load)它,這是C語(yǔ)言工作的方式杆烁,不過(guò)牙丽,這種方式有一個(gè)問(wèn)題。假設(shè)你要編寫一組三角函數(shù)sin兔魂、cos等等烤芦,你將它們?nèi)咳揭粋€(gè)文件trig.rb中,為后世享用析校。
同時(shí)构罗,Sally想要模擬善良和邪惡,并且她編寫了一組對(duì)自己有用的例程智玻,包括be_good和sin遂唧,并將它們放到moral.rb中,Joe想要編寫一個(gè)程序找出針尖上有多少跳舞的天使吊奢,想要在他的程序中加載盖彭。
答案是使用模塊機(jī)制,模塊定義了一個(gè)namespace(命名空間),它是一個(gè)沙箱(sandbox)谬泌,你的方法和常量可以在其中任意發(fā)揮滔韵,而無(wú)需擔(dān)心被其他方法或常量干擾,三角函數(shù)可以放到一個(gè)模塊中掌实。
module Trig
PI = 3.1415926
def Trig.sin(x)
end
def Trig.cos(x)
end
end
而品行好壞的方法可以放到另一個(gè)模塊中陪蜻。
module Moral
VARY_BAD = 0
BAD = 1
def Moral.sin(badness)
end
end
模塊常量的命名和類常量一樣,都以大寫字母開頭贱鼻,方法定義同樣看起來(lái)很相似:這些模塊方法就類似于類方法的定義宴卖。
如果第三方的程序想要使用這些模塊,它可以簡(jiǎn)單地加載兩個(gè)文件(使用Ruby的require語(yǔ)句)并引用它們的完整名稱(qualified name)邻悬。
require 'trig'
require 'moral'
y = Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)
同類方法一樣症昏,你可以用模塊名和句點(diǎn)來(lái)調(diào)用模塊方法, 使用模塊名和兩個(gè)冒號(hào)來(lái)引用常量父丰。
9.2 Mixin
模塊有另一個(gè)妙用肝谭,它提供了一種稱為mixin的功能,以雷霆之勢(shì)蛾扇,極大地的消除了對(duì)多重繼承的需要攘烛。
在上一節(jié)的示例中,我們定義了模塊方法镀首,它們的名字都以模塊名為前綴坟漱,如果這讓你想到類方法,你接下來(lái)的想法可能是“如果我在模塊內(nèi)定義實(shí)例方法會(huì)怎么樣更哄?”
好問(wèn)題芋齿,模塊并沒(méi)有實(shí)例,因?yàn)槟K并不是類成翩,不過(guò)觅捆,你可以在類的定義中,include一個(gè)模塊捕传,當(dāng)包含發(fā)生時(shí)惠拭,模塊所有的實(shí)例方法瞬間在類中也可以使用了。它們被混入(mixin)了庸论,實(shí)際上职辅,所混入的模塊其實(shí)際行為就像是一個(gè)超類。
module Debug
def who_am_i?
"#{self.class.name} (\##{self.object_id}): #{self.to_s}"
end
end
class Phonograph
include Debug
# ...
end
class EightTrack
include Debug
# ..
end
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.who_am_i?
et.who_am_i?
通過(guò)包含Debug模塊聂示,Phonograph和EightTrack都得以訪問(wèn)who_am_i?這個(gè)實(shí)例方法域携。
在繼續(xù)前行之前,我們將探討關(guān)于include語(yǔ)句的幾點(diǎn)問(wèn)題鱼喉,首先秀鞭,include與文件無(wú)關(guān)趋观,C程序員使用叫做#include的預(yù)處理器指令,在編譯期將一個(gè)文件的內(nèi)容插入到另一個(gè)文件中锋边,Ruby語(yǔ)句只是簡(jiǎn)單地產(chǎn)生一個(gè)指向指定模塊的引用皱坛。如果模塊位于另一個(gè)文件中,在使用include之前豆巨,你必須使用require(或者不那么常用的旁系剩辟,load)將文件加載進(jìn)來(lái)。第二點(diǎn)往扔,Ruby的include并非簡(jiǎn)單地將模塊的實(shí)例方法拷貝到類中贩猎,相反,它建立一個(gè)由類到所包含模塊的引用萍膛。
如果多個(gè)類包含這個(gè)模塊吭服,它們都指向相同的內(nèi)容,即使當(dāng)程序正在運(yùn)行時(shí)蝗罗,如果你改變模塊中一個(gè)方法的定義艇棕,所有包含這個(gè)模塊的類都會(huì)表現(xiàn)出新的行為。
Mixin為你向類中添加功能绿饵,提供了一種控制精巧的方式欠肾,不過(guò)瓶颠,它們真正的力量的拟赊,當(dāng)mixin的代碼和使用它的類中的代碼開始交互時(shí),它們會(huì)一起迸發(fā)出來(lái)粹淋,讓我們以標(biāo)準(zhǔn)的Ruby mixin——Comparable為例吸祟。
你可以使用Comparable mixin向類中添加比較操作符(<, <=, ==, >=和>)以及between?方法。為了使其能夠工作桃移,Comparable假定任何使用它的類都定義了<=>操作符屋匕,這樣,作為類的一個(gè)編寫者借杰,你定義一個(gè)方法<=>过吻,再包含Comparable,然后就可以免費(fèi)得到6個(gè)比較函數(shù)蔗衡,讓我們用Song類來(lái)嘗試一下纤虽,令它們基于時(shí)長(zhǎng)來(lái)進(jìn)行比較,我們所要做的就是包含Comparable模塊并實(shí)現(xiàn)比較操作符<=>绞惦。
class Song
include Comparable
def initialize(name, artist, duration)
@name = name
@artist = artist
@duration = duration
end
def <=>(other)
self.duration <=> other.duration
end
end
我們可以用幾首測(cè)試歌曲來(lái)檢查一下結(jié)果逼纸。
song1 = Song.new("My Way", "Sinatra", 225)
song2 = Song.new("Bicyclops", "Fleck", 260)
song1 <=> song2
song2 < song2
song1 == song1
song1 > song2
9.3 迭代器與可枚舉模塊(lterators and the Enumerable Module)
你可能已經(jīng)注意到Ruby收集(collection)類支持大量針對(duì)收集的各種操作:遍歷、排序等等济蝉,你可能會(huì)想杰刽,“哎呀菠发,如果我自己的類也能支持這些出色的特性,那就太好了贺嫂!”
當(dāng)然滓鸠,你的類可以支持所有這些出色的特性,感謝mixin和Enumerable模塊的魔力第喳,你要做的就是編寫一個(gè)稱為each的迭代器哥力,由它依次返回收集中的元素。包含Enumerable墩弯,然后你的類瞬間支持諸如map吩跋、include?和find_all等操作。
如果在你收集的對(duì)象中使用<=>方法是想了有意義的排序語(yǔ)義渔工,你還會(huì)得到諸如min锌钮、max和sort等方法。
9.4 組合模塊(Composing Modules)
我們討論了Enumerable的inject方法引矩,Enumerable是另一個(gè)標(biāo)準(zhǔn)的mixin梁丘,它基于宿主類(host class)中的each實(shí)現(xiàn)了許多方法,因此旺韭,我們可以在任何包括了Enumerable模塊并定義了each方法的類中使用了inject氛谜。許多內(nèi)建的類都是如此。
[1, 2, 3, 4, 5 ].inject {|v, n| v+n }
('a'..'m').inject {|v,n| v+n}
我們還可以定義自己的類以包含Enumerable区端,繼而得到inject支持值漫。
class VowelFinder
include Enumerable
def initialize(string)
@string = string
end
def each
@string.scan(/[aeiou]/) do |vowel|
yield vowel
end
end
end
vf = VowelFinder.new("the quick brown fox jumped")
vf.inject {|v,n| v+n}
注意,我們使用了和前面實(shí)例中調(diào)用inject的相同模式——使用它來(lái)求和织盼,當(dāng)作用于數(shù)字時(shí)杨何,它返回算術(shù)和,當(dāng)作用于字符串時(shí)沥邻,返回串聯(lián)的字符串危虱。我們也可以使用一個(gè)模塊來(lái)封裝這個(gè)功能。
module Summable
def sum
inject {|v, n| v+n}
end
end
class Array
include Summable
end
class Range
include Summable
end
class VomelFinder
include Summable
end
[1, 2, 3, 4, 5].sum
vf = VowelFinder.new("the quick brown fox jumped")
vf.sum
9.4.1 Mixin中的實(shí)例變量(Instance variables in Mixins)
從C++轉(zhuǎn)向Ruby的人經(jīng)常問(wèn)我們唐全,“mixin中的實(shí)例變量會(huì)如何呢埃跷?在C++中,我必須兜好幾個(gè)圈子才能控制如何在多重繼承中共享變量邮利。Ruby是如何處理的呢弥雹?”
我們告訴他們,好吧近弟,對(duì)初學(xué)者來(lái)說(shuō)缅糟,這根本不是什么問(wèn)題,回憶一下Ruby中實(shí)例變量是如何工作的:當(dāng)前綴為@的變量第一次出現(xiàn)時(shí)祷愉,即在當(dāng)前對(duì)象(也就是self)中創(chuàng)建實(shí)例變量窗宦。
對(duì)mixin來(lái)說(shuō)赦颇,這意味著你要混入客戶類中的模塊,可能會(huì)在客戶對(duì)象中創(chuàng)建實(shí)例變量赴涵,并可能使用attr_reader或類似方法媒怯,定義這些實(shí)例變量的訪問(wèn)方法。例如髓窜,下面實(shí)例中的Observable模塊扇苞,會(huì)向包含它類中添加實(shí)例變量@observer_list。
module Observable
def observers
@observer_list ||= []
end
def add_observer(obj)
observers << obj
end
def notify_observers
observers.each {|o| o.update}
end
end
多數(shù)時(shí)候寄纵,mixin模塊并不帶有它們自己的實(shí)例數(shù)據(jù)——它們只是使用訪問(wèn)訪問(wèn)方法從客戶對(duì)象中取得數(shù)據(jù)鳖敷。但是,如果你要?jiǎng)?chuàng)建的mixin不得不持有它們自己的狀態(tài)程拭。確保這個(gè)實(shí)例變量具有唯一的名字定踱,可以對(duì)系統(tǒng)中其他的mixin區(qū)別開來(lái)(也許使用模塊名作為變量名的一部分)∈研或者崖媚,模塊可以使用模塊一級(jí)的散列表,以當(dāng)前對(duì)象的ID作為索引恤浪,來(lái)保存特定于實(shí)例的數(shù)據(jù)畅哑,而不必使用Ruby的實(shí)例變量。
module Test
State = {}
def state = (value)
State[object_id] = value
end
def state
State[object_id]
end
end
class Client
include Test
end
c1 = Client.new
9.4.2 解析有歧義的方法名(Resolving Ambiguous Method Names)
關(guān)于mixin水由,人們經(jīng)常問(wèn)到的另一個(gè)問(wèn)題是荠呐,方法查找是如何處理的?特別的是绷杜,如果類直秆、父類以及類所包含的mixin中,都定義有相同名字的方法時(shí)鞭盟,會(huì)發(fā)生什么?
答案是瑰剃,Ruby首先會(huì)從對(duì)象的直屬類(immediate class)中查找齿诉,然后是類所包含的mixin,之后是超類以及超類的mixin晌姚。如果一個(gè)類有多個(gè)混入的模塊粤剧,最后一個(gè)包含的模塊將會(huì)被第一個(gè)搜索。
9.5 包含其他文件(including other Files)
因?yàn)镽uby可以使我們輕松地編寫良好的挥唠、模塊化的代碼抵恋,你經(jīng)常會(huì)發(fā)現(xiàn)自己編寫了含有大量自包含功能的小文件——比如x的接口、完成y的算法宝磨,等等弧关。典型地盅安,你會(huì)將這些文件組織為類或庫(kù)。
產(chǎn)生這些文件之后世囊,你希望在新的程序中結(jié)合使用它們别瞭,Ruby有兩個(gè)語(yǔ)句來(lái)完成這一點(diǎn),每次當(dāng)load方法執(zhí)行時(shí)株憾,都會(huì)將制定的Ruby源文件包含進(jìn)來(lái)蝙寨。
load "filename.rb"
更常見的是使用require方法來(lái)加載指定的文件,且只加載一次嗤瞎。
require 'filename'
被加載文件中的局部變量不會(huì)蔓延到加載它們所在的范圍中墙歪。例如,這里有一個(gè)名為include.rb贝奇。
a = 1
def b
2
end
下面是當(dāng)我們把它包含到另一個(gè)文件中時(shí)箱亿,將會(huì)發(fā)生什么?
a = "cat"
b = "dog"
require "included"
require有額外的功能:它可以加載共享的二進(jìn)制庫(kù)弃秆,兩者都可以接受相對(duì)或者絕對(duì)路徑届惋,如果指定了一個(gè)相對(duì)路徑(或者只是一個(gè)簡(jiǎn)單的名字),它們將會(huì)在當(dāng)前加載路徑中(load path——$:)的每個(gè)目錄下搜索這個(gè)文件菠赚。
使用load或者require所加載的文件脑豹,當(dāng)然也可以包含其他文件,而這些文件又包含別的文件衡查,依次類推瘩欺,可能不那么明顯的是,require是一個(gè)可執(zhí)行的語(yǔ)句——它可能在一個(gè)if語(yǔ)句內(nèi)拌牲、或者可能包含一個(gè)剛剛拼合的字符串俱饿。搜索路徑也可以在運(yùn)行時(shí)更改,只需將你希望的目錄加入$:數(shù)組中塌忽。
因?yàn)閘oad會(huì)無(wú)條件地包含源文件拍埠,你可以使用它來(lái)重新加載一個(gè)在程序開始執(zhí)行厚可能更改的源文件,下面是一個(gè)認(rèn)為設(shè)計(jì)的示例土居。
這并不是嚴(yán)格為真的枣购,Ruby在數(shù)組$中保存了被require所加載的文件列表,不過(guò)擦耀,這個(gè)列表只包括了調(diào)用require時(shí)所指定的文件名棉圈,欺騙Ruby讓它多次加載同一個(gè)文件,是有可能眷蜓。
5.times do |i|
File.open("temp.rb", "w")
f.puts "module Temp"
f.puts " def Temp.var"
end
load "temp.rb"
puts Temp.var
end
對(duì)于這個(gè)功能分瘾,可以考慮一個(gè)不太實(shí)際的例子:Web應(yīng)用重新加載正在運(yùn)行的模塊,這讓它能動(dòng)態(tài)地更新自己吁系;它不需要重新啟動(dòng)來(lái)集成軟件的新版本德召,這是使用例如Ruby等動(dòng)態(tài)語(yǔ)言的眾多好處之一白魂。