最近更新: 2007-03-18

Delegate in C# and Module in Ruby

我在《類別繼承、介面宣告與模組混成(mix-in)》中提到 Java 的介面(interface) 無助於提高程式碼再用性。而 jaceju 在回應中提醒我還有 delegate 這種方式。

我當年學的是 Java 1.0 ,那時的 Java 還沒有 delegate 這種概念。對我這個在 C/C++ 中用慣函數指標的人而言,沒有函數指標或類似方式是一件非常彆手彆腳的事。後來我在 C# 中碰過 delegate ,直覺反應這是 C# 版的函數指標。而 Java 則以類別型式的 Delegate 實踐此概念。但是 delegate 與 Ruby 的混成(mix-in)概念相比,還是很麻煩... 非常麻煩,不會比 PHP 好到哪去。

Ruby 之模組的作法

首先,我們先看看 Ruby 的混成(mix-in)的作法。我定義了一個模組 MySampleModule 及一個類別 MyClassMyClass 類之建構子定義了一個實例變數 @title。 Ruby 的實例變數對應於 C++/C#/Java 中的私有資料成員 (See also: Ruby Reference: Instance Variable)。而在 MySampleModule 模組中定義了一個方法 show,請注意到它將顯示 @title 的內容,而 @title 在模組中並未出現。Ruby 對此採動態繫結策略:根據實際調用此方法之個體決定 @title 指涉何者。

module MySampleModule
    def show
        puts @title
    end
end

class MyClass
    include MySampleModule
    def initialize
        @title = 'xyz';
    end
end

x = MyClass.new
x.show

C# 之 delegate 的作法

接著我們來看看 C# 的 delegate 作法。實際上,它和 PHP 的作法非常相似,參閱《PHP 實踐 mix-in 概念之可行性》。差別僅在於 PHP 以「名」參照,將之稱為 Variable function 。所以 jaceju 說有點 delegate 味道

為了方便閱讀, C#的例子中所用名稱和 Ruby的例子中一樣。與 Ruby 的模組語法相比,不難發現 delegate 的語法冗長多了。它必須先宣告 delegate 函數簽名 methodDeleagte,接著再宣告方法 show 並委派 MySampleModule.show 處理。語法上有兩個缺點。其一、若 MySampleModule 中有多個方法且函數簽名皆不同時,MyClass 就必須宣告多個 delegate 函數簽名。其二、當我們在 MySampleModule 中增加新的方法時,我們也必須在 MyClass 中增添委派的程式碼;Ruby 完全隱藏於 include MySampleModule 之中,我們不須處理。

using System;
public class SamplesDelegate  {
    public class MySampleModule  {
        public static void show()  {
            Console.WriteLine( "Title: ");
            //Console.WriteLine( "Title: {0}", this.title);
            //ERROR! The solution is the same as PHP.
        }
    }

    public class MyClass {
        //include Module in Ruby
        public delegate void methodDelegate();
        public methodDelegate show = new methodDelegate(MySampleModule.show);

        private String title;   // @title in Ruby
        public MyClass() {
            this.title = "xyz"; // @title in ruby
        }
    }

    public static void Main()  {
        MyClass x = new MyClass();
        x.show();
    }
}

實際使用時,它所面臨的狀況和 PHP 一樣,不能使用 this 存取活動個體之屬性。MySampleModule 本身是一個類,其中的 this 被認定指涉 MySampleModule 類之實例。即便我們委派 MySampleModule.showMyClass 類之 show 方法,並藉由 MyClass 之實例 x 調用,MySampleModule.show 中之 this 仍不會動態繫結至 x。Ruby 會將實例變數動態繫結至活動個體;JavaScript 也會將 this 動態繫結至活動個體。在 Ruby 與 JavaScript 之方法內出現的屬性存取對象,係依此方法為哪個個體所調用而定 - 若為 x 所調用,則屬性存取對象就是 x 之屬性;若為 y 所調用,則為 y 之屬性。在 C# 中,屬性存取對象則依此方法定義於哪個類別中而定 - 若為 MySampleModule 所定義,則屬性存取對象必須是 MySampleModule 之實例。

C#的解決方式也和 PHP 一樣,屬性必須宣告為 public ,並將 this 作為引數傳遞給委派方法。當限制與解決方式相同時,程式碼的行數就是關鍵。C#所需的行數比 PHP 還多出許多, C# 之 delegate 比 PHP 的作法麻煩。與 Ruby 相比就更不用談了。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/2872705.html

樂多舊回應
未留名 (#comment-22870556)
Fri, 10 May 2013 23:24:22 +0800
其實c#做混成都是用extension method. 對某interface擴充, 而所有繼承該interface的類別也全被擴充了.
未留名 (#comment-22871040)
Sat, 11 May 2013 07:28:23 +0800
本文寫作時(2007),C# 還不支援 extension method。 extension method 應該是在 C# 3.0 時加入的語言特性。

話說 extension method 的用法還真是復古。早期用 C 語言實作 OO 時,我們都是這樣寫的。定義一個函數,第一個參數放 instance 。只是 C 語言不支持成員連接符號,所以調用時只能寫 ext_method(self) 。

不過 Python 倒是一直保留著這個形式。Python 可以寫 o.ext_method() 也可以寫成 ext_method(o) 。