方法该返回接口还是具体类,以及面向接口编程

这两天突然闲得蛋疼,逛了一下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

posted @ 2012-03-08 14:16 麒麟.NET 阅读(...) 评论(...) 编辑 收藏