面向接口的编程
关于接口、接口编程、抽象类的概念,在面向对象领域,想必大家都早有耳闻了,我也一直被这些概念所困扰,最近有点闲暇时间,也参考了圈子一些人理解,现在梳理如下,欢迎大家拍砖:
refer to: http://dev.yesky.com/436/7581936.shtml
http://www.cnblogs.com/Gavinzhao/archive/2009/11/10/1599700.html
1.面向接口编程和面向对象编程是什么关系?
首先,面向接口编程和面向对象编程两者并不平级,面向接口编程是面向对象编程体系中的思想精髓之一,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。
2. 接口的本质
接口,在表面上是由几个没有主体代码的方法定义组成的集合体,有唯一的名称,可以被类或其他接口所实现(或者继承)。它在形式上可能是如下的样子:
{
void Method1();
void Method2(int para1);
void Method3(string para2,string para3);
}
所以,接口可以这样定义:
2.1 )接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则(一组操作规范)。
2.2 )接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同
所谓不同粒度即是:
A、 在我看来: 人和猪是有本质区别的,如果用接口来实现的话应该是:IPerson , IPig
B、 在动物回家看来:人和猪都一样是动物,如果接口来实现的话,应该是:IAnimal
C、 在遗传学家看来:人和猪,树木,绿草都一样是遗传性所致,如果接口来实现的话,应该是:IDescendable这个接口(注:descend vi. 遗传)
等等..., 以上不同的视野可以理解为 “粒度视图”, 而“同类事物”是建立在特点粒度视图的基础上的。
面向对象思想和核心之一叫做多态性,什么叫多态性?说白了就是在某个粒度视图层面上对同类事物不加区别的对待而统一处理。而之所以敢这样做,就是因为有接口的存在
3.面向接口编程综述
大家对接口和接口的思想内涵有了一个了解,那么什么是面向接口编程呢?我个人的定义是:在系统分析和架构中,分清层次和依赖关系,每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。
这样做的好处是显而易见的:
首先对系统灵活性大有好处。当下层需要改变时,只要接口及接口功能不变,则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉,
其次使用接口的另一个好处就是不同部件或层次的开发人员可以并行开工,就像造硬盘的不用等造CPU的,也不用等造显示器的,只要接口一致,设计合理,完全可以并行进行 开发,从而提高效率。
“面向接口编程”中的接口是一种思想层面的用于实现多态性、提高软件灵活性和可维护性的架构部件,而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段。
4. 在实际编程语言中:接口和抽象类的区别
abstract class 和interface 是支持抽象类定义的两种机制(这里说的抽象类,是设计层面的抽象类,而非java,C#,Php等编程语言里的抽象类,请注意区分)。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进 行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对 于问题领域本质的理解、对于设计意图的理解是否正确、合理。 抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。
4.1 定义方式不同:
接口: interface Demo{ void method1(); void method2(); … } 抽象类: abstract class Demo{ abstract void method1(); abstract void method2(); … }
4.2 成员变量不同
接口: 只能包含static final 的成员变量,并且赋初始值, 在实现类中不可修改。(通常不在接口中定义变量)
抽象类: 变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值 。
4.3 成员方法不同
接口: 所有方法只能是一个声明、定义,而非 实现(即,不需要实现该方法)
抽象类:可以有默认方法,和方法的实现
4.3 设计理念不同
接口:一个类却可以实现多个interface,这里是 like-a的关系。
抽象类:abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系(因为Java不支持多继承 -- 转注) , 是is-a的关系。
需要补充了解的设计原则: ISP, OCP 等等。。
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door{ abstract void open(); abstract void close(); } |
使用interface方式定义Door:
interface Door{ void open(); void close(); } |
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中, 主要是为了展示 abstract class
和interface 反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解
决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door{ abstract void open(); abstract void close(); abstract void alarm(); } |
或者
interface Door{ void open(); void close(); void alarm(); } |
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door{ void open(){…} void close(){…} void alarm(){…} } |
或者
class AlarmDoor implements Door{ void open(){…} void close(){…} void alarm(){…} } |
这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation
Principle),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方
法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反
之依然。
解决方案二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分
别定 义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用 abstract class
方式定义;两个概念都使用interface方式定义;一个概念 使用 abstract class
方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有
理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分
析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用
interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它
有具有报 警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract
class在Java语言中表示一种继承关系,而继承关系 在本质上是"is-a"关系。所以对于Door这个概念,我们应该使用abstarct
class方式来定义。另外,AlarmDoor又具有报警功能,说
明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door{ abstract void open(); abstract void close(); } interface Alarm{ void alarm(); } class Alarm Door extends Door implements Alarm{ void open(){…} void close(){…} void alarm(){…} } |
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其 实abstract
class表示的是"is-a"关系,interface表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解
上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。