脆弱的基类是“继承破坏封装”的唯一原因吗?

时间:2018-06-23 14:13:40

标签: oop inheritance encapsulation late-binding dynamic-dispatch

正如“四人帮”在“ Design Patterns”中指出的那样:it's often said that 'inheritance breaks encapsulation' ,用“面向对象编程语言中的封装和继承”。

但是,每次我读到“ inheritance breaks encapsulation”时,都会模糊地解释这一主张的原因, 或举例说明Fragile Base Class问题。

在阅读论文时,我感到,唯一真正破坏封装的继承属性是 downcalls open recursiondynamic dispatch在{{1 }}),并定义为“当超类方法调用在子类中被重写的方法时” ,根据Ruby和Leavens在“安全地创建正确的子类而不会看到超类代码”中的介绍。
此外,根据Aldrich在“选择性开放递归:脆弱的基类问题的解决方案”中的说法,开放递归显然是Fragile Base Class问题的原因。

因此,如果易碎的基类问题是“继承破坏封装” 的唯一原因,那么说 downcalling破坏封装会更清楚。由于存在一些避免在仍然使用继承的情况下避免向下调用的解决方案,因此继承本身并没有真正涉及破坏封装。此外,由于由委托人使用委托人的上下文(this),四人帮派提出的摆脱继承的委托模式也可以允许公开递归和向下调用(这可能导致一种脆弱的委托)类问题)。

因此,我的问题是:
易碎的基类问题是为什么说“继承破坏封装” 的唯一原因吗?

3 个答案:

答案 0 :(得分:3)

这是一个非常有趣的问题。在某种意义上,我倾向于同意您的观点,即继承的主要问题是父母与子女之间的联系,以及这种关系如何阻碍父母阶级在不破坏子女的情况下发展的能力。

我要解释的是,您的意思是问脆弱的基类问题是否是违反“继承破坏封装”原理的唯一体现,对吗?

TLDR;

我相信(像您一样),如果我们在使用继承时破坏封装,那么可以肯定的是,这种强耦合问题的表现将是父类的脆弱性,当它改变时,父类会破坏其子代。

因此,从这种意义上或解释上,我认为您可能是正确的。

相反的情况不一定成立,即拥有脆弱的基类并不一定意味着您违反了继承封装规则。

耦合是问题

我仔细阅读了您提供的相同书目,此摘录可为您提供一些启示。

从设计模式:可重用的面向对象软件的元素:

  

“父类通常会定义其子类的至少一部分”   物理表示。因为继承将子类公开给   关于其母公司实施的详细信息,通常会说   “继承破坏封装” [Sny86]。

因此,似乎GoF在此暗示基本问题是耦合问题。显然,脆弱的基类是耦合问题的体现,因此您可能仍然会遇到某些问题

对象通常具有一个公共接口,该接口向世界公开其可以做的事情的契约。对象的公共接口中的这​​些方法必须满足对象提供的服务的许多前提条件,不变性和后置条件。并且对象的用户根据该合同与其进行交互。

对象的用户不必知道合同的实施细节。而且,如果我们违反了合同,所有对象的用户都会遭受损失。

对对象公共接口的这种依赖是一种耦合形式,当存在耦合时,就存在一种脆弱性来改变。

例如,驾驶员不需要知道液压方向盘系统在其汽车中的工作原理,但他们仍然可以驾驶轿车或SUV,就像他们是同一个人一样。因为他们了解方向盘的抽象以及控制其公共接口的合同。但是,如果我们通常改变方向盘使其像叉车一样工作,那么可能每个人都会撞车(我们打破了方向盘公共界面)。

因此,可以预期类的公共接口非常稳定,并且可以预期那里的任何更改肯定会破坏其客户端的功能。

当子类不能仅仅依赖于对象的公共接口,子类需要更多有关如何实现其父类的知识以提供功能性子实现时,继承会破坏封装(这就是为什么委托是一个很好的选择)在某些情况下,因为委托仅取决于对象的公共接口。

状态耦合

这以不同的方式表现出来,例如,如果您可以直接访问父类中的状态变量(也可以在共享的Snyder文章中提到),则可能破坏封装。

来自面向对象编程语言的封装和继承:

  

“为了保留封装的全部好处,外部   类定义的接口不应包含实例   变量”

在具有可访问性修饰符的静态语言(例如Java或C#)中,如果我们公开父类的非私有字段,则可能会出现这种冲突。在没有可访问性修饰符的动态语言中,当子类实现者访问仅用于父类的私有字段(例如Python中的_field)时,就会出现这种冲突。

您是否认为此字段访问问题是脆弱的基类问题的一部分?在我看来,您共享的著名文章的脆弱的基类问题的想法没有不必要地涵盖这种形式的继承耦合。

受保护的接口耦合

这体现的另一种方式是,现在父类可能需要公开除公共接口以外的新接口,但仅用于继承目的:受保护的接口。因此,可能需要公开一组“受保护”或“特权”方法,这些方法可以访问通常在常规对象用户提供的公共界面中未公开的其他详细信息。

这显然是必要的,因为子类需要这些附加细节,以便能够以某些扩展功能或更改的行为提供对父类的明智实现。

现在,父类还需要确保此受保护的接口非常稳定,因为那里的任何更改都会破坏继承类,因为其公共接口中的更改将破坏常规的类用户。

在这一点上,我们产生了一种强烈的耦合形式,由于其可能对子类造成潜在的问题,这种耦合可能阻止父类在将来发展。

现在,请注意,封装的耦合和破坏在设计时就已经表现出来了,因此即使在代码中也从未表现出这种耦合问题,因为我们从未在父类中引起任何变化,所以基类的脆弱性也在此引入。 / p>

所以,我的解释是,由继承引入的耦合导致封装的破坏,进而导致您所描述的脆弱的基类问题。

从某种程度上来说,您的问题似乎暗示了一个因果关系链,其中似乎表明,脆弱的基类问题是破坏继承的问题,但在我的情况下,我认为这是相反的:父母与孩子之间的耦合破坏了封装这种高水平的耦合在设计中表现为脆弱的基类问题。

不破坏封装的脆弱性

话虽如此,我们现在有一个问题,我们可以拥有一个脆弱的基类而不破坏封装吗?

我相信我们会的。子类可能仅完全取决于父类的公共接口。例如,子类可能提供了一个全新的方法,该方法未继承,也不属于父类的公共或受保护接口的一部分。

有一天,我们在父类中添加了一个新方法,该方法具有与很久以前在子类中添加的签名相同的签名,但其意图完全不同。

现在我们已经打破了一些东西,因为此对象的用户希望新方法的行为与父类接口中说明的一样,而不是在子类中实现。

该错误可能很难捕获。在某些语言中,这可能会导致子类失败,而在其他语言中,则可以假定子重写版本是使用的权利。

此问题的变体是,如果父类中定义的新方法与子类中定义的完全相同的方法具有不同的可访问性修饰符,则这是两个类的独立演变的一部分。

无论如何,这些差异并不一定会破坏封装,但是由于继承带来的耦合,它们确实使父子关系变得脆弱,对吗?

换句话说,尽管在这种情况下父类和子类之间的封装很好,但父类还是脆弱的。

使用继承破坏封装确实会导致脆弱的基类,但是就我所知,脆弱的基类不一定意味着隐含继承关系中的封装问题。

答案 1 :(得分:0)

您只需要注意何时以及如何使用继承。

在许多语言中,默认情况下可以从基类继承(根据语言规则),除非基类的开发人员已采取措施阻止它。但是仅仅因为您可以继承,并不意味着基类被设计为在子类化时可以正常工作。

并且封装暂时不起作用。如果我创建一个类,则所有内容都被很好地封装了,那么您将在我不知情的情况下创建一个子类,而我却不了解它,所以对我的类进行了更改,这会破坏您的工作。那有可能发生。

因此您需要一个设计的基类进行子类化。

答案 2 :(得分:0)

不。这不是这样说的唯一原因。据说这是因为,当不明智地使用继承时,您可能最终将类(基类和派生类)耦合为InheritanceBreaksEncapsulation

开放递归只是有关OO设计的一个特定技术问题。句子“继承破坏封装”只是一个更笼统的主张。

此外,不一定要完全填充完整的封装,这取决于您的需求和上下文。