方法該返回接口還是具體類,以及面向接口編程


這兩天突然閑得蛋疼,逛了一下CSDN,發現了這篇帖子,於是引發了一場不大不小的關於方法應該返回接口or具體類,以及面向接口編程的討論。

方法的返回類型應該更抽象還是更具體,沒有確切的答案,唯一正確的答案是:It depends。要時情況而定。

帖子里很多牛人說,像String、List<T>這樣的類型,返回具體類沒有什么,特別是例子里的方法,返回IList<T>就顯得很白痴。我同意String這種跟基元類型差不多的類型完全可以返回具體類,但對於List<T>和IList<T>,就完全不敢苟同了。

因為,在設計API時(這里主要討論方法),你需要控制留給用戶“權限”。假如你的方法只希望返回一個只讀的可枚舉的集合類型,那么就應該返回IEnumerable<T>,這樣用戶就不能進行修改、添加、排序和轉換。而如果你希望給用戶一個不可轉換的列表,就應該返回IList<T>,這樣用戶就不能調用ConvertAll方法。如果你希望留給用戶的操作完全和List<T>一致,這時返回具體的List<T>就是最適當的。List<T>雖然十分常用,但它與IList<T>還是有區別的,不能簡單地將它跟String這種類型划等號。為了驗證我的想法,我在Stack Overflow上搜到了這個帖子,偶像Jon Skeet的回答跟我的想法不謀而合。當然,這也應該是絕大多數同學的想法。

我的另一個觀點是:在設計API時,讓方法返回接口類型是面向接口編程的一部分。但很多人指出我對面向接口編程的理解有誤。

這里首先要說明一下什么是面向接口編程。據我所知,這個原則最早是在《設計模式》里提出來的,即Program to an interface, not an implementation(針對接口編程,而不是針對實現)。意思是指:

  • 我們應該根據抽象類中定義的接口來操縱對象,用戶只需要知道定義這些接口的抽象類,而不必關心使用的是什么具體類型。
  • 在聲明變量時,不將變量聲明為某個特定的具體類,而是讓它遵從抽象類所定義的接口,即將變量聲明為抽象類。
  • 在實例化的時候,使用創建型模式。創建型模式可以確保系統是針對接口的方式書寫,而不是針對實現的方式書寫。

可以看到,這里的接口,與IList<T>這種接口不是一個概念,所以我用斜體字來表示Program to an interface里的接口

這里的接口是指,抽象類對外聲明的契約,比如各種公共方法等。用戶代碼應該只跟這些契約有關,而不應該跟這些契約的實現有關。比如,抽象類A中定義了一個方法M,用戶在聲明變量時應該這樣:A a = …,在調用時是這樣:a.M()。用戶代碼所關心的問題就是調用A定義的M,而不用理會究竟是子類C1的M,還是C2的M,這樣,用戶代碼就跟具體的實現解耦了。

然而我們必須要知道,《設計模式》這本書是針對C++這門語言的,而C++沒有C#中的接口這個概念。在C#和Java里,接口是和抽象類差不多同一層次的抽象,它僅僅提供契約,而不提供任何默認實現。所以對於接口IA,聲明時和調用時采用這樣的代碼:IA a = …; a.M(); 也同樣是針對接口編程。這不應該有任何異議吧?

現在回到我之前的觀點:將方法的返回類型設計為接口,是否是針對接口編程的一部分?我們來看代碼

public interface IBar
{
    void N();
}

public class Bar : IBar
{
    public void N(){}
}

public class Foo
{
    public static IBar M()
    {
        return new Bar();
    }
}

在用戶代碼中,我們可以這樣聲明一個IBar:

IBar bar = Foo.M();

我在CSDN的帖子里指出,如果Foo的M方法返回一個Bar,那么用戶在聲明變量的時候仍然使用IBar bar = Foo.M()是沒有任何意義的,因為已經明確知道M返回的是具體類型了。現在想想其實也是有意義的。至少在API改變的時候,如以后將M的返回值改成IBar或其他實現了IBar的類時,客戶代碼不需要修改。這也是針對接口編程的一個意義所在。

那么我們在設計API的時候讓M返回IBar而不是Bar,這樣做的意義何在呢?我們可以強制用戶在聲明變量的時候使用IBar bar而不是Bar bar,從而避免由於M的實現修改后(比如返回了另一個實現了IBar的類)造成客戶端代碼無法編譯的情況。因此我說,返回接口,也是針對接口編程的一部分。事實上上面所描述的《設計模式》這本書中關於針對接口編程的第三點——實例化變量的時候使用創建型模式,就是這個意思。
不知道我說明白了沒有,列位看明白了沒有。對於面向接口編程,我也搜到了很多帖子,希望對大家有幫助:

http://stackoverflow.com/questions/1992384/program-to-an-interface-what-does-it-mean

http://stackoverflow.com/questions/1413543/what-does-it-mean-to-program-to-a-interface

http://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to-an-interface

http://stackoverflow.com/questions/9249832/interface-segregation-principle-program-to-an-interface

http://www.artima.com/lejava/articles/designprinciples.html

另外我還在Stack Overflow上單獨開了一貼,詢問返回接口是否能與針對接編程掛鈎,也有人說我對“針對接口編程”有誤解,不過我覺得他也沒明白我的意思,我當然知道兩種“接口”的不同啦:)

http://stackoverflow.com/questions/9598441/should-the-return-type-of-a-method-declaration-be-interface-or-concrete-class


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM