继承(面向对象的编程)

面向对象的编程遗产是基于目的或者班级在另一个对像上(基于原型的继承)或班级(基于班级的继承),保留类似执行。也定义为推导新类(子类)来自现有的超级班级或基类然后将它们组成一个课程层次结构。在大多数基于班级的对象的语言中,是通过继承创建的对象,即“子对象”,都会获取“父对象”的所有属性和行为,除了:构造函数,驱动器,超载操作员朋友功能基类。继承允许程序员创建基于现有类的类,[1]在维护相同的行为的同时指定新实现(实现接口),重复使用代码并通过公共类别独立扩展原始软件,接口。通过继承的对像或类的关系产生了定向无环图.

继承的班级称为子类其父级或超级班级的。术语“继承”既适用于基于类的基于类和原型的编程,但是在狭窄使用中,该术语保留用于基于类的编程(一个类继承另一个),基于原型编程中的相应技术被称为代表团(一个对象代表其他)。可以根据简单的网络接口参数预先定义类调整的继承模式,以保留语言间的兼容性。[2][3]

继承不应与亚型.[4][5]在某些语言中,继承和亚型同意,[a]而在其他方面则有所不同。通常,亚型建立了is-a关系,而继承仅重复实施并建立句法关系,而不一定是语义关系(继承不能确保行为亚型)。为了区分这些概念,有时将亚型称为接口继承(不承认类型变量的专业化也引起了亚型关系),而此处定义的继承被称为实施继承或者代码继承.[6]尽管如此,继承是建立亚型关系的常用机制。[7]

继承与对象组成,一个对象包含另一个对象(或一个类的对象包含另一类的对象);看关于继承的组成。构图实现a有个与亚型的IS-A关系相反。

历史

1966年托尼·霍尔(Tony Hoare)在记录上介绍了一些评论,特别是介绍了记录子类的想法,具有共同属性的记录类型,但被变体标签区分,并在变体中私有字段。[8]受此影响,1967年Ole-Johan Dahl克里斯汀·尼加德(Kristen Nygaard)提出了一种允许指定属于不同类但具有共同属性的对象的设计。共同的特性是在超类中收集的,每个超类本身可能具有超类。因此,子类的值是复合对象,由属于各种超级类的一些前缀部分以及属于子类的主要部分组成。这些部分都被连接在一起。[9]复合对象的属性将通过点表示法访问。这个想法首先在模拟67编程语言。[10]然后这个想法传播到短暂聊天C ++爪哇Python和许多其他语言。

类型

单个继承
多元继承

基于范式和特定语言,有多种类型的继承。[11]

单个继承
子类继承一个超类的功能。一个类获取另一类的属性。
多元继承
一个类可以拥有一个以上的超类并继承所有父类的功能。

“多重继承 ...广泛认为很难有效地实施。例如,在他的书中的一本关于C ++的摘要中目标c布拉德·考克斯实际声称,将多个继承添加到C ++是不可能的。因此,多重继承似乎更像是一个挑战。由于我早在1982年就考虑过多种继承,并在1984年发现了一种简单有效的实施技术,因此我无法抵抗挑战。我怀疑这是唯一影响事件序列的唯一情况。”[12]

多级继承
从另一个子类继承了一个子类。并不少见的是,类是从另一个派生类派生的,如图“多级继承”所示。
多级继承
班上一个用作基类为了派生的类B,这又用作基类为了派生的类C。班上B被称为中间的基类,因为它为继承提供了链接一个C。连锁,链条ABC被称为继承路径.
具有多级继承的派生类如下:
班级一个{...};//基类班级B上市一个{...};// b源自a班级C上市B{...};// c源自B
此过程可以扩展到任意数量的级别。
分层继承
在这里,一个班级用作超过一个子类的超级类(基类)。例如,父类A可以具有两个子类B和C。B和C的父类都是A,但是B和C是两个单独的子类。
混合继承
混合继承是在发生上述两种或多种继承的混合物时。一个例子是当A类具有一个子类B时,该子类B具有两个子类C和D。这是多级继承和层次继承的混合物。

子类和超类

子类派生的类继承课, 或者儿童课模块化的继承一个或多个的导数类来自一个或多个其他类的实体(称为超类基础类, 或者父类)。班级继承的语义因语言而异,但通常子类自动继承实例变量成员功能它的超类。

定义派生类的一般形式是:[13]

