|
锁定老贴子 主题:接口还是继承
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
时间:2004-04-07
多态和继承的问题我是这样看得:首先要分清楚,实现多态和接口多态。
实现多态就是代码重用(abastract class),接口多态体现的是is-a关系(interface)。把两者混为一谈只会捣乱。 实现多态其实不是一个必要的特性。能不用就尽量避免。一个好的oo设计,应该有合理的接口继承体系和一个尽量扁平的实现继承树结构。 我一直认为,除非是自己内部使用的私有(至少是package私有)类,尽量不要用extends。继承不够灵活,父子类耦合过大,而且你还占用了子类的唯一一个父类的位置。从实际经验中发现,90%的情况,用接口聚合都何以完成继承所能完成的功能,而且灵活很多。这也是为什么我现在认为vb6的继承和多态方式要比 java好的多。 接口的聚合的一个缺点是cast,到处upcast,downcast让代码过于丑陋.因此在接口聚合中引入generic会让代码更加的清晰和漂亮。同时generic提倡的扁平的类结构和模块之间的正交分解也是接口聚合所缺少的。采用generic和接口聚合就能实现Policy base desing,这种设计方式足以代替复杂繁琐的AOP,而且不需要引入额外的语言特性。 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2004-04-07
耦合与内聚是一个相对的概念,任何的对象或者类只要有联系,那么就必定有耦合,只是耦合的大小和机制不同罢了。
作一个好的系统分析和设计,是需要尽量降低耦合,但是,它依然有一个成本效益的。Java推荐复合,不建议继承,但是并不是反对继承。的确继承可以用复合实现,可以降低程序的耦合度,但是它带来了另外一个问题,也就是程序的可读性与大为提高了工作量。 好的设计是会去追求耦合与内聚的平衡点的,就如同数据库设计在追求范式的时候也同样追求通过冗余提高效率。 楼上,不要绝对地看待一个问题,不要极端地排斥一种方法,存在及合理。 |
|
| 返回顶楼 | |
|
时间:2004-04-07
凤舞凰扬 写道 耦合与内聚是一个相对的概念,任何的对象或者类只要有联系,那么就必定有耦合,只是耦合的大小和机制不同罢了。
作一个好的系统分析和设计,是需要尽量降低耦合,但是,它依然有一个成本效益的。Java推荐复合,不建议继承,但是并不是反对继承。的确继承可以用复合实现,可以降低程序的耦合度,但是它带来了另外一个问题,也就是程序的可读性与大为提高了工作量。 好的设计是会去追求耦合与内聚的平衡点的,就如同数据库设计在追求范式的时候也同样追求通过冗余提高效率。 楼上,不要绝对地看待一个问题,不要极端地排斥一种方法,存在及合理。 我不认为这与极端有什么的关系.不要绝对之类云云,大有你好我好大家好的意思。很多问题虽然嘴巴上可以各打50大板,但是事实并非如此。逻辑上有这样一种规则,从一个互相矛盾的命题可以推论出任何结果。所以对于这种各打五十大板的说法。我即无法评论,也看不出有什么价值。因为这种评论本身就不含有什么信息量,说了等于不说。 至于为何还存在着继承,我想这仅仅是"政治"上的妥协。如果存在及合理,那么在OO中使用goto合理么? |
|
| 返回顶楼 | |
|
时间:2004-04-08
不可绝对,
至少从性能的角度看,调用父类方法要比调用接口方法要快 |
|
| 返回顶楼 | |
|
时间:2004-04-08
rostone 写道 不可绝对,
至少从性能的角度看,调用父类方法要比调用接口方法要快 那这句话如何 "不可绝对, 至少从性能的角度看,使用goto要比不使用goto快" |
|
| 返回顶楼 | |
|
时间:2004-04-08
为什么不考虑抽象类的聚合?
这段我帖过了,再帖一下:) ===================== (来自熊节的网站) ================================================== 接口和抽象类 在去年的12 月初, 我在Design Patterns Explained 的讨论组上看到这个系列帖子, 觉得很有意思, 所以整理出来。如果你喜欢这种形式, 请写信到gigix@263.net, 我会整理 更多有趣的帖子出来。 面向对象中有一个术语: 派生。在用Java 编程的时候, 派生的来源可以是抽象类 ( abstract class)、也可以是接口( interface)。何时使用抽象类? 何时使用接口? 这几 个帖子正是讨论了这个问题。 ( 提出问题) Alan, 我买了你的书, 读到了其中“ Bridge 模式” 那一章。然后, 我把一位朋友— — 他是个Java专家, 有五、六年使用Java的经验— — 叫来, 把你的书给他看, 并请他为我解 释你的Java 代码。我自己比较偏向于使用接口, 而你更多的使用了抽象类。他得出的结论 是: 你偏向于使用抽象类, 这反映出你对C++的爱好多于对Java的爱好。毕竟Java才有接 口这个概念, 而C++只有抽象类。因此, 我们感觉: 如果把你的示例代码中的抽象类换成接 口, 也许会使它们更能反映出实际情况和“ 按照协议设计” 的思想。实际上, 如果读过Peter Coad的书, 你会发现他也主要使用接口。 ( Alan Shalloway 的答复) 感谢你对这本书的评论。 我用Java 也有五、六年了, 所以我不认为使用抽象类就表示没有经验。实际上, 我觉 得Java 的一大缺憾就是它不支持多继承, 所以开发者经常被迫使用接口, 因为继承只能使 用一次。当然了, 这里面涉及到多继承的性能问题和基类成员的名字重复问题。但是不管怎 么说, 不允许多继承总是值得质疑的。 至于我这本书, 我选择使用抽象类, 是因为大多数人都熟悉它— — 不是每个人都喜欢去 看章节后面的C++示例的, 所以我决定用这种更通用的示例。 接口的问题就是它无法拥有默认行为。如果你用接口来定义一个什么东西并希望跨过这 层封装来添加一些行为, 你就必须使用委托。在我的记忆中, 有很多次就是因为不支持默认 行为而造成了麻烦。为了绕过这个限制, 我会使用委托, 但是这也是个痛苦的事情。 BTW: 我很喜欢Coad的书— — 我认为那是有史以来最好的设计书籍之一。 ( Scott Bain 的跟贴) 从我自己的角度来说, 我是一个Java程序员( 从来没用过C++)。但是每当我遇到单继 承关系的时候, 我倾向于使用抽象类, 尽管我知道Java 不支持多继承。为什么这样? 因为 即使我现在没有任何默认行为, 将来我还可以放进一些默认行为, 而不必修改子类的结构。 而且, 如果要在一个继承体系中放置工厂方法来生成子类的实例, 抽象类是就是最好、 最合乎逻辑的地方。因为客户对象只需要“ 知道” 抽象类就可以了, 通过转型得到的实例完 全封装了子类的存在和选择子类的规则。这使得系统的扩展变得简单,而且不会造成副作用。 只有当需要让一个类“ 象什么” 而非“ 是什么” 的时候, 我才会使用接口。 ( Alan Shalloway 的回复) PDF created with FinePrint pdfFactory trial version http://www.fineprint.com 精彩! 很高兴看到这样的回复! J 我也发现, 在Java 中缺少默认行为造成的影响似乎还没有真正引起人们的重视。从一 开始, SUN 似乎就忽略了向后兼容的问题。我可以大胆的预言: 完全用接口构建成的系统将 在几年之后发现这是一个大问题。据我所知, 有几个基于COM 模型( 也就是接口) 构建的应 用程序的开发者现在已经陷入了维护问题, 因为当他们想修改接口来应对新的情况( 比如添 加新的参数或新的方法) 时, 这会耗去他们大量的时间。如果使用抽象类, 修改就不会这么 困难, 因为你可以只修改默认方法, 子类就不用动了。 我可不是说不能使用接口, 但是你必须清楚: 有些时候应该使用接口, 而有些时候就是 应该使用抽象类。不幸的是, 由于Java不支持多继承, 你必须很小心的做出选择。 ( Scott Bain 的跟贴) 同样精彩! 关于接口, 我再来废话几句: 有时候, 一个东西可以“ 是” 几个东西。不明白这句话吗? 我就拿我自己来举个例子吧: 我“ 是”一个程序员、一个老师、一个作者、一个父亲… … 你可以说我“ 实现了老师的接口”, 学生们会向我询问关于上课的问题。我还“ 实现了父亲的接口”, 但是我的学生可能知道这 一点, 也可能不知道。如果他们知道这一点, 他们就可以放心的向我询问关于抚养小孩的问 题了。 但是, 尽管我实现了所有这些接口, 我还有一个抽象类— — “ 人”。这个抽象类给我提 供了所有的默认行为( 比如说我偶尔会走背运)。 |
|
| 返回顶楼 | |
|
时间:2004-04-08
我对这个话题很感兴趣,不过我并不很懂oo,但是希望你的观点成立,希望你能证明,你所提倡的模式能够很好的解决aop来解决的问题。
games说,如果能用已有的技术解决的问题,为什么要发明一种新的技术? |
|
| 返回顶楼 | |
|
时间:2004-04-09
Trustno1 写道 多态和继承的问题我是这样看得:首先要分清楚,实现多态和接口多态。
实现多态就是代码重用(abastract class),接口多态体现的是is-a关系(interface)。把两者混为一谈只会捣乱。 实现多态其实不是一个必要的特性。能不用就尽量避免。一个好的oo设计,应该有合理的接口继承体系和一个尽量扁平的实现继承树结构。 我一直认为,除非是自己内部使用的私有(至少是package私有)类,尽量不要用extends。继承不够灵活,父子类耦合过大,而且你还占用了子类的唯一一个父类的位置。从实际经验中发现,90%的情况,用接口聚合都何以完成继承所能完成的功能,而且灵活很多。这也是为什么我现在认为vb6的继承和多态方式要比java好的多。 这里的用词,我不是很清楚。关于实现多态和接口多态的描述,我试着换一种描述,你看是不是你的本意。 扩展继承:通过继承,扩展基类的属性与方法 多态继承:通过继承,覆写基类的方法 扩展是在原有的基础上增加,而多态是重新实现与原有基类同样多的接口。 从Java语法来说,我认为extend的含义,只能代表前一种继承。 而后一种继承,其实不能称为继承,应该学习delphi的做法,另外使用一个关键词,overload。 就像这样: [code:1]public class Animal { public void move(){ } } public class NormalBird extends animal { public void fly(){ } } public class Eagle overload NormalBird { public void fly(){ System.out.println("Eagle Fly"); } } public class Spadger overload NormalBird { public void fly(){ System.out.println("Spadger Fly"); } }[/code:1] 这两种方式,含义是不一样的,不应该混用,就是说,不应该子类对于父类,即扩展,又覆盖。这样可能会带来混乱,和理解上的困难。但是这并不意味着这两种方式中,只有一种有价值,从面向对象是对于真实世界的尽可能贴近的描述这个角度出发,应该尽可能按照世界本来应该的那个样子去划分类,而不是仅仅考虑:另外的手段,也可以实现。 你说到一个好的OO设计,应该怎样怎样。我的理解是,OO的精髓在于,面向对象——面向真实世界,而不是面向过程,面向计算机的实现方式。 所谓面向真实世界,就是说:“判断一个设计是不是好,就在于他是不是‘合理’,不是合计算机的理,而是合外在世界的理。” Trustno1 写道 接口的聚合的一个缺点是cast,到处upcast,downcast让代码过于丑陋.因此在接口聚合中引入generic会让代码更加的清晰和漂亮。同时generic提倡的扁平的类结构和模块之间的正交分解也是接口聚合所缺少的。采用generic和接口聚合就能实现Policy base desing,这种设计方式足以代替复杂繁琐的AOP,而且不需要引入额外的语言特性。
generic我没有什么了解,能介绍一下吗? |
|
| 返回顶楼 | |
|
时间:2004-04-09
楼上的看来是彻底没有看懂我的意思啊!看来误解的挺多,好吧关于接口继承和实现继承的问题,我将手头的一个sample讲解一下。
我们如果要做一个服务器的shell(ServiceShell),我们可以通过这个ServiceShell来控制服务器的启动,关闭,暂停,唤醒的操作。我们可以定义这样一个interface [code:1] public interface Serviceable { //启动 public void launch()throws NetException,ServiceException,RemoteException; //暂停 public void suspend()throws NetException,RemoteException; //唤醒 public void wakeup()throws NetException,RemoteException; //关闭 public void close()throws NetException,RemoteException; //清理资源,例如socket等 public void clear()throws NetException,RemoteException; //根据外部的传入的参数进行配置 public void deployByParams(Hashtable params)throws ServiceException,NetException,RemoteException; //得到服务器的运行状态 public int getStatus()throws RemoteException; } [/code:1] 如果我们要写一个Http服务器,ftp服务器。对于不同的服务器只要implement Serviceable这个接口,就能统一的对所有的服务器进行管理。这个问题很简单,大家都应该清楚就不多说了。 我们现在来看一个HttpServer的代码片断 [code:1] public class HttpServer implements Serviceable { private boolean isLaunched; private boolean isRunning; //启动 public void launch()throws NetException,ServiceException,RemoteException { if(isLaunched==true) throw new ServiceException("Service is running"); //do launch isLaunched=true; } //暂停 public void suspend()throws NetException,RemoteException { if(isRunning==false) throw new ServiceException("Service is not running"); //do suspend is Running=false; } ....... } [/code:1] 我们分析一下这段代码,可以发现无论是HttpServer还是FTPServer,他们的状态标志的跳转,以及各个方法的状态检查和保护代码都是一样的.那么按照我们的习惯,就应该把他们抽象出来。我们抽象成这样一个类 [code:1] public abstract class Service implements Serviceable { private boolean isLaunched; private boolean isRunning; //启动 public void launch()throws NetException,ServiceException,RemoteException { preLaunch(); if(isLaunched==true) throw new ServiceException("Service is running"); //do launch isLaunched=true; postLaunch(); } //暂停 public void suspend()throws NetException,RemoteException { presuspend(); if(isRunning==false) throw new ServiceException("Service is not running"); //do suspend is Running=false; postsuspend(); } protected abstract void preLaunch(); protected abstract void postLaunch(); protected abstract void presuspend(); protected abstract void postsuspend(); ...... } [/code:1] 那么一个HttpServer就是如下的形式了. [code:1] public class HttpServer extends Service { protected void preLaunch() { //do something } protected void postLaunch() { //do something } protected void presuspend() { //do something } protected void postsuspend() { //do something } } [/code:1] 这就是我们通常采用的实现继承的来达到代码的复用。 那么什么是基于接口继承呢?要使用接口继承,首先要明白功能和需求的正交化分解。正交化分解就是,要把功能分解成互不相关的独立部分。例如上面的HttpServer,如果进行正交化分解,那么可以分成下面几个不相关的部分 1,服务器对外接口2,状态标志的互斥和变换3,服务器状态机跳转4,HttpServer的具体实现。由于整个ServiceShell的运行完全依赖于服务器状态机跳转这个抽象的逻辑,因此我们将服务器状态机跳转设定为Host class(宿住类),其他的部分就称为Policy 。这样的设计方式就称为Policy base desgin. 首先我们为所有的Policy设计接口。 [code:1] public interface Service { public void setFlag(ServiceFlag flg); public void preLaunch(); public void postLaunch(); public void presuspend(); public void postsuspend(); ..... } public interface ServiceFlag { setLaunchFlag(); setSuspendFlag(); setCloseFlag(); .... int getStatus(); } [/code:1] 实现Host class宿主类,并且约定Policy class之间的协议。 [code:1] public class ServiceShell implements Serviceable { ServiceFlag svFlag Service service; public Service(ServiceFlag Flag,Service sv) { sv.setFlag(Flag); service=sv; svFlag=Flag; } public void Launch() { if(svFlag.getStatus()==LAUNCHED) throw new ServiceException("service is launched); sv.prelaunch() svFlag.setLaunchFlag(); sv.postLaunch(); } } [/code:1] 这样的做法的好处在哪里呢? 首先,基于实现的继承强调的一个非常关键的问题在于基类需要相当的稳固,否则基类一旦改动,基类就真的是鸡肋了。 例如下面的代码中, [code:1] if(svFlag.getStatus()==LAUNCHED) throw new ServiceException("service is launched); [/code:1] 我们不仅仅需要在状态冲突的时候抛出异常,我们需要将这个错误纪录到日志里面。而且不同的服务器有不同的纪录日志的方法,有些写数据库,有些写文件,有些或者直接是printscreen。 如果采用基于实现的继承,你就必须在基类中插入一个抽象方法。 [code:1] public abstract void printErr(String str); [/code:1] 一旦增加了这样一个方法,所有的子类都要跟着改。而基于接口的继承,由于所有的Policy都是正交的,彼此不相关的。因此我只要添加一个输出错误的接口即可,对于其他的Policy则都不会受到影响。 第二,在于Policy是可替换的。还是用上面的printErr这个例子。采用基于接口的继承对于不同的需求我可以进行不同配置,完全不需要涉及到具体的实现。对于同一个HttpServer,可以适配上所有的打印借口,甚至只要做一个factory就能达到动态装配的效果。如果是采用基于实现的继承,那么要更改printErr地实现必须深入到具体的实现中去,而且需求改一次就需要动一次代码。 第三,大家应该从Policy base design中看得出一些AOP的影子了吧。不错其实只要继续的深入下去完全可以设计出一个不需要引入额外语法的AOP。另外AOP与基于接口继承的相比有一个不足,那就是AOP只能横切到接口和方法签名,而对于内部逻辑是无能为力的。例如上面的那个printErr,AOP是无法横切到这个地步的。至于Generic,我今天从新看了JSR14发现可能是我对于Tiger的generic预期过于乐观了。目前的java generic功能很弱对于裸类型构造,mixin都没有支持因此要用java generic来做policy base design和AOP不太可能。如果对generic的AOP有兴趣的话,可以去看<modern c++ design>,以及到google上去search c++ MixIn这样的关键字。应该有不少资源 第四:关于Gigix说的不用抽象类就没有默认方法的说法,是不成立的。相对于abstract class,Host class同样描述了默认方法。另外楼上的提到接口继承的可读的性的问题,请您具体的指出这样的实现可读性差在那里? |
|
| 返回顶楼 | |
|
时间:2004-04-14
Trustno1 写道 楼上的看来是彻底没有看懂我的意思啊!看来误解的挺多,好吧关于接口继承和实现继承的问题,我将手头的一个sample讲解一下。
看了你楼上的那篇和第一篇,感觉两者描述的差距实在太大,也不能怪别人不理解你的意思,是你自己第一篇没有表达清楚。 有点疑问,你说的正交的接口,怎么两个接口Service和ServiceFlag好像并不正交啊,两者有联系啊。 对于你的想法,我的理解是,把抽象类要具体实现的那部分逻辑提取出来了,单独构成了一个你所说的宿主类,然后把该抽象类中其他的抽象方法根据业务需求重组或者重新定义了一下,分别归入了几个不同的接口中。不知道对不对?其实就算用了抽象类,你想要添加一些东西也可以新定义一个接口什么的,不矛盾啊。而且你也可以把一个抽象类根据功能需求什么的,分解成一个抽象类和若干个几个互不相干的接口啊。可能是我眼拙,没看出什么区别来。 我的看法: 不含抽象方法的抽象类就可以看作(这里是可以改为)一个普通类 不含具体方法的抽象类就可以看作(这里可以改为)一个接口 抽象类和接口的运用完全在于需要,没有什么地方非要谁谁不可的,两者可以根据需要灵活的组合,只要依赖于OO的几个基本原则就可以了。 |
|
| 返回顶楼 | |












