抽象类和接口的讨论


一旦公开接口发布之后,它就不能被修改了。进而,这一点又可以引出了接口的一个设计准则:接口的职责应该尤其单一。例如.NET框架中的IComparableIEnumerable等接口。因为如果您设计了一个接口,其中包含了许多成员,那么在新版本中这个接口则更有可能需要补充新的功能……但是接口又不可以修改,这该怎么办呢?

如果是的话,问题就相对好办多了,因为我们可以向类中添加新的成员——只要这个新成员不是abstract的,就不会破坏外部已经出现的 依赖。不过加上之后,API设计是否合理,语义是否清晰,就是另一回事情了。

因此,某些抽象类可能是为了预留而实现成抽象类的。

接口的确有优势(可以让类来实现多个接口,且struct实现接口不能继承一个类),但是接口也是有一定缺陷的。

不过比较奇怪的是,有(不止一个)朋友回复说,使用抽象类是为了使用扩展方法。我从来没有看到过某个资料说接口和扩展方法有任何冲突,事实上我们也可以为接口定义扩展方法。由于一个类可以实现多个接口,而接口又可以实现扩展方法,这似乎也又有了多继承的意味。

#3 pzhxd[未注册用户]2009-08-19 12:27

回复中所谓的扩展方法含义可能是指为抽象类新加方法来实现扩展,而不是指c#3语言特性中的扩展方法吧.

·  #7[楼主] Jeffrey Zhao      2009-08-19 12:40

引用pzhxd:回复中所谓的扩展方法含义可能是指为抽象类新加方法来实现扩展,而不是指c#3语言特性中的扩展方法吧.
我不知道啊,有个朋友的原话是使用抽象类的原因,是不是可以用C#3.0的扩展方法

  回复  引用  查看    

·  #8 RednaxelaFX      2009-08-19 12:45

啊,没看上一帖……

话说我想起JavaAWTAPI设计,其中有很多类似这样的接口:

view sourceprint?

1.public interface MouseListener extends EventListener {

2.    void mouseClicked(MouseEvent e);

3.    void mouseEntered(MouseEvent e);

4.    void mouseExited(MouseEvent e);

5.    void mousePressed(MouseEvent e);

6.    void mouseReleased(MouseEvent e);

7.}


如果要监听某个鼠标事件,例如说我只要监听clicked,那么实现这个接口就需要实现所有5个方法,包括那4个我不关注的。

然后AWT里还有另外一些抽象类:

view sourceprint?

01.public abstract class MouseAdapter

02.    implements MouseListener, MouseWheelListener, MouseMotionListener {

03.    public void mouseClicked(MouseEvent e) { }

04.    public void mouseEntered(MouseEvent e) { }

05.    public void mouseExited(MouseEvent e) { }

06.    public void mousePressed(MouseEvent e) { }

07.    public void mouseReleased(MouseEvent e) { }

08.    // ...

09.}


它只是实现了些接口,内容全部是空的实现。继承这个抽象类就可以少写很多空方法,省点事。
老赵是怎么看这种设计的?这个例子中的MouseListener的职责是否不够单一?

#10[楼主] Jeffrey Zhao      2009-08-19 12:52

@RednaxelaFX
呵呵,又被你抢先了,其实我今天还打算写一篇文章谈一下类似的问题呢。就先在这里讨论一下吧。说实话,我不喜欢Java的这个设计,它的确职责不单一。
我觉得Java也是意识到这个问题的,所以把Mouse相关事件和MouseMove相关事件也分开了,也算是一定程度上职责的差分。
我还是喜欢.NET的事件机制,粒度更细,使用更方便。

#13 熊呜呜      2009-08-19 13:14

个人浅见如下:
定义接口应当是对一个建模的概念的完整规格说明。例如:IEnumerable

如果无法对建模的概念进行明确的完整说明,则不应使用接口。

抽象类的对象抽象概念与接口的差别,就是允许包换公共状态和行为的。

采用RouteBase抽象类是为了将来把公共状态和行为提取出来,至于大家说的以便于以后的增加方法扩展我觉得倒是其次。

 

#15 lakie[未注册用户]2009-08-19 13:44

如果是为了能更灵活的扩展api得方法,我觉得倒不是接口的缺陷,因为你仅仅为了扩展方法,你可以再另外定义一个接口来包容新加的方法啊,如果你使用抽象类来规避这个问题,那就不能标注为abstract,那还不如直接用类表示好了,这样才真正扩展一个方法没多少影响。
且恰恰使用抽象类会有这个问题,因为一个类只能继承一个父类或者抽象类,如果你要扩展方法,这个时候你必须要去改抽象类,这似乎违背某些优良的编程原则 (不记得叫什么了,好像是写好的东西不要轻易去动它,避免程序重新编译或者测试什么的)。而如果用接口的话,你可以再定义一个接口,让一个类来继承。这样 就不必动原来的接口,也不会影响另外继承了最初那个接口的类。

记得以前看过敏捷开发里提到的两个原则,一个是接口分离,一个是依赖倒置,类似是为了在使用接口的时候,规避你说的这个问题。

 

#19[楼主] Jeffrey Zhao      2009-08-19 14:09

@lakie
我说的是接口的职责的扩充,如果是可以定义成其他接口,那么说明本不是一种职责啊,本来就不应该修改原有接口。
修改抽象类不会引起重新编译,只要不添加抽象成员就可以了。Framework Design Guidelines里举了个Stream的例子,我觉得很合适。
至于依赖倒置等等……我觉得和我现在说的问题关系不大,谈不上规避不规避的吧。
还有我越来越认为,开发一个公开的API,和一套项目中自己的设计,方式是不一样的,因为公开API一旦发布再也不可以修改,所以会走更保险的路。

 

#23 something[未注册用户]2009-08-19 14:27

@lakie
还是感觉接口和抽象类在于契约语义上的区别比较关键。您说的应该是开-闭原则吧,但我觉得这个原则并不是说你要是改了原先的代码就是不好,而是说你的 设计好得足以让修改可以不用改原先的代码,但是人都不是神,程序都是可能会有点bug的,难道改个bug也要非得不直接改原代码,不重新编译原代码么?如 果这样我觉得就是对OCP的过度理解。而在此同时,如果接口并不是非常的稳定,却将它定义为接口,那将来它一改,所有使用了你的类库的程序都不能运行了, 而抽象类则可以,那为什么要用抽象类而不直接用类呢?因为抽象类不可以被实例化,在许多场景中,一个类是不应该被实例化的,可以在.net fx中找到许多这样的类,通常只能实例化它的子类,或调用它本身的某个Create什么的静态方法来实例化。


#66 Ivony...   

接口可以理解为can do

类(无所谓抽象与否)表达的是be的逻辑,接口表达的是like的逻辑。

因为相对于接口而言,类可以有更多的制约,类的继承是一个抽象到具象的过程,从某种意义上来说是从开放到封闭的过程。接口则不然。

这也就是我说的如果有功能上的抽象需要,保留接口。保留接口的最大意义在于你可以让一个东西像什么。

 


来源:http://www.cnblogs.com/JeffreyZhao/archive/2009/08/19/more-on-class-and-interface.html
posted @ 2009-10-12 17:24  guangrou  阅读(171)  评论(0编辑  收藏  举报