程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#動靜結合編程之三:Duck Typing

C#動靜結合編程之三:Duck Typing

編輯:關於C#

中庸

C#是靜態類型語言,要使用類型必須引用該類型的定義。因此,從軟件組織角度會發生組件間的引用依賴關系。常見的引用依賴關系有兩種模式:

a. 正向依賴:組件A用到了組件B中定義的類T,組件A直接引用組件B,依賴關系是“組件A -> 組件B”。

b. 反向依賴:組件A通過接口I定義功能規范,針對抽象編程;組件B反過來引用組件A,並定義類T實現接口I;由另一組件C將I與T粘合起來,依賴關系是“組件A <- 組件B”。這就是著名的IoC方式。

簡單說來,IoC是“誰制定規范,誰就擁有控制權;誰執行規范,誰就被控制”。如果規范借助於C#的靜態類型檢查,比如接口或抽象類,那麼規范就表現出較強的語法約束性,使得組件A的編寫比較獨立,而組件B則受制與組件A。

本系列的第一篇舉了一個基於接口的IoC例子,我們看到當需要采用第三方組件時,為了適用接口的靜態類型約束,不得不增加一個adapter去實現接口並包裝對第三方組件的調用。這表現出基於接口的IoC在粘合規范與實現時不太靈活。

但是,規范和類型約束沒有必然的聯系。在基於委托的IoC例子中,我們不需要任何的adapter,就能輕松的粘合規范與實現,表現出較強的靈活性。這就是通過委托定義規范,不會造成組件B對組件A的依賴,組件A和組件B的實現都顯得比較獨立。

實際上,我們還可以有比委托更靈活的規范表達方式,比如:通過HTTP + XML來表達規范,這樣甚至是語言無關的,完全可能組件A由C#編寫,組件B由Java編寫。

上面列舉的3種規范定義方式:基於接口、基於委托、基於HTTP + XML分別代表了由約束到協議,由嚴格到靈活的3種風格。當然,還有更多的方式,但這裡只列舉這三種作為代表。動與靜之間需要把握一個分寸,接口過於死板;而HTTP + XML的方式則完全是基於運行時協議的,需要自己做很多檢查工作;委托的好處在於既消除了組件A、B的依賴關系,又能享受IDE智能提示和編譯器檢查(簽名檢查)等好處。因此,委托是把動與靜結合得恰到好處的中庸之道。

Duck Typing

但可惜委托還無法覆蓋接口或類的所有功能,有朋友提到“接口是對象功能的抽象,而委托是方法功能的抽象”就是這個意思。那麼我們自然會想,有沒有一種方式,能將委托的思想應用於對象呢?有!它就是:duck typing。前文已經談到,duck typing關注“對象能做什麼”或者說“如何使用對象”,對象繼承自什麼類,或者實現什麼接口並不重要。duck typing的本意為“如果一只動物,走起來像鴨子,叫起來像鴨子,我就可以把它當作鴨子”。與繼承性多態相對應,duck typing可以實現非繼承多態。按duck typing的本意,那麼更純正的duck typing看起來應該是這個樣子:

static void Main(string[] args)

{

    object person= new Person();

    IPerson duck= Duck.Create<IPerson>(person);//創建鴨子對象


    Console.WriteLine(duck.Name + " will be " + (duck.Age + 1) + "next year");

    duck.Play("basketball");

    Console.WriteLine(duck.Mother);//為null

    //duck無法調用duck.Sing()


}

interface IPerson
{
    string Name { get; }
    int Age { get; }
    string Mother { get; }
    void Play(string ball);
}

class Person
{
    public string Name { get { return "Todd"; } }
    public int Age { get { return 26; } }

    public void Play(string ball) { Console.WriteLine("Play " + ball); }

    public void Sing(string song) { Console.WriteLine("Sing " + song");}

}

上面的例子中,雖然person對象沒有實現IPerson接口,我們一樣可以通過Duck.Create<IPerson>(person)創建鴨子對象調用person的屬性和方法。這種把接口和對象粘合的方式與委托和方法的粘合方式非常接近,真正達到了我們所謂把委托思想應用於對象的想法。

C#中要實現Duck.Create<T>的功能,可以通過Emit動態創建實現T接口的代理類,在代理類中攔截方法調用,並將方法調用轉換成target對象上的反射調用。Castle開源項目的DynamicProxy是一個很好用的工具,在它的幫助下很容易實現代理類的創建和方法調用的攔截。

動態類型

事實上,duck typing是動態類型概念的一種。C#4.0已經通過dynamic關鍵字來實現動態類型,讓我們先來看看下面的示例:

string json = @"{ ""FirstName"": ""John"", ""LastName"": ""Smith"", ""Age"": 21 }";

dynamic person = CreateFromJson(json);

Console.WriteLine("{0} will be {1} next year", person.FirstName, person.Age + 1);
Console.WriteLine(person.ToJson());

person.Play("basketball");//不存在的方法,可以通過編譯,但會拋出運行時異常

通過dynamic關鍵字,我們不需要在編譯時為person對象指定類型,編譯器不會進行類型檢查,而是將對象的屬性訪問和方法調用轉換為反射調用,所以,只要對象的運行時類型能通過反射找到匹配的屬性或方法即可。

上面的例子通過json創建了一個dynamic對象,就像javascript中操作json一樣方便。在運行 時,person.FirstName和person.Age能通過反射正確地進行屬性訪問,person.ToJson()也可以正確地執行,但 person.Play( "basketball")由於運行時類型不存在該方法而拋出異常。

C#4.0的味道如何?很爽嗎?不過,說實在的,我覺得有點兒不太舒服了!仔細想想,它像接口,像委托,還是更像HTTP + XML? 對於dynamic對象,編譯器不進行對象類型檢查,不進行屬性類型檢查,也不進行方法簽名檢查。很明顯,它像HTTP+XML,完全基於運行時協議,沒有一點兒靜態的東西。如果類比委托的話,更理想的方式應該是,不進行對象類型檢查,但進行屬性類型和方法簽名檢查,就像下面這樣:

string json = @"{ ""FirstName"": ""John"", ""LastName"": ""Smith"", ""Age"": 21 }";


dynamic person = CreateFromJson(json);

Console.WriteLine("{0} will be {1} next year", person.FirstName<string>, person.Age<int> + 1);
Console.WriteLine(person.ToJson<string>());

person.Play<string>("basketball");

string firstName = person.FirstName<string>;
int age = person.Age<int>;
Func<string> toJson = person.ToJson<Func<string>>;


Action<string> play = person.Play<Action<string>>;

這樣,除了屬性和方法的名稱是動態的外,屬性的類型和方法的簽名都是靜態的,把運行時錯誤的可能降到最低,同時享受靜態檢查的好處。其實,沿著這個思路,我們大可不必等著C#4.0的dynamic才開始動態類型,在C#2.0時代也可以這樣:

object jsonObj = CreateFromJson(@"{ ""FirstName"": ""John"", ""LastName"": ""Smith"", ""Age"": 21 }");

Dynamic person = new Dynamic(jsonObject);

string firstName = person.Property<string>("FirstName");
int age = person.Age<int>("Age");
Func<string> toJson = person.Method<Func<string>>("ToJson");

Action<string> play = person.Method<Action<string>>("Play");

看到這裡,相信您一定明白該如何實現Dynamic類了吧?如果覺得有用,就自己嘗試實現一下吧!

後續

下一篇打算繼續探討在C#模擬實現動態類型,敬請關注!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved