Cid*_*ori 5 oop inheritance encapsulation late-binding dynamic-dispatch
正如"四人帮"在" 设计模式 "中所述:" 人们经常说 '继承打破了封装'",在"面向对象编程语言中的封装和继承"中解释Snyder.
然而,每次我读" 继承断裂包封 ",这要求背后的原因要么依稀解释的,或与所述的一个例子说明脆基类的问题.
在阅读论文时,我感觉真正打破封装的唯一继承属性是downcalls,这是开放递归(动态调度打开this)允许的特性,并定义为"当超类方法调用在子类中重写的方法时",根据Ruby&Leavens在"安全地创建正确的子类而不看超类代码"中的说法.
此外,根据Aldrich在"选择性开放递归:脆弱基类问题的解决方案"中的说法,开放递归显然是导致脆弱基类问题的原因.
因此,如果脆弱基类问题是"继承打破封装"的唯一原因,那么可以更清楚地说,下调会破坏封装.由于存在一些解决方案以避免在使用继承时进行下调,因此继承本身并不真正涉及破坏封装.此外,四人帮提出的取消继承的委托模式也可以允许开放递归和下调,因为委托人的context(this)由委托使用(这可能导致一种脆弱的委托类问题).
因此,我的问题是:
脆弱的基类问题是否被称为"继承打破封装"的唯一原因?
你的问题很有趣。我倾向于同意你的观点:继承的主要问题是父级和子级之间的耦合。这种关系阻碍了父类在不破坏子类的情况下发展的能力。
\n\n我的理解是,您的意思是问脆弱的基类问题是否是违反“继承破坏封装”原则的唯一表现,对吧?
\n\n太长了;
\n\n我相信(像你一样),如果我们在使用继承时打破封装,那么,这种强耦合问题的表现肯定会明显地体现在父类的脆弱性上,当它的子类发生变化时,它会破坏它的子类。
\n\n所以,从这个意义上或解释来看,我认为你可能是对的。
\n\n相反的情况则不一定,即,拥有脆弱的基类并不一定意味着您违反了继承封装规则。
\n\n耦合是问题所在
\n\n我仔细阅读了您提供的同一参考书目,这段摘录可以为这个问题提供一些线索。
\n\n从设计模式:可重用的面向对象软件的元素:
\n\n\n\n\n\xe2\x80\x9c 父类通常定义其子类的至少一部分\xe2\x80\x99\n 物理表示。因为继承将子类暴露给其父类\xe2\x80\x99s 实现的详细信息,所以 \xe2\x80\x99s 经常说\n \xe2\x80\x9cin继承破坏了封装\xe2\x80\x9d [Sny86]。 \xe2\x80\x9d
\n
所以,GoF 似乎在这里暗示根本问题是耦合问题。显然,脆弱的基类是耦合问题的表现,因此您可能仍然会遇到一些问题
\n\n一个对象通常有一个公共接口,向世界公开它可以做什么的契约。对象的公共接口中的这些方法必须满足该对象提供的服务的几个前提条件、不变量和后置条件。因此,对象的用户根据该契约与其进行交互。
\n\n对象的用户不需要知道合约的实现细节。因此,如果我们违反了该合同,该对象的所有用户都会受到影响。
\n\n这种对对象公共接口的依赖是一种耦合形式,当存在耦合时,就会存在一种需要改变的脆弱性。
\n\n例如,驾驶员不需要知道汽车中的液压方向盘系统如何工作,但他们仍然可以驾驶轿车或 SUV,就好像它们是同一件事一样,因为他们了解方向盘的抽象和管理其公共接口的合同。然而,如果我们改变方向盘,一般来说,就像叉车一样工作,那么可能每个人都会撞毁他们的汽车(我们破坏了方向盘公共接口)。
\n\n因此,预计类的公共接口相当稳定,并且预计那里的任何更改肯定会破坏其客户端的功能。
\n\n当子类不能仅仅依赖于对象的公共接口时,当它们需要额外了解如何实现其父类以提供功能性子实现时(这就是为什么委托是一个很好的替代方案),继承会破坏封装。在某些情况下,因为委托仅取决于对象的公共接口)。
\n\n状态耦合
\n\n这以不同的方式表现出来,例如,如果您可以直接访问父类中的状态变量,则可能会破坏封装(在您共享的 Snyder 文章中也提到过)。
\n\n来自面向对象编程语言中的封装和继承:
\n\n\n\n\n“为了保留封装的全部优点,类定义的外部接口不应包含实例变量”
\n
在具有可访问性修饰符的静态语言(例如,Java 或 C#)中,如果我们从父类公开非私有字段,则可能会出现这种违规行为。在没有可访问性修饰符的动态语言中,当子类实现者访问仅供父类私有使用的字段(例如,_fieldPython 中的 a)时,这种违规就会显现出来。
您是否认为此字段访问问题是脆弱基类问题的一部分?在我看来,这种形式的继承耦合并不一定是在您分享的著名文章的脆弱基类问题的思想中。
\n\n受保护的接口耦合
\n\n这体现的另一种方式是,现在父类可能需要公开除公共接口之外的新接口,但仅用于继承目的:受保护的接口。因此,它可能需要公开一组“受保护”或“特权”方法,这些方法可以访问通常不会在为常规对象用户提供的公共接口中公开的其他详细信息。
\n\n这显然是必要的,因为子类需要这些附加细节才能为父类提供一些扩展功能或更改行为的合理实现。
\n\n现在,父类还需要确保这个受保护的接口非常稳定,因为任何更改都会破坏继承类,就像其公共接口的更改会破坏常规类用户一样。
\n\n此时,我们陷入了一种强烈的耦合形式,这种耦合形式可能会阻止父类在未来发展,因为它可能会在子类中引起潜在的问题。
\n\n现在,请注意,耦合和封装的破坏在设计时表现出来,因此,这里也引入了基类的脆弱性,即使这种耦合问题从未在代码中显现出来,因为我们从未导致父类发生更改。
\n\n所以,我的解释是,继承引入的耦合导致封装的破坏,进而导致您所描述的脆弱基类问题。
\n\n在某种程度上,您的问题似乎暗示了一个因果链,您似乎认为脆弱的基类问题是破坏继承的原因,但就我而言,我相信情况恰恰相反:父级和子级之间的耦合破坏了封装,而这种高耦合程度在设计中表现为脆弱的基类问题。
\n\n易碎且无封装破损
\n\n话虽这么说,我们现在有一个问题,我们可以在不破坏封装的情况下拥有一个脆弱的基类吗?
\n\n我相信我们会的。子类只能完全依赖于父类的公共接口。例如,子类可能提供了一个全新的方法,该方法不是继承的,并且它不是父类的公共或受保护接口的一部分。
\n\n然后有一天,我们在父类中添加一个新方法,该方法的签名与我们很久以前在子类中添加的方法完全相同,但其意图完全不同。
\n\n现在我们已经破坏了一些东西,因为该对象的用户希望新方法的行为如父类接口中所述,而不是如子类中实现的那样。
\n\n这个错误可能很难被发现。在某些语言中,这可能会导致子类失败;在其他情况下,可以假设子覆盖版本是使用权。
\n\n此问题的一个变体是,如果父类中定义的新方法具有与子类中定义的完全相同的方法不同的可访问性修饰符,作为两个类独立演化的一部分。
\n\n无论如何,这些差异并不一定会破坏封装,但由于继承引入的耦合,它们确实使父/子关系变得脆弱,对吗?
\n\n换句话说,尽管在这种情况下父类和子类之间的封装很好,但父类是脆弱的。
\n\n通过继承打破封装确实会导致脆弱的基类,但据我所知,脆弱的基类并不一定意味着继承关系中存在封装问题。
\n您只需要小心何时以及如何使用继承即可。
在许多语言中,默认情况下您可以从基类继承(根据语言规则),除非基类的开发人员采取措施阻止它。但仅仅因为可以继承,并不意味着基类被设计为在子类化时可以正常工作。
而且封装目前还不起作用。如果我创建一个类,所有内容都被很好地封装,您在我不知情的情况下创建了一个子类,而我在不知情的情况下对我的类进行了更改,从而破坏了您的类。这可能会发生。
因此,您需要一个旨在进行子类化的基类。