班级子类能见度超类{//子类成员};
  • 结肠表明子类从超类继承。可见性是可选的,如果存在,则可以是私人的或者上市。默认可见性是私人的。可见性指定基类的功能是私人得出或者公开派生.

某些语言还支持其他结构的继承。例如,在埃菲尔合同该定义班级规范也是继承人继承的。超类建立了一个常见的接口和基础功能,专门的子类可以继承,修改和补充。考虑子类继承的软件重复使用在子类中。对类的实例的引用实际上可能是指其子类之一。所引用的对象的实际类是无法预测的编译时间。统一接口用于调用许多不同类的对象的成员函数。子类可以用全新的功能替换超级类功能,必须共享相同的功能方法签名.

不可分类的类

在某些语言中,可以将课堂宣布为不可分类通过将某些类修饰符添加到类声明中。示例包括最后关键字in爪哇C ++ 11向前或密封C#中的关键字。在班级关键字和类标识符声明。这种不可分类的类限制了可重复使用,尤其是当开发人员只能访问预编译时二进制并不是源代码.

一个不可分类的类没有子类,因此可以轻松地推导编译时间引用或指示对该类的对象实际上是参考该类的实例,而不是子类的实例(它们不存在)或超类的实例(升级参考类型违反了类型系统)。由于要引用的对象的确切类型是在执行之前已知的,所以早期结合(也被称为静态调度)可以代替较晚的绑定(也被称为动态调度),需要一个或多个虚拟方法表查找取决于是否是否多元继承或只有单个继承在使用的编程语言中得到支持。

非基金方法

正如类可能是不可分类的一样,方法声明可能包含方法修饰符,以防止该方法被覆盖(即,用相同名称和类型签名在子类中替换为新功能)。一种私人的方法是无法克服的,仅仅是因为除了类以外,其他类是不可通过的类成员函数(尽管对于C ++是不正确的)。一种最后Java中的方法密封C#或A中的方法冷冻Eiffel中的功能不能被覆盖。

虚拟方法

如果超类方法是虚拟方法,那么超类方法的调用将是动态派遣。某些语言要求将方法特别声明为虚拟(例如C ++),而在其他方法中,所有方法都是虚拟的(例如Java)。非虚拟方法的调用将始终静态派遣(即,在编译时确定函数调用的地址)。静态调度比动态调度更快,并且允许优化内联扩展.

继承成员的可见性

下表显示了使用C ++建立的术语,哪些变量和函数取决于得出类时给出的可见性。[14]

基类可见性派生的类可见性
私人推导受保护的推导公共推导
  • 私人→
  • 受保护→
  • 公共→
  • 没有继承
  • 私人的
  • 私人的
  • 没有继承
  • 受保护
  • 受保护
  • 没有继承
  • 受保护
  • 上市

申请

继承用于彼此共同汇总两个或多个类。

覆盖

方法覆盖的插图

许多面向对象的编程语言允许类或对象替换其继承的方面的实现(通常是一种行为)。这个过程称为覆盖。覆盖介绍了一个复杂性:哪个版本的行为是继承类使用的实例(是其自身类别的一部分),还是来自父(基本)类的类别?答案在编程语言之间有所不同,某些语言提供了表明不覆盖特定行为的能力,并且应按照基类的定义行为。例如,在C#中,只有在子类中标记为虚拟,抽像或覆盖修饰符的基本方法或属性,而在诸如Java之类的编程语言中,可以调用不同的方法来覆盖其他方法。[15]替代品是隐藏继承的代码。

代码重复使用

实施继承是子类的机制重复使用基础类中的代码。默认情况下,子类保留了基类的所有操作,但是子类可能覆盖某些或所有操作,用自己的基础阶级实施替换。

在以下python示例中,子类SquareSumComputer立方体计算机覆盖转换()基类的方法sumcomputer。基类包括操作以计算正方形在两个整数之间。除了将数字转换为正方形的操作外,子类重新使用基类的所有功能正方形立方体分别。因此,子类计算两个整数之间的正方形/立方体的总和。

以下是Python的示例。

班级sumcomputer防守__在里面__自己一个b):自己.一个=一个自己.b=b防守转换自己x):增加NotimplementedError防守输入自己):返回范围自己.一个自己.b防守计算自己):返回自己.转换价值为了价值自己.输入())班级SquareSumComputersumcomputer):防守转换自己x):返回x*x班级立方体计算机sumcomputer):防守转换自己x):返回x*x*x

在大多数季度中,用于代码再利用的唯一目的的班级继承已失利。主要问题是实施继承不提供任何保证多态性替代性 - 重复使用类的实例不一定被替换为继承类的实例。替代技术,明确代表团,需要更多的编程工作,但避免了替代性问题。在C ++中,私有继承可以用作实施继承没有替代性。公共继承代表“ IS-A”关系,代表团代表“ has-a”关系,而私人(和受保护的)继承可以被视为“一种”关系。[16]

