這兩天突然閑得蛋疼,逛了一下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上單獨開了一貼,詢問返回接口是否能與針對接口編程掛鈎,也有人說我對“針對接口編程”有誤解,不過我覺得他也沒明白我的意思,我當然知道兩種“接口”的不同啦:)