Ruby的中介編程與反射能力示範
在系列文章的前幾篇,我已經說明了 JavaScript 與 PHP 中介編程與反射能力的方式。 本文則就同樣的需求功能,示範 Ruby 的實作方式。
本文所示範的輸出結果,與 JavaScript的中介編程與反射能力示範 、 PHP的中介編程與反射能力示範 相同。 該做什麼,前文都提過了,此處不再重談。
我把 Ruby 的範例程式拆成兩段列出。一方面是為了方便說明。另一方面是為了突顯 Ruby 的 open class 能力。
程式第一段,先定義 MyData
的基本需求: 儲存資料欄位與內容。
class MyData
# Ruby 將所有資料成員視為私有的。
# 此外,當任何方法想賦值的資料成員不存在時,Ruby會自動建立。
# 所以不需要顯著地列出資料成員宣告。
def initialize(args)
args.each_pair {|name, value|
instance_variable_set("@#{name}", value)
}
end
end
# Ruby 1.9, 方法的最後一個參數為 hash 時,可以省略 { }
d = MyData.new id: 1,
title: "rock",
content: "hello world",
timestamp: Time.now.to_s
puts "Properties of d\n";
d.instance_variables.each do |name|
print " #{name}: ", d.instance_variable_get(name), "\n"
end
begin
d.title
rescue
puts "我現在還沒添加 title 的取值方法。"
end
puts "\n"
請看第21行的動作。 Ruby 的所有實例變數都是 private 的,基本上,在我們定義存取運算子之前應該是不能得知其值的。實際上,我們仍然可以透過反射函數去取得它的值。 PHP 則不允許這麼做。
任何具有強式反射能力的語言中, private 關鍵字和說明文件相差不遠:有需要時,我總是使用反射機制去取得。
《程式設計師提升生產力秘笈》(The Productive Programmer), Neal Ford, O'Reilly出版
接下來的第二段程式,則要擴充 MyData
的定義內容,補上資料欄位的 setter 與 getter 。同時我還要實作 each
方法,讓使用者(外部)不必透過反射函數就能完成走訪動作。
如果我們想為類別增加更多的行為能力,我們既不需要用到繼承,也不需要回到原始定義處修改。Ruby 允許再度添加定義內容到已存在類別。我們需要做的就只是打開它,直接加上我們想要的行為。Ruby 稱之為 open class 。 JavaScript 可以透過 prototype chains 隱蔽地實現 open class 的目的。 在 C++, Java, PHP 中,這種動作會擲出類別已定義的錯誤,它們不允許這麼做。
# Ruby 允許再度添加定義內容到已存在類別,這種動作稱為 open class 。
# JavaScript 可以透過 prototype chains 隱蔽地實現 open class 的目的。
# 在 C++, Java, PHP 中,這種動作會擲出類別已定義的錯誤 (不允許)
class MyData
alias initialize1 initialize
def initialize(args)
initialize1 args
args.each_pair {|name, value|
# 添加資料成員的 getter 與 setter
MyData.class_eval <<-DEFINE_GETTER_AND_SETTER
def #{name}
return @#{name}
end
def #{name}=(value)
@#{name} = value
end
DEFINE_GETTER_AND_SETTER
}
end
def each
self.instance_variables.each { |name|
yield name, self.instance_variable_get(name)
}
end
end
d2 = MyData.new id: 1,
title: "rock",
content: "hello world",
timestamp: Time.now.to_s,
table: "Data"
puts "Properties of d2\n";
d2.content = " hello ruby"
puts d2.content
d2.each {|name, value|
print " #{name}: #{value}\n";
}
實作過程中,我在 class_eval
的部份碰到了些麻煩。我原本是想用 define_method
來實現,但是不知為何,它似乎不能在 args.each_pair
區塊內調用。Ruby 會抱怨 define_method
未定義。但若不寫在區塊中則可用。試了幾種不同的手段後,class_eval
似乎是在這種情形下唯一可用的方式。
事實上,在本系列文章中, Ruby 的程式是我第一個寫好的。我寫好後,才回頭去完成 JavaScript 與 PHP 的部份。 就我的範例需求功能來看, Ruby 的實作方式最順暢,效果也最好。例如資訊隱藏的部份, JavaScript 基於本身的特性,無法作到資料保護。程序員仍然可以跳過 setter ,直接改變 props 中的資料欄位內容。JavaScript 的 Prototype 特性略嫌晦澀,就算是老手也偶爾會碰觸到 prototype chains 陷阱。
PHP 則不支援 open class 能力,因此我們要擴充功能時(例如增加 Iterator
的功能),便不能在外面實作,而要進入類別的原始定義處重構。說白話些,就是我們不能分成好幾段源碼文件來寫,而要打開類別第一次定義的源碼檔,修改即有的類別定義源碼。
而且在實作迭代功能時的限制也比較多。
Ruby 提供豐富而一致的表達能力,易於閱讀與理解。它總是能支持我的想法,而不是限制我的想法;Java 常用詭異的方式扭曲我們的思想。同時程式結構還能保有高度的抽象性,讓我不必花太多的功夫在細枝末節上;C/C++ 要花不少精力關注資源釋放的情形。不論是程序式編程、class-based編程還是函數式編程風格, Ruby 幾乎都能滿足。Ruby 確實是很優秀的程式語言
樂多舊回應