继承的另一个频繁使用是确保类保持某种共同的界面;也就是说,他们实现了相同的方法。母类可以是在子类中实施的实施操作和操作的组合。通常,超类型和亚型之间没有接口变化 - 孩子实现所描述的行为而不是其母体类别。[17]

继承与亚型

继承类似于但与亚型不同。[4]亚型使给定类型可以代替另一种类型或抽象,据说可以建立一个is-a亚型与某些现有抽象之间的关系,无论是隐式还是显式,取决于语言支持。该关系可以通过在支持继承作为亚型机制的语言中明确表示。例如,以下C ++代码在类之间建立了明确的继承关系B一个, 在哪里B是子类和亚型一个,可以用作一个无论何处B指定(通过引用,指针或对象本身)。

班级一个{上市空白dosomethinglike()const{}};班级B上市一个{上市空白Dosomethingblike()const{}};空白Useanaconst一个&一个{一个.dosomethinglike();}空白一些(){BbUseanab);// b可以代替A。}

在不支持继承的编程语言中亚型机制,与之间的关系相比类型。继承,即使在支持继承作为亚型机制的编程语言中,也不一定需要行为亚型。完全有可能在预期的上下文中使用其对象的行为会不正确地得出一个类。看到Liskov替代原则.[18](相比内涵/表示。)在某些OOP语言中,代码重用和亚型的概念重合,因为声明子类型的唯一方法是定义一个继承另一个实现另一个的新类。

设计约束

在设计程序中广泛使用继承会施加某些约束。

例如,考虑课程其中包含一个人的姓名,出生日期,地址和电话号码。我们可以定义一个子类学生其中包含该人的平均成绩和上课,还有另一个子类员工其中包含该人的工作标题,雇主和薪水。

在定义这种继承层次结构时,我们已经定义了某些限制,并不是所有的限制:

单身
使用单个继承,一个子类只能从一个超类继承。继续上面给出的示例,一个对象可以是学生或一个员工,但不是两者。使用多个继承部分解决了这个问题,因为然后可以定义一个学生雇员从两者继承的类学生员工。但是,在大多数实施中,它仍然只能从每个超级类别继承一次,因此不支持学生有两个工作或参加两个机构的案例。埃菲尔(Eiffel)可用的继承模型通过支持重复继承.
静止的
对象的继承层次结构已固定在实例化当选择对象的类型并且不会随时间变化时。例如,继承图不允许学生对象成为一个员工在保留其状态的同时对象超类。(但是,可以通过装饰器图案[19]
能见度
每当客户端代码可以访问对象时,它通常都可以访问所有对象的超类数据。即使尚未公开宣布超级阶级,客户仍然可以投掷其超类型的对象。例如,没有办法给函数一个指针学生的平均成绩和成绩单,也没有使该功能访问学生的所有个人数据超类。许多现代语言,包括C ++和Java,都提供了“受保护的”访问修饰符,该修饰符允许子类访问数据,而无需在继承链之外的任何代码访问它。

复合重用原理是继承的替代方案。该技术通过将行为与小学等级结构分开,并按照任何业务领域类要求的特定行为类别分开,支持多态性和代码重复使用。这种方法通过允许在运行时进行行为修改并允许一个类实现自助餐风格的行为,而不是仅限于其祖先类的行为,从而避免了类层次结构的静态性质。

问题和替代方案

从至少1990年代起,实施继承是有争议的,以对象为导向的编程的理论家。其中的作者设计模式,他们提倡界面继承,并偏爱构图而不是继承。例如,装饰器图案(如前所述以上已经提出了),以克服阶级之间继承的静态性质。作为对同一问题的更基本解决方案,面向角色的编程引入了独特的关系,由...演奏,将继承和构图的属性结合到一个新概念中。

根据艾伦·霍鲁布(Allen Holub),实施继承的主要问题是它引入了不必要的耦合“脆弱的基类问题”[6]对基类实施的修改可能会导致子类的无意行为变化。使用接口会避免此问题,因为没有共享实现,只有API。[19]说明这一点的另一种方式是“继承破裂封装”。[20]问题在开放的对象系统中明显表面构架,预计客户端代码将从系统提供的类中继承,然后在其算法中替换为系统类。[6]

据报导,Java发明家詹姆斯·高斯林(James Gosling)反对实施继承的说法,他说如果他要重新设计Java,他将不包括它。[19]从1990年出现的语言设计将其与亚型的继承(接口继承)出现。[21]一个现代的例子是编程语言。

复杂的继承或在不够成熟的设计中使用的继承可能导致溜溜球问题。当继承在1990年代后期用作结构程序的主要方法时,随着系统功能的增长,开发人员倾向于将代码分解为更多的继承层。如果开发团队将继承的多层继承与单个责任原则结合在一起,则导致许多非常薄的代码层,许多层仅由1或2行实际代码组成。太多的层使调试成为一个重大挑战,因为很难确定需要调试哪个层。

继承的另一个问题是必须在代码中定义子类,这意味着程序用户不能在运行时添加新的子类。其他设计模式(例如实体 - 组件 - 系统)允许程序用户在运行时定义实体的变化。

也可以看看

笔记

  1. ^通常只有在基于静态的类的OO语言中才是正确的,例如C ++C#,Java,然后Scala.

参考

  1. ^约翰逊,拉尔夫(1991年8月26日)。“设计可重复使用的课程”(PDF).www.cse.msu.edu.
  2. ^Madsen,OL(1989)。“虚拟类:面向对象的编程中的强大机制”。关于面向对象的编程系统,语言和应用程序的会议程序.
  3. ^戴维斯,土耳其人(2021)。高级方法和计算机视觉中的深度学习。Elsevier科学。第179–342页。
  4. ^一个b库克,威廉·R。希尔,沃尔特;Canning,Peter S.(1990)。继承不是亚型。第17届ACM SIGPLAN-SIGACT关于编程语言原则(POPL)的会议论文集。pp。125–135。Citeseerx 10.1.1.102.8635.doi10.1145/96709.96721.ISBN 0-89791-343-4.
  5. ^Cardelli,Luca(1993)。类型的编程(技术报告)。数字设备公司。 p。 32–33。 SRC研究报告45。
  6. ^一个bcMikhajlov,Leonid;Sekerinski,Emil(1998)。对脆弱基类问题的研究(PDF)。第12届欧洲面向对象编程会议(ECOP)会议论文集。计算机科学的讲义。卷。1445.施普林格。pp。355–382。doi10.1007/bfb0054099.ISBN 978-3-540-64737-9.
  7. ^温度,伊万;杨,香港;Noble,James(2013)。程序员在Java中的继承作用(PDF)。ECOUP 2013-面向对象的编程。计算机科学的讲义。卷。7920.施普林格。pp。577–601。doi10.1007/978-3-642-39038-8_24.ISBN 978-3-642-39038-8.
  8. ^Hoare,C。A. R.(1966)。记录处理(PDF)(技术报告)。pp。15–16。
  9. ^达尔(Ole-Johan);克里斯汀·尼加德(Nygaard)(1967年5月)。班级和子类声明(PDF)。IFIP仿真语言工作会议。奥斯陆:挪威计算中心。
  10. ^Dahl,Ole-Johan(2004)。“对象取向的诞生:模拟语言”(PDF).从对象取向到形式方法.2635:15–25。doi10.1007/978-3-540-39993-3_3.
  11. ^“ C ++继承”.www.cs.nmsu.edu.
  12. ^斯特鲁斯特鲁普,比贾恩(1994)。C ++的设计和演变。皮尔逊。 p。 417。ISBN 9780135229477.
  13. ^史基尔特,赫伯特(2003)。完整的参考C ++。塔塔·麦格劳希尔(Tata McGrawhill)。 p。417.ISBN 978-0-07-053246-5.
  14. ^Balagurusamy,E。(2010)。用C ++面向对象的编程。塔塔·麦格劳希尔(Tata McGrawhill)。 p。 213。ISBN 978-0-07-066907-9.
  15. ^覆盖(C#参考)
  16. ^“ GOTW#60:异常安全类设计,第2部分:继承”。 gotw.ca。检索2012-08-15.
  17. ^Venugopal,K.R。; Rajkumar Buyya(2013)。掌握C ++。Tata McGrawhill Education Private Limited。p。609。ISBN 9781259029943.
  18. ^米切尔,约翰(2002)。以面向对象的语言中的“ 10”概念””。编程语言的概念。剑桥大学出版社。 p。287.ISBN 978-0-521-78098-8.
  19. ^一个bcHolub,艾伦(2003年8月1日)。“为什么扩展是邪恶的”。检索3月10日2015.
  20. ^Seiter,Linda M。;帕尔伯格,詹斯;Lieberherr,Karl J.(1996)。“使用上下文关系的对象行为的演变”.ACM Sigsoft软件工程笔记.21(6):46。Citeseerx 10.1.1.36.5053.doi10.1145/250707.239108.
  21. ^美国,皮埃尔(1991)。通过行为亚型设计一种面向对象的编程语言。REX学校/研讨会关于面向对象的语言的基础。计算机科学的讲义。卷。489.第60–90页。doi10.1007/bfb0019440.ISBN 978-3-540-53931-5.

进一步阅读