JavaRush /Java 博客 /Random-ZH /继承作为一种现象
articles
第 15 级

继承作为一种现象

已在 Random-ZH 群组中发布
说实话,我最初并没有计划写这篇文章。我认为我在这里要讨论的问题是微不足道的,甚至不值一提。然而,在为本网站撰写文章的过程中,我在一个论坛上提出了关于多重继承的讨论。结果发现,大多数开发者对于继承的理解非常模糊。因此,他犯了很多错误。由于继承是 OOP 最重要的特性之一(如果不是最重要的话!),因此我决定专门写一篇文章来讨论这一现象。* * * 首先,我想区分两个概念——对象和类。这些概念经常被混淆。同时,它们是 OOP 的核心。而且,在我看来,有必要了解它们之间的差异。那么,对象。基本上,什么都可以。这是立方体。木质、蓝色。边缘的长度是5厘米,这是一个物体。还有一座金字塔。塑料, 红色. 10 厘米肋骨。这也是一个对象。他们有什么共同点?不同的尺寸。形状不同。材质不同。然而,他们有一些共同点。首先,立方体和金字塔都是正多面体。那些。顶点数和面数之和比边数多 2。更远。两种形状都有面、边和顶点。两个图形都具有肋骨尺寸等特征。两种形状都可以旋转。两个图都可以画出来。最后两个属性已经是行为。等等。编程实践表明,操作同类对象比操作异构对象容易得多。由于这些人物之间仍然存在一些共同点,因此希望以某种方式强调这种共同点。这就是阶级概念发挥作用的地方。那么,定义。
类是一组对象的公共属性的描述符。这些属性既可以是对象的特征(大小、重量、颜色等),也可以是行为、角色等。
评论。没有说出“全部”(所有属性的描述符)这个词。这意味着任何对象都可以属于多个不同的类。 继承作为一种现象 - 1 让我们以几何形状为基础来举同样的例子。最一般的描述是正多面体。无论边缘大小、面数和顶点数如何。我们唯一知道的是这个图形有顶点、边和面,并且边的长度相等。 更远。我们可以让描述更加具体。假设我们要绘制这个 多面体让我们引入一个绘制正多面体这样的概念。画画我们需要什么?描述不依赖于特定顶点坐标的通用绘制方法。也许是物体的颜色。 现在我们来介绍CubeTetrahedron类。属于这些类别的物体当然是正多面体。唯一的区别是每个新类的顶点、边和面的数量已经严格固定。进一步地,知道了具体图形的类型,我们就可以给出绘制方法的描述。这意味着 CubeTetrahedron类的任何对象也是绘制的 正多面体类的对象。存在类的层次结构。在这个层次结构中,我们从最一般的描述下降到最具体的描述。请注意,任何类的对象也符合层次结构中任何更通用类的描述。这种类关系称为 继承。每个子类都继承父类的所有属性,更一般地说,并且(可能)将一些自己的属性添加到这些属性中。或者它重写了父类的一些属性。这里我想引用Gradi Bucha关于面向对象设计的经典著作:
因此,继承定义了类之间的“是一个”层次结构,其中子类继承自一个或多个超类。这实际上是遗传的试金石。给定类 A 和 B,如果 A“不是”B 的一种,那么 A 不应该是 B 的子类。
翻译一下听起来是这样的:
因此,继承定义了类之间的“is”层次结构,其中子类继承自一个或多个超类。事实上,这是继承的定义测试(字面意思是试金石,我的注释)。如果我们有类 A 和 B,并且类 A“不是”类 B 的变体,那么 A 一定不是 B 的子类。
读到这里的人可能会困惑地用手指转动太阳穴。第一个想法是,这只是微不足道的事情!这是真实的。但如果你知道我见过多少疯狂的继承层次结构!在我一开始提到的那个讨论中,其中一位参与者非常认真地从……机枪继承了一辆坦克!原因很简单,坦克有一挺机枪。这是最常见的错误。继承与聚合(将一个对象包含在另一个对象中)相混淆。坦克不是机枪,它装有一挺机枪。由于这个错误,大多数情况下人们希望使用多重继承。现在让我们直接转向 Java。继承方面有什么?该语言中有两种类型的类 - 能够包含实现的类和不能包含实现的类。后者称为接口,尽管本质上它们是完全抽象的类。 继承作为一种现象 - 2 因此,该语言允许您从可能包含实现的另一个类继承一个类。但仅来自一个!让我解释一下为什么这样做。关键是每个实现只能处理它自己的部分——它知道的那些变量和方法。即使我们从 AB继承类 C,那么从类 A 继承的方法 processA也只能使用内部变量 a ,因为它对 b一无所知,就像它对 c和方法 processC一无所知一样。 processB方法也是如此 只能与变量 b 一起使用。也就是说,本质上,继承的部分是孤立的。C 类当然可以与它们一起工作,但如果它们只是作为 C 类的一部分包含在内而不是继承的话,它也可以与这些部分一起工作。然而,这里还有另一个麻烦,那就是名称的重叠。 如果processAprocessB的方法命名相同,即 process,那么调用类 C的 process方法会产生什么效果?将调用这两个方法中的哪一个?当然,C++ 在这种情况下有控制权,但这并不能增加语言的和谐性。因此,实现继承没有优点,但也有缺点。因此,Java 中的这种实现继承已被放弃。但是,开发人员可以选择多重继承,例如从接口继承。用 Java 术语来说,就是接口实现。接口是什么?一套方法。(我们目前不考虑接口中常量的定义;更多信息请参见 此处。)什么是方法?方法的核心决定了对象的行为。几乎每个方法的名称都包含一个操作,这并非巧合 - getXXXdrawXXXcountXXX等。由于接口是方法的集合,因此 接口实际上是 行为的 决定因素。使用接口的另一个选择是定义对象的角色。 观察者、倾听者等 在这种情况下,该方法实际上是对某些外部事件的反应的体现。这又是行为。一个对象当然可以有几种不同的行为。如果需要渲染,就渲染。如果他需要被拯救,他就被拯救了。嗯,等等。因此,从定义行为的类继承的能力非常非常有用。同样,一个对象可以有多个不同的角色。然而, 实施 行为完全取决于子班的良心。从接口(其实现)继承表明此类的对象应该能够执行此操作。它如何做到这一点取决于每个独立实现接口的类。让我们回到继承中的错误。我开发各种系统的经验表明,通过接口继承,您可以实现任何系统,而无需使用多个实现继承。因此,当我遇到关于 C++ 中缺乏多重继承的抱怨时,对我来说,这肯定是设计不正确的迹象。最常见的错误就是我已经提到过的错误——继承与聚合相混淆。有时,这种情况的发生是由于错误的假设。那些。以车速表为例,有人认为只能通过测量距离和时间来测量速度,之后车速表成功地继承了尺子和钟表,从而成为了尺子和钟表,根据遗产。(我要求用速度计测量时间,通常他们都会用笑话来回答。或者他们根本不回答。)这里有什么错误?在前提下。事实上,车速表不测量时间。顺便说一句,还有距离。任何速度计中都存在的里程表是同一外壳中第二个设备的典型示例,即 聚合。不需要测量速度。它可以完全删除 - 这不会以任何方式影响速度测量。有时这样的错误是故意犯的。这更糟糕。“是的,我知道这样不对,但这对我来说更方便。” 这会导致什么?但事情是这样的:我们将从大炮和机枪继承坦克。这样比较方便。结果,坦克就变成了大炮和机枪。接下来我们将为飞机配备两挺机枪和一门大炮。我们得到什么?一架悬挂着三辆坦克武器的飞机!因为肯定有人在不理解的情况下将坦克用作机枪。完全根据继承层次结构。他是绝对正确的,因为设计这样一个等级制度的人犯了一个错误。
总的来说,我不太理解“这样对我来说更方便”的做法。像听者一样写起来很方便,说基本语法的人都是kazly。当然,我有些夸张,但主要思想仍然是——除了一时的便利之外,还有读写能力这样的东西。这个概念是根据大量人的经验来定义的。事实上,这就是英语中所说的“最佳实践”——最佳解决方案。通常,看似简单的解决方案在未来会带来很多问题。
当然,这个例子是非常夸张的,因此是荒谬的。然而,也有一些不太明显的案例却导致了灾难性的后果。通过从对象继承而不是聚合它,开发人员使任何人都能够直接使用父对象的功能。就其所暗示的一切而言。想象一下,您有一个与数据库一起使用的类 DBManager您可以使用DBManager - DataManager创建另一个类来处理您的数据。此类将执行数据控制、转换、附加操作等。一般来说,是业务层和基础层之间的一层。如果从 DBManager 继承 DataManager,那么使用它的任何人都可以直接访问数据库。因此,他将能够绕过控制、转换等执行任何行动。好吧,我们假设没有人想要造成故意伤害,直接行动就可以了。但!我们假设基础已经改变。我的意思是,一些控制或转换的原则已经改变。数据管理器已更改。但之前直接使用数据库的代码将继续工作。他们很可能不会记得他。结果将是这样一个类的错误,那些寻找它的人会变成灰色。任何人都不会想到他们会绕过 DataManager 使用数据库。顺便说一下,一个现实生活中的例子。花了很长时间才发现错误。最后,我再说一遍。 仅当存在“是”关系时才应使用继承。因为这是继承的本质——使用子类的对象作为基类的对象的能力。如果类之间没有“是”关系,就不应该有继承!从来没有并且在任何情况下都不会。更重要的是——因为它太方便了。原始来源链接:http://www.skipy.ru/philosophy/inheritance.html
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION