OOP&完全避免实现继承是可能的吗?

时间:2020-03-06 14:37:35  来源:igfitidea点击:

我将选择Java作为示例,大多数人都知道,尽管其他所有OO语言也都可以使用。

Java与许多其他语言一样,具有接口继承和实现继承。例如。 Java类可以从另一个类继承,并且在那里具有实现的每个方法(假设父类不是抽象的)也都被继承。这意味着接口将被继承,该方法的实现也将被继承。我可以覆盖它,但不必这样做。如果我不覆盖它,那么我已经继承了实现。

但是,我的类也可以仅继承接口(而不是Java术语),而无需实现。实际上,接口在Java中实际上是用这种方式命名的,它们提供了接口继承,但是没有继承任何实现,因为接口的所有方法都没有实现。

现在有一篇文章,说继承接口比实现更好,我们可能想阅读它(至少是第一页的前半部分),这很有趣。它避免了诸如脆弱的基类问题之类的问题。到目前为止,这很有意义,而本文中所说的许多其他事情对我来说也很有意义。

令我烦恼的是,实现继承意味着代码重用,这是OO语言最重要的特性之一。现在,如果Java没有类(如Java的教父James Gosling所希望的那样),它将解决实现继承的所有问题,那么如何使代码重用成为可能?

例如。如果我有一个Car类,而Car有一个方法move(),则它将使Car移动。现在,我可以将Car细分为不同类型的汽车,这些汽车都是汽车,但都是Car的专用版本。有些可能以不同的方式移动,但无论如何都需要覆盖move(),但是大多数都将保留继承的移动,因为它们就像抽象父Car一样移动。现在假设一秒钟,Java中只有接口,只有接口可以彼此继承,一个类可以实现接口,但是所有类始终是最终的,因此任何类都不能从任何其他类继承。

当拥有接口汽车和上百个汽车类时,如何为它们中的每个实现一个相同的move()方法,如何避免这种情况?在OO世界中,除了实现继承以外,还有哪些用于代码重用的概念?

某些语言具有Mixins。 Mixins是我的问题的答案吗?我阅读了有关它们的内容,但我无法真正想象Mixins如何在Java世界中工作,以及它们是否真的可以解决这里的问题。

另一个想法是,有一个类仅实现Car接口,我们将其称为AbstractCar,并实现move()方法。现在,其他汽车也实现了Car接口,在内部创建了AbstractCar的实例,并通过在内部抽象Car上调用move()来实现自己的move()方法。但这不会浪费资源吗(一个方法只调用另一个方法,JIT可以内联代码,但仍然可以)并使用额外的内存来保留内部对象,我们甚至不需要实现继承? (毕竟每个对象都需要比仅封装数据总和更多的内存)对于程序员来说,编写像这样的虚拟方法也不难吗?

public void move() {
    abstractCarObject.move();
}

任何人都可以想象出一个更好的主意,如何避免实现继承,并且仍然能够以一种简便的方式重用代码?

解决方案

我们也可以使用组合和策略模式。

public class Car
{
  private ICar _car;

  public void Move() {
     _car.Move();
  }
}

这比使用基于继承的行为要灵活得多,因为它允许我们在运行时进行更改,方法是根据需要替换新的Car类型。

我们可以使用合成。在示例中,一个Car对象可能包含另一个名为Drivetrain的对象。汽车的move()方法可以简单地调用其传动系统的drive()方法。反过来,Drivetrain类可以包含诸如Engine,Transmission,Wheel等对象。如果以此方式构建类层次结构,则可以通过将不同的较简单零件组合(例如,重用代码)。

我们应该阅读设计模式。我们会发现接口对于许多类型的有用设计模式至关重要。例如,抽象化不同类型的网络协议将具有相同的接口(与调用它的软件)相同的接口,但是由于每种协议类型的行为不同,代码复用很少。

对于某些算法,在展示如何将编程的无数元素组合在一起以完成某些有用的任务方面令人大开眼界。设计模式对对象的作用相同。向我们展示如何组合对象以执行有用的任务。

四人帮的设计模式

为了使混合/合成更容易,请看一下我的注释和注释处理器:

http://code.google.com/p/javadude/wiki/注释

特别是,mixins示例:

http://code.google.com/p/javadude/wiki/AnnotationsMixinExample

请注意,如果委派的接口/类型具有参数化方法(或者方法上的参数化类型),则当前不起作用。我正在努力...

简短的回答:是的,这是可能的。但是,我们必须有目的地做到这一点,而绝非偶然(使用最终,摘要和设计时要牢记继承性,等等)。

长答案:

好吧,继承实际上不是为了"代码重用",而是为了类"专业化",我认为这是一种误解。

例如,从Vector创建一个Stack是一个非常糟糕的主意,只是因为它们是相似的。还是HashTable中的属性,仅因为它们存储值。参见[有效]。

"代码重用"更多是面向对象特性的"业务视图",这意味着对象很容易在节点之间分配;并且具有可移植性,并且没有以前编程语言产生的问题。事实证明,这是半个错误。现在,我们有了可以轻松分发的库。例如,在Java中,jar文件可用于任何项目,从而节省数千小时的开发时间。 OO在可移植性等方面仍然存在一些问题,这就是现在WebServices如此流行(就像以前是CORBA一样)的原因,但这是另一个线程。

这是"代码重用"的一方面。另一个是有效的,与编程有关。但是在这种情况下,不仅是要"保存"代码行并创建易碎的怪物,而且还要考虑继承性。这是前面提到的书中的第17项。第17项:继承的设计和文件,或者禁止继承。参见[有效]

当然,我们可能会有Car类和大量子类。是的,我们提到的有关Car接口,AbstractCar和CarImplementation的方法是正确的方法。

我们定义了汽车应遵守的"合同",并说这些是我在谈论汽车时希望采用的方法。具有抽象功能的抽象汽车,每辆汽车都有基本功能,但要离开并记录子类负责处理的方法。在Java中,我们可以通过将方法标记为抽象来实现此目的。

当我们以这种方式进行操作时,"脆弱"类没有问题(或者至少设计者有意识或者有威胁),并且子类仅完成设计者允许的那些部分。

继承更多地是"专业化"这些类,以同样的方式,卡车是Car的专门版本,而MosterTruck是Truck的专门版本。

从Car创建" ComputerMouse"子句并不明智,因为它具有一个类似于汽车的Wheel(滚轮),它可以移动,并且在其下方有一个wheel以节省代码行。它属于另一个域,它将用于其他目的。

从一开始就采用编程语言来防止"实现"继承,我们应该在类声明中使用final关键字,并且这种方式禁止子类。

子类化不是故意的,而是邪恶的。如果处理不当,可能会成为一场噩梦。我要说的是,我们应该从私有化和"最终化"开始,并尽可能使事情变得更加公开和可扩展。演示文稿"如何设计良好的API及其重要性"中对此也作了广泛的解释,请参见[良好的API]

继续阅读文章,再加上时间和实践(还有很多耐心),这件事会变得更加清楚。尽管有时我们只需要完成工作并复制/粘贴一些代码:P即可。没关系,只要我们先尝试做好。

这是Joshua Bloch(以前在Sun的Java核心工作,现在在Google工作)的引用。
[有效的]
有效的Java。绝对是非初学者应该学习,理解和实践的最佳Java书籍。一个必须有。

有效的Java

[Good API]介绍API的设计,可重用性和相关主题的演示。
这有点冗长,但值得每一分钟。

如何设计良好的API及其重要性

问候。

更新:请看我发送给视频链接的第42分钟。它讨论了这个主题:

"当我们在公共API中有两个类,并且我们想使一个成为另一个子类时,例如Foo是Bar的子类,请问问自己,Every Foo是Bar吗?

在前一分钟,它在讨论TimeTask时讨论了"代码重用"。

反对继承的大多数示例的问题是这样的示例:人员错误地使用了继承,而不是继承未能正确抽象。

在我们发布的链接的文章中,作者使用Stack和ArrayList显示了继承的"破坏性"。该示例存在缺陷,因为Stack不是ArrayList,因此不应使用继承。该示例与String扩展字符或者PointXY扩展Number一样有缺陷。

在扩展类之前,应始终执行" is_a"测试。由于不能以某种方式说出每个堆栈都是一个ArrayList而不是错误的,因此我们不应该继承。

Stack的约定与ArrayList(或者List)的约定不同,并且stack不应继承那些不关心的方法(例如get(int i)和add())。实际上,Stack应该是具有以下方法的接口:

interface Stack<T> {
   public void push(T object);
   public T pop();
   public void clear();
   public int size();
}

诸如ArrayListStack之类的类可能会实现Stack接口,在这种情况下,使用组合(具有内部ArrayList)而不是继承。

继承还不错,不好的继承也很糟糕。

对于面向对象的语言,继承不是必需的。

可以考虑使用Javascript,它比Java更加面向对象。没有类,只有对象。通过将现有方法添加到对象来重用代码。 Javascript对象本质上是名称到函数(和数据)的映射,映射的初始内容由原型建立,并且新条目可以动态添加到给定实例。

回答我自己的问题很有趣,但是我发现这很有趣:Sather。

这是一种完全没有实现继承的编程语言!它知道接口(称为没有实现或者封装数据的抽象类),并且接口可以彼此继承(实际上它们甚至支持多重继承!),但是一个类只能实现接口(抽象类,数量众多),它不能从另一个类继承。但是,它可以"包含"另一个类。这是一个委托概念。包含的类必须在类的构造函数中实例化,并在销毁类时销毁。除非我们覆盖它们的方法,否则类也将继承其接口,而不继承其代码。而是创建了一些方法,这些方法将对方法的调用转发到包含对象的同名方法。包含的对象和仅封装的对象之间的区别在于,我们不必创建自己的委托,它们也不会作为可以传递的独立对象而存在,它们是对象的一部分,可以与我们一起生活和死亡对象(或者更准确地说,是对象:以及所有包含的对象的内存是通过单个alloc调用创建的,相同的内存块,我们只需要在构造函数调用中将它们初始化,而当使用真正的委托时,每个对象都会一个自己的alloc调用,一个自己的内存块,并且完全独立于对象而生活)。

语言不是很漂亮,但我喜欢它背后的想法:-)