最近更新: 2009-11-15

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 確實是很優秀的程式語言

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/10711741.html

樂多舊回應
PALAY@java.net(PALAY) (#comment-21421077)
Fri, 26 Nov 2010 07:53:33 +0800
不算聰明