抽象类和接口的讨论
一旦公开接口发布之后,它就不能被修改了。进而,这一点又可以引出了接口的一个设计准则:接口的职责应该尤其单一。例如.NET框架中的IComparable,IEnumerable等接口。因为如果您设计了一个接口,其中包含了许多成员,那么在新版本中这个接口则更有可能需要补充新的功能……但是接口又不可以修改,这该怎么办呢?
如果是“类”的话,问题就相对好办多了,因为我们可以向类中添加新的成员——只要这个新成员不是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
啊,没看上一帖……
话说我想起Java的AWT的API设计,其中有很多类似这样的接口:
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里还有另外一些抽象类:
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