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

來源:互聯網
上載者:User
中庸

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.Cast<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.Cast<IPerson>(person)建立鴨子物件調用person的屬性和方法。這種把介面和對象粘合的方式與委託和方法的粘合方式非常接近,真正達到了我們所謂把委託思想應用於對象的想法。

C#中要實現Duck.Cast<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.Property<int>("Age");
Func<string> toJson = person.Method<Func<string>>("ToJson");

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

----------------------------------------------

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

 

後續

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

修改曆史

2009-03-26 初版

2009-03-26 澄清duck typing的概念,補充例子

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.