- 論壇徽章:
- 2
|
![]()
鴨子類(lèi)型是我目前在Ruby語(yǔ)言里最喜歡的一個(gè)“功能特征”,主要是因?yàn)樗茏屛覀冚p松的寫(xiě)出漂亮的代碼——畢竟,你無(wú)需再擔(dān)心類(lèi)型:你可以把精力全部集中到你想發(fā)送的消息上,以及你需要打交道的對(duì)象能發(fā)揮的功能上。
![]()
我第一次接觸Ruby時(shí)就知道它是一種“鴨子類(lèi)型語(yǔ)言”,但我的靜態(tài)編譯型語(yǔ)言的背景知識(shí)妨礙了我真正理解鴨子類(lèi)型的真正含義。理論很簡(jiǎn)單:如果你設(shè)計(jì)一個(gè)方法,它需要一個(gè)‘鴨子’參數(shù),那么你呼叫一聲“嘎嘎”,任何以“嘎嘎”回應(yīng)你的對(duì)象都可以傳入這個(gè)方法——這個(gè)對(duì)象究竟是什么類(lèi)型并不重要。很顯然,你可以得出這樣的結(jié)論,如果你寫(xiě)出一個(gè)Dog類(lèi),它實(shí)現(xiàn)了一個(gè)叫“嘎嘎”的方法(很奇怪的狗),那么,你可以把這個(gè)狗傳入上面的那個(gè)方法,一點(diǎn)問(wèn)題都沒(méi)有。非常酷吧,鴨子類(lèi)型的強(qiáng)大功能震撼了我,我認(rèn)識(shí)到,它在各種對(duì)象間打通了一條重要的溝通途徑,強(qiáng)化了API的能力,減少了代碼中的干擾。為了說(shuō)明這些,讓我來(lái)展示一些Ruby標(biāo)準(zhǔn)庫(kù)中的幾個(gè)例子。
File.open
File.open(“path/to/file&rdquo 最常見(jiàn)的讀取文件的方法:你傳入path,這個(gè)方法會(huì)返回一個(gè)能讀取文件的對(duì)象。你是否注意到,我加粗強(qiáng)調(diào)了“path”這個(gè)詞。這是特意的——這個(gè)‘open’函數(shù)實(shí)際可以接受任何可以扮演路徑角色的東西,并不僅僅指路徑字符串。這區(qū)別有些微妙,但你會(huì)發(fā)現(xiàn)我們可以把代碼這樣寫(xiě):
class VimConfig
# ... behavior ... #
def to_path
"~/.vimrc"
end
end
config = VimConfig.new
config_file = File.open config
很帥,不是嗎?Ruby的File API在使用它的參數(shù)前會(huì)進(jìn)行轉(zhuǎn)化,轉(zhuǎn)化的一種途徑是通過(guò)‘to_path’方法。如果你感到奇怪,下面是實(shí)現(xiàn)它的C語(yǔ)言代碼(‘rb_f_open’ 調(diào)用 ‘FilePathValue’,后者最終調(diào)用 ‘rb_get_path_check_to_string&rsquo ):
static VALUE
rb_f_open(int argc, VALUE *argv)
{
ID to_open = 0;
int redirect = FALSE;
if (argc >= 1) {
CONST_ID(to_open, "to_open" ;
if (rb_respond_to(argv[0], to_open)) {
redirect = TRUE;
}
else {
VALUE tmp = argv[0];
FilePathValue(tmp);
if (NIL_P(tmp)) {
redirect = TRUE;
}
else {
VALUE cmd = check_pipe_command(tmp);
if (!NIL_P(cmd)) {
argv[0] = cmd;
return rb_io_s_popen(argc, argv, rb_cIO);
}
}
}
}
if (redirect) {
VALUE io = rb_funcall2(argv[0], to_open, argc-1, argv+1);
if (rb_block_given_p()) {
return rb_ensure(rb_yield, io, io_close, io);
}
return io;
}
return rb_io_s_open(argc, argv, rb_cFile);
}
VALUE
rb_get_path_check_to_string(VALUE obj, int level)
{
VALUE tmp;
ID to_path;
if (insecure_obj_p(obj, level)) {
rb_insecure_operation();
}
if (RB_TYPE_P(obj, T_STRING)) {
return obj;
}
CONST_ID(to_path, "to_path" ;
//to_path call!
tmp = rb_check_funcall(obj, to_path, 0, 0);
if (tmp == Qundef) {
tmp = obj;
}
StringValue(tmp);
return tmp;
}
數(shù)組索引
數(shù)組索引(a_array[index])是另外一個(gè)很好的例子:它會(huì)向索引調(diào)用‘to_int’方法,所以,任何能響應(yīng)to_int方法的對(duì)象都可以當(dāng)作索引。這讓我們可以這樣寫(xiě):
class PodiumPosition
# .. behavior .. #
def to_int
@race_position
end
end
position = PodiumPosition.new(1)
prizes = [ "orange", "apple", "corn" ]
puts "Congrats, you won #{prizes[position]}"
IO.select
我是通過(guò)IO.select API才第一次發(fā)現(xiàn)了Ruby的強(qiáng)大。這個(gè)API會(huì)調(diào)用系統(tǒng)select(2)函數(shù),接收文件描述符參數(shù),并掛起當(dāng)前的線(xiàn)程,直到有文件可以進(jìn)行讀寫(xiě)操作。這個(gè)Ruby函數(shù)定義如下:
select(read_array
[, write_array
[, error_array
[, timeout]]]) → array or nil
因此,你可以傳入一個(gè)數(shù)據(jù)流數(shù)組,而“select”函數(shù)會(huì)一直等到流文件準(zhǔn)備好可讀或可寫(xiě)。問(wèn)題是,很多數(shù)據(jù)流是存儲(chǔ)在具有各種行為特征的特定對(duì)象里的(例如一個(gè)執(zhí)行網(wǎng)絡(luò)操作的Connection類(lèi)),這些對(duì)象里的IO接口通常經(jīng)過(guò)了二次封裝,外界無(wú)法直接訪(fǎng)問(wèn)。根本不可能通過(guò)重構(gòu)內(nèi)核代碼來(lái)適應(yīng)‘select’ API。打破它的封裝嗎?很顯然不行!這時(shí)‘to_io’方法就成了救星!
class Connection
# .. rest of the class .. #
def accept_connection(io)
@io = io
# new connection code
end
def to_io
@io
end
end
class Reactor
# array_of_connections_to_read is an array of instances of the above Connection class
# array_of_connections_to_write is an array of instances of the above Connection class
def tick
to_read, to_write = IO.select(array_of_connections_to_read, array_of_connections_to_write)
end
end
你可以看到,Ruby的標(biāo)準(zhǔn)庫(kù)里到處都是鴨子類(lèi)型
![]()
.
重構(gòu)
最明顯,也是最值得一提的鴨子類(lèi)型的好處是,它讓重構(gòu)變得更容易:“用多型替換條件判斷”和 “Replace Type Code with Strategy/State”的重構(gòu)原則,當(dāng)你不需要考慮類(lèi)型、只關(guān)心行為時(shí),這些都變得極其簡(jiǎn)單和容易實(shí)現(xiàn)。
鴨子類(lèi)型的黑暗面
沒(méi)有編譯器為你探路是很危險(xiǎn)的。專(zhuān)業(yè)的Ruby程序員(1)永遠(yuǎn)不會(huì)忘記有責(zé)任測(cè)試它們的代碼的各種行為,并且(2)一定寫(xiě)出整潔的代碼,并及時(shí)重構(gòu)。Ruby代碼必須要認(rèn)真寫(xiě),否者調(diào)試起來(lái)就會(huì)是一場(chǎng)噩夢(mèng)。
同時(shí),動(dòng)態(tài)語(yǔ)言一般最合適的是開(kāi)發(fā)小型或中型軟件。我的經(jīng)驗(yàn)告訴我,當(dāng)系統(tǒng)變得復(fù)雜時(shí),最好把它拆分成小的應(yīng)用,如果是用動(dòng)態(tài)語(yǔ)言開(kāi)發(fā)的,那這種做法更加重要——一個(gè)reddit的網(wǎng)友說(shuō)需要在一個(gè)10萬(wàn)行的程序了修改一個(gè)函數(shù)的名稱(chēng),我只能說(shuō),這很難實(shí)現(xiàn)。修改公開(kāi)的接口,這很難很難。有時(shí)最好把它標(biāo)注為‘廢棄’就行了。
結(jié)論
動(dòng)態(tài)語(yǔ)言能漂亮的解決你的問(wèn)題,但需要有很好的設(shè)計(jì),Ruby的標(biāo)準(zhǔn)庫(kù)里鴨子類(lèi)型為我們提供了方便的途徑。它是一個(gè)很好的例子,向我們展示了一個(gè)Ruby程序員該如何的編程:按對(duì)象的行為——而不是按對(duì)象的類(lèi)型——來(lái)接收參數(shù)。
我希望這篇文章給那些仍然不明白像Ruby這樣的語(yǔ)言的強(qiáng)大之處的人帶來(lái)新的認(rèn)識(shí)。我推薦閱讀下面幾本書(shū)來(lái)進(jìn)一步的學(xué)習(xí):
[英文原文:Quacking The Dog - Duck typing for happiness]
轉(zhuǎn)自:http://www.aqee.net/quacking-the-dog-duck-typing-for-happiness/
本文來(lái)自ChinaUnix新聞?lì)l道,如果查看原文請(qǐng)點(diǎn):http://news.chinaunix.net/opensource/2013/0823/2907892.shtml
|
|