我会选择Java作为一个例子,大多数人都知道它,尽管其他所有OO语言都可以正常工作.
与许多其他语言一样,Java具有接口继承和实现继承.例如,Java类可以从另一个继承,并且每个在其中具有实现的方法(假设父级不是抽象的)也是继承的.这意味着接口是继承的,也是此方法的实现.我可以覆盖它,但我没有.如果我不覆盖它,我继承了实现.
但是,我的类也可以"继承"(不是用Java术语)只是一个接口,没有实现.实际上接口实际上是用Java命名的,它们提供了接口继承,但没有继承任何实现,因为接口的所有方法都没有实现.
现在有这篇文章,说继承接口比实现更好,你可能想读它(至少在第一页的前半部分),这很有意思.它避免了脆弱的基类问题.到目前为止,这一切都很有意义,文章中说的许多其他内容对我来说很有意义.
让我感到困惑的是,实现继承意味着代码重用,这是OO语言最重要的属性之一.现在,如果Java没有类(如James Gosling,Java的教父希望根据本文),它解决了实现继承的所有问题,但是如何使代码重用成为可能呢?
例如,如果我有一个类Car和Car有一个方法move(),这会使Car移动.现在我可以为不同类型的汽车分类汽车,这些都是汽车,但都是汽车的专用版本.有些可能以不同的方式移动,这些需要覆盖move(),但大多数只会保留继承的移动,因为它们像抽象的父Car一样移动.现在假设在Java中只有接口,只有接口可以相互继承,一个类可以实现接口,但所有类都是final,所以没有类可以从任何其他类继承.
如果你有一个Interface Car和100个Car类,你需要为每个类实现相同的move()方法吗?OO世界中存在除了实现继承之外的代码重用的哪些概念?
有些语言有Mixins.Mixins是我问题的答案吗?我读到了它们,但我无法想象Mixins如何在Java世界中运行,以及它们是否真的可以在这里解决问题.
另一个想法是,有一个类只实现Car接口,让我们称之为AbstractCar,并实现move()方法.现在其他汽车也实现了Car接口,在内部他们创建了一个AbstractCar实例,他们通过在内部抽象Car上调用move()来实现自己的move()方法.但这不会浪费资源(一种方法只调用另一种方法 - 好吧,JIT可以内联代码,但仍然)并使用额外的内存来保存内部对象,你甚至不需要实现继承?(毕竟每个对象都需要更多的内存,而不仅仅是封装数据的总和)对于程序员来说,编写虚拟方法也不是很尴尬
public void move() { abstractCarObject.move(); }
?
任何人都可以想象一个更好的想法如何避免实现继承,仍然能够以简单的方式重用代码?
简短的回答:是的,这是可能的.但是你必须故意这样做而不是偶然(使用最终的,抽象的和带有继承的设计等)
答案很长:
那么,继承实际上不是为了"代码重用",而是为了"类专业化",我认为这是一种误解.
例如,从Vector创建堆栈是一个非常糟糕的主意,因为它们是相似的.或者来自HashTable的属性只是因为它们存储了值.见[有效].
"代码重用"更像是OO特征的"业务视图",这意味着您的对象可以在节点之间轻松分发; 并且是可移植的,并且没有以前的编程语言生成的问题.事实证明这一点已经过时了.我们现在拥有可以轻松分发的库; 例如,在java中,jar文件可以在任何节省数千小时开发的项目中使用.OO仍然存在可移植性等问题,这就是现在WebServices如此受欢迎的原因(就像之前的CORBA一样),但这是另一个线程.
这是"代码重用"的一个方面.另一个是有效的,与编程有关的那个.但在这种情况下,不仅仅是"保存"代码行并创建脆弱的怪物,而是考虑到继承的设计.这是前面提到的书中的第17项; 第17项:继承的设计和文件,或禁止它.见[有效]
当然,你可能有一个Car类和大量的子类.是的,你提到的关于Car interface,AbstractCar和CarImplementation的方法是正确的方法.
你定义了汽车应该遵守的"合同",并说这些是我在谈论汽车时所期望的方法.具有基本功能的抽象汽车,每辆汽车都离开并记录子类负责处理的方法.在java中,您可以通过将方法标记为抽象来完成此操作.
当你以这种方式进行时,"脆弱"类(或者至少设计者是有意识的或威胁)没有问题,并且子类只完成设计者允许的那些部分.
继承更多的是"专门化"类,以同样的方式,Truck是Car的专用版本,MosterTruck是Truck的专用版本.
它没有让sanse从Car创建一个"ComputerMouse"子类酶,因为它有一个像汽车一样的轮子(滚轮),它会移动,并且下方有一个轮子以保存代码行.它属于不同的域,它将用于其他目的.
防止"实现"继承的方法是从编程语言开始,你应该在类声明中使用final关键字,这样就可以禁止子类.
如果它是故意的,那么子类化并不是邪恶的.如果做得不好,可能会成为一场噩梦.我会说你应该尽可能私密和"最终"开始,如果需要的话,可以让事情变得更加公开和可扩展.在"如何设计良好的API及其重要性"演示文稿中也对此进行了广泛的解释.请参阅[Good API]
继续阅读文章,随着时间和练习(和很多耐心),这件事情会更加清晰.虽然有时您只需要完成工作并复制/粘贴一些代码:P.这是好的,只要你尝试先做好.
以下是来自Joshua Bloch的参考资料(以前在Sun工作,现在是Google的核心工作)
有效的Java
如何设计一个好的API及其重要性
问候.
"如果你在公共API中有两个类,并且你想让一个类成为另一个的子类,比如Foo是Bar的子类,那么问问你的自己,Every Foo是吧?......"
在前一分钟,它谈到了"代码重用",同时谈论TimeTask.
大多数示例反对继承的问题是人正在使用继承错误的示例,而不是继承失败以正确抽象.
在您发布链接的文章中,作者使用Stack和ArrayList显示了继承的"破坏性".该示例存在缺陷,因为Stack不是ArrayList,因此不应使用继承.该示例与String扩展字符或PointXY扩展Number一样有缺陷.
在扩展类之前,应始终执行"is_a"测试.既然你不能说每个堆栈都是一个ArrayList而不是在某种程度上是错误的,那么你就不应该知道.
Stack的契约与ArrayList(或List)的契约不同,堆栈不应该是继承不关心的方法(如get(int i)和add()).事实上,Stack应该是一个具有以下方法的接口:
interface Stack{ public void push(T object); public T pop(); public void clear(); public int size(); }
像ArrayListStack这样的类可能实现Stack接口,在这种情况下使用组合(具有内部ArrayList)而不是继承.
继承不坏,坏继承不好.