interface还是abstract class?

使用interface还是abstract class在许多地方都有讨论。但许多讨论更专注于术的层面,即二者在语法上的区别和细节差异,如Interface or abstract class? 中排名第二的回答。实际上,从逻辑抽象层次出发,选用interface或abstract class才是正道。

interface与abstract class区别

在语法上的几点区别:

  1. abstract class可以包含成员变量,保存类的状态。
  2. abstract class可以有方法实现,interface不能有实现。 abstract class中的实现可以用于派生类的代码复用,起到util/helper类(anti-pattern)的作用。
  3. C#用interface完成“多继承”的功能,一个类可以实现多个interface,但只能继承一个abstract class。

而选用interface或abstract class不应从语法实现的层面思考,更应关注设计中的抽象。因此二者最重要的区别在于:

abstract class代表“是一种”(a kind of)关系,而interface只定义它能做什么的外围功能。二者的区别有点类似继承和聚合的关系。

举个例子。Cat和Bird都是一种Animal,都有weight(体重)属性,此时,使用abstract class更合适些,同时将weight也置于abstract class中以复用。但会不会飞,Fly()实现成IFly interface更合理,因为Cat不会飞,只有Bird实现IFly即可。

滥用interface

顺嘴提下滥用interface的现象。曾见过一个项目中的所有类都配上了一个特有的interface,"I+ClassName"式的interface遍布整个项目。乍一看似乎挺规整,仔细一看全是无效操作。所谓“一顿操作猛如虎,仔细一看原地杵”,甚至还不如原地杵:如此之多本不该存在的interface,增加了额外的抽象和开销,极影响可读性。举例说明,如何看待这样的一个"interface"?

public IGetSpecificServiceRankScore
{
    double GetSpecificServiceRankScore(enum serviceType, int maxReturnNum, double threshold1, double threshold2, List<string> blackList);
}

上述interface的作者犯了教条主义错误,没有理解“面向interface编程”和依赖倒置的意义。有如下问题:

  1. interface的定义需要通用,具有普适性,从而可以有多个不同的实现。如此“一个萝卜一个坑”式的定义,显然不太可能会有多个不同实现,那么也就失去了interface的意义。
  2. 即使有多个不同的实现,实际开发中笔者不只一次看到interface随着实现一并修改的情况。实现可以增加或修改,interface不能天天改。假设interface已有n个实现,此时为了增加第n+1个实现修改了接口定义,那么就要改动所有n个实现和增加第n+1个实现,显然带来了额外的开发成本。

就此例而言,可做如下改进:

public IGetServiceRankScore
{
    double GetServiceRankScore(enum serviceType);
}

可以看下C# System命名空间中IDisposable和IComparable的定义,简明、通用。虽然实际业务逻辑相对复杂,但从逻辑抽象层次方面抽象interface,还是应该遵从通用、普适的原则,取到可用和通用的折衷。

public interface IDisposable
{
    void Dispose();
}

public interface IComparable
{
    int CompareTo(object obj);
}

依赖倒置原则无可争议,但在复杂业务逻辑实践中我更推崇“先实现后抽象”的做法。即程序中先有多个不同的实现(至少两个),然后根据这些实现通用的内容抽象出一个通用的interface。原因在于在开始设计和实现之时,大部分人其实想不明白这个接口的输入输出是什么,与其如此,不如先摸着石头过河,让现实告诉你正确的路径。