OO编程中的典型问题是钻石问题.我有父类A,有两个子类B和C.A有一个抽象方法,B和C实现它.现在我有一个子类D,它继承了B 和 C.钻石问题现在,D使用什么实现,B或C之一?
人们声称Java不知道钻石问题.我只能有接口的多重继承,因为它们没有实现,我没有钻石问题.这是真的吗?我不这么认为.见下文:
[删除车辆示例]
钻石问题总是导致糟糕的类设计,也不是程序员和编译器需要解决的问题,因为它首先不应该存在?
更新:也许我的榜样选择不当.
看到这个图像
(来源:suffolk.edu)
当然你可以用C++创建Person虚拟,因此你只有一个人在内存中的实例,但真正的问题仍然存在恕我直言.你如何为GradTeachingFellow实现getDepartment()?考虑一下,他可能是一个系的学生,另一个系教学.所以你可以退回一个部门或另一个部门; 对于这个问题没有完美的解决方案,并且没有实现可以继承(例如学生和教师都可以作为接口)似乎并没有解决问题.
你所看到的是违反Liskov替代原则的方式如何使得一个有效的,逻辑的面向对象结构变得非常困难.
基本上,(公共)继承应该只缩小类的目的,而不是扩展它.在这种情况下,通过继承两种类型的车辆,您实际上是在扩展目的,并且正如您所注意到的那样,它不起作用 - 水上交通工具的移动应该与公路交通工具的移动非常不同.
您可以在水陆两用车辆中汇总水上交通工具和地面车辆对象,并在外部决定哪两个适合当前情况.
或者你可以决定"车辆"类是不必要的通用,你将有两个单独的接口.这并不能解决你自己的两栖车辆的问题 - 如果你在两个界面中调用移动方法"移动",你仍然会遇到麻烦.所以我建议聚合而不是继承.
C#
有明确的接口实现来部分处理这个问题.至少在你有一个中间接口(它的一个对象......)的情况下
然而,可能发生的事情是AmphibianVehicle对象知道它目前是在水上还是陆地上,并做正确的事情.
在您的示例中,move()
属于Vehicle
接口并定义合同"从A点到B点".
当GroundVehicle
和WaterVehicle
扩展Vehicle
,他们含蓄地继承了这个合同(比喻:List.contains
从继承其合同Collection.contains
-如果指定的不同的东西想象!).
因此,当具体AmphibianVehicle
实施时move()
,它真正需要尊重的合同是Vehicle
.有一颗钻石,但无论你是考虑钻石的一面还是另一面(或者我称之为设计问题),合同都不会改变.
如果您需要"移动"的合同来体现表面的概念,请不要将其定义为不对此概念进行建模的类型:
public interface GroundVehicle extends Vehicle { void ride(); } public interface WaterVehicle extends Vehicle { void sail(); }
(比喻:get(int)
合同是由List
界面定义的.它不可能由定义Collection
,因为集合不一定是订购的)
或者重构您的通用接口以添加概念:
public interface Vehicle { void move(Surface s) throws UnsupportedSurfaceException; }
我在实现多个接口时遇到的唯一问题是来自完全不相关的接口的两个方法碰巧碰撞:
public interface Vehicle { void move(); } public interface GraphicalComponent { void move(); // move the graphical component on a screen } // Used in a graphical program to manage a fleet of vehicles: public class Car implements Vehicle, GraphicalComponent { void move() { // ??? } }
但那不会是钻石.更像是一个颠倒的三角形.
人们声称Java不知道钻石问题.我只能有接口的多重继承,因为它们没有实现,我没有钻石问题.这是真的吗?
是的,因为你在D中控制接口的实现.两个接口(B/C)之间的方法签名是相同的,并且看到接口没有实现 - 没有问题.