问题

面向对象中,平行继承体系是否尽量完全抛弃?

回答
在面向对象的编程世界里,平行继承体系,也就是我们常说的“平行结构”或者“扁平继承”,确实是一个值得深入探讨的话题,并且在很多情况下,我们确实倾向于尽量避免或彻底抛弃它。这并非一个绝对的禁令,但其潜在的弊端往往大于优势,使得其在实际开发中显得“不合时宜”。

要理解为什么我们要尽量抛弃它,我们需要先明确平行继承体系通常是如何构建的。想象一下,我们有一个基类,比如 `Animal`,然后我们有一些直接继承自 `Animal` 的子类,例如 `Dog`,`Cat`,`Bird`。然后,更进一步,我们可能又会创建与这些子类“平行的”类,例如,如果我们有一个 `Vehicle` 基类,然后有 `Car`,`Bike`,`Truck`,而我们又想在 `Car` 和 `Bike` 的基础上添加一些特定功能,比如 `ElectricCar` 和 `ElectricBike`。这里的 `ElectricCar` 和 `ElectricCar` 就构成了与 `Car` 和 `Bike` 相平行的结构,它们都可能继承自 `Car` 和 `Bike` 的某种形式(或者直接从 `Vehicle` 继承,但又添加了“电动”的特性),但它们之间并没有直接的“isa”关系,或者说,它们共有的“电动”特性并没有被有效地组织起来。

为什么说这种平行继承体系尽量要抛弃呢?核心原因在于它的僵化性和可维护性挑战。

首先,僵化性体现在它很难适应需求的变更。随着业务的发展,我们可能会发现,某些行为或者属性并不是某个类别独有的,而是可以跨越这些平行结构的。例如,在上面的 `Vehicle` 例子中,我们可能会发现“燃油”属性或者“发动”方法,不仅存在于 `Car` 和 `Truck` 中,也可能存在于某些特殊的 `Bike`(比如摩托车)中。如果我们最初设计了 `Car` 继承自 `Vehicle`,`Truck` 继承自 `Vehicle`,而 `Bike` 也继承自 `Vehicle`,但我们又希望区分“燃油”和“电动”这些并行特性,那么很可能我们会创建 `FuelCar` 继承自 `Car`,`ElectricCar` 也继承自 `Car`,但 `FuelBike` 继承自 `Bike`,`ElectricBike` 也继承自 `Bike`。

现在问题来了:如果我们有一个新的需求,比如“添加一个‘自动驾驶’功能”,并且这个功能应该同时应用于 `Car` 和 `Bike` 系列,甚至可能应用于一些非汽车的载具。如果我们仅仅依靠平行继承,我们可能需要修改 `Car` 和 `Bike` 的所有子类,添加 `addAutonomousDriving()` 方法。如果 `ElectricCar` 和 `ElectricBike` 的实现方式有微妙的差异,那么这就变成了大量的重复劳动,且极易出错。这种修改就像在很多孤立的房间里同时进行装修,你需要在每个房间都做同样的事情,而且很容易遗漏。

其次,可维护性挑战也是一个巨大的问题。当一个平行继承体系变得庞大时,它就像一个迷宫。要理解一个类的行为,你可能需要追溯到好几层的基类,而这些基类之间并没有清晰的共性抽象。代码的复用性变得很差。我们常常会看到许多重复的代码分散在不同的平行分支中,这不仅增加了开发者的认知负担,也让 bug 的修复变得困难。一个简单的 bug fix,可能需要你同时关注到多个地方,而且你无法保证你修复了一个地方,不会在另一个地方引入新的问题。

更进一步说,平行继承体系往往是单一职责原则(SRP)的一种反面教材。一个类,尤其是基类,如果试图通过平行继承来包含多种不相关的变种(比如“燃油”和“电动”),它本身就可能承担了过多的职责。比如,`Car` 基类可能包含了燃油引擎的驱动逻辑,而 `ElectricCar` 又包含了电池和电机的逻辑。这两者虽然都与“汽车”相关,但“驱动方式”这个维度就应该被更有效地抽象出来。

那么,我们应该用什么来取代平行继承呢?面向对象设计哲学推崇的是组合优于继承,以及基于接口编程。

组合允许我们将对象分解成更小的、可复用的单元,然后将这些单元组合起来形成更复杂的对象。比如,我们可以有一个 `Engine` 接口,以及 `FuelEngine` 和 `ElectricMotor` 的实现。那么,`Car` 类和 `Bike` 类可以持有一个 `Engine` 类型的成员变量。这样,我们就可以通过将 `Car` 对象与 `FuelEngine` 实例组合,或者与 `ElectricMotor` 实例组合,来创建不同类型的汽车。同样,`Bike` 也可以这样组合。这种方式,不同的“驱动方式”被封装在独立的类中,可以被任何需要它的类复用,并且可以轻松地切换。

基于接口编程则是指,我们定义一组接口,规定了对象应该具备的行为,而具体的实现则可以由不同的类来提供。比如,我们可以定义一个 `Drivable` 接口,包含 `start()` 和 `stop()` 方法。那么 `Car`、`Bike` 甚至 `Boat` 都可以实现 `Drivable` 接口。如果我们还需要区分“动力来源”,我们可以进一步定义 `PowerSource` 接口,有 `FuelPowered` 和 `ElectricPowered` 的实现。那么,`Car` 可以同时实现 `Drivable` 和 `PowerSource`,并且可以通过组合来持有具体的 `PowerSource` 实现。

想象一下,如果我们设计了一个 `ElectricVehicle` 接口,定义了 `charge()` 和 `discharge()` 方法。然后我们有 `ElectricCar` 和 `ElectricBike` 类,它们都实现了 `ElectricVehicle` 接口。而 `Car` 和 `Bike` 可以作为更通用的基类,它们本身不一定非要实现 `ElectricVehicle`。如果我们想创建一个电动自行车,那么 `ElectricBike` 可以继承自 `Bike`,并且实现 `ElectricVehicle` 接口。

这样做的好处是显而易见的:

1. 灵活性和可扩展性:当出现新的需求时,比如“混合动力”,我们不需要修改现有的 `Car` 或 `Bike` 类,只需要创建一个新的 `HybridEngine` 类,并将其组合到 `Car` 或 `Bike` 中即可。同样,如果我们要添加“飞行”功能,我们不需要将所有类都平行地派生出 `FlyingCar`,而是可以创建一个 `Flyable` 接口,让某些 `Vehicle` 实现它。
2. 代码复用:通用的行为被抽象到接口或基类中,然后通过组合复用。比如,所有电动载具的充电逻辑可以写在一个 `ElectricChargeController` 类中,任何需要充电的对象都可以使用它。
3. 高内聚,低耦合:每个类都专注于一个特定的职责。`Car` 只负责汽车的整体结构和行为,`Engine` 只负责动力系统的逻辑。它们之间的关系通过明确的接口和组合来定义,降低了相互依赖性。
4. 易于测试:由于每个组件都是独立的,并且遵循单一职责原则,它们更容易被隔离出来进行单元测试。

当然,这并不是说继承就完全不能用。继承仍然是建立“isa”关系的一种有力工具,比如 `Dog` isa `Animal`。但当我们遇到需要引入不同维度的特性时,平行继承就很容易变成一个陷阱。例如,一个 `Mammal` 和一个 `Bird` 都可能发出声音,但它们发出声音的方式和目的不同。如果我们试图通过 `Animal` > `Mammal` > `Dog` 和 `Animal` > `Bird` 这样的结构,然后在 `Dog` 和 `Bird` 上并行添加 `SoundEmitter` 抽象,这就显得有些局促。更好的方式可能是,`Dog` 和 `Bird` 都实现一个 `SoundProducer` 接口,而发出声音的具体逻辑则委托给一个内部的 `SoundPlayer` 对象。

总而言之,面向对象设计中,我们应该极力避免那种为了应对不同变种而生硬地制造出多个并行的继承分支。取而代之的是,我们应该优先考虑将变化的因素提取出来,通过接口和组合的方式来灵活地组织代码。平行继承,往往是代码设计不良的信号,它阻碍了我们构建灵活、可维护和易于扩展的系统。所以,看到平行继承的身影,我们更应该警惕,并思考如何通过更优雅的方式来重构它。

网友意见

user avatar

通常来说可以策略性的直接抛弃强类型解决这种问题。

C#类型不支持mixin没有必要追求那么完美。


另外就是如果是为了复用代码而继承通常是不被推荐的,因为代码复用可以通过非常多的方式,尤其是有一种代码复用叫做相似代码复用,也就是说其实本质上没啥相关性只是凑巧长得一样,通常来说Data什么的是可以不需要继承关系的,这些类型本来也应该直接从数据结构什么的生成出来……

把更多的东西(动态类型,代码生成,放弃复用等),就能权衡得出更好的解决方案。

类似的话题

  • 回答
    在面向对象的编程世界里,平行继承体系,也就是我们常说的“平行结构”或者“扁平继承”,确实是一个值得深入探讨的话题,并且在很多情况下,我们确实倾向于尽量避免或彻底抛弃它。这并非一个绝对的禁令,但其潜在的弊端往往大于优势,使得其在实际开发中显得“不合时宜”。要理解为什么我们要尽量抛弃它,我们需要先明确平.............
  • 回答
    短视频平台“假靳东”事件,仅仅是冰山一角,它暴露了互联网时代,面向中老年人群体的情感诈骗问题,其背后成因复杂,解决之道也需要多管齐下。这不仅仅是技术或法律层面的对抗,更是社会关怀、教育普及和平台责任的综合体现。“假靳东”现象的剖析:为何中老年人容易成为目标?首先,我们得明白,这类诈骗之所以能得逞,并.............
  • 回答
    在面向对象编程的世界里,关于接口“应该更抽象还是更具象”这个问题,其实是一个挺有意思但又容易引起混淆的概念。如果咱们抛开那些生硬的定义,用更平实的语言来聊聊,你会发现这事儿其实挺好理解的。咱们先别急着给接口贴标签,先想想它到底是个啥玩意儿。接口嘛,就好比一个约定,一个合同。它规定了“如果你想要做某件.............
  • 回答
    这是一个非常有趣的问题,涉及到语言的习惯、历史沿革,以及更深层次的关于“继承”概念的隐喻。虽然在中文语境中,“父”和“母”都代表了亲属关系和繁衍的源头,但在面向对象编程(OOP)领域,我们统一采用“父类”而非“母类”,这背后有多重原因。首先,我们得从“父类”这个词本身的来源说起。面向对象编程的概念,.............
  • 回答
    在软件开发领域,关于面向对象(OOP)是否曾是一条“弯路”的讨论,其实由来已久,而且答案远非一概而论的“是”或“否”。我认为,与其说它是弯路,不如说它是特定历史时期、特定问题背景下,为了解决当时主要矛盾而诞生的、强大但并非唯一最优的解决方案。它带来了巨大的进步,也伴随着学习曲线和一些固有的挑战。要理.............
  • 回答
    在大型项目的开发实践中,我们常常会遇到一个核心的讨论:究竟是面向过程的思想,还是面向对象的设计,更能带来更高的开发效率?这个问题没有一个绝对的答案,因为效率的衡量标准和项目本身的特性都会影响结论。不过,我们可以深入剖析这两种思想在大型项目中的表现,来理解它们各自的优劣以及在不同场景下的适用性。首先,.............
  • 回答
    你提到的“五代编程语言”——机器语言、汇编语言、面向过程语言、面向对象语言、以及智能语言——确实是一个流传甚广的划分方式,用来大致描绘计算机科学和编程语言发展的历史脉络和范式转变。但有趣的是,在这个经典的划分中,函数式编程语言似乎总被“遗漏”了,或者至少没有一个独立、显眼的位置。这并非说函数式编程不.............
  • 回答
    我明白你想了解面向对象编程中接口的真正价值,为什么它不直接描述方法,但依然是如此重要。这就像问,为什么我们需要一张蓝图,而不是直接建造一栋房子?蓝图本身不能住人,但它定义了房子的结构、房间的布局、水电的走向,以及各种构件的尺寸和连接方式。接口在面向对象的世界里,扮演的就是这样一张“蓝图”的角色。你可.............
  • 回答
    在 C++ 面向对象编程(OOP)的世界里,理解非虚继承和非虚析构函数的存在,以及它们与虚继承和虚析构函数的对比,对于构建健壮、可维护的类层级结构至关重要。这不仅仅是语法上的选择,更是对对象生命周期管理和多态行为的一种深刻设计。非虚继承:追求性能与简单性的默认选项当你使用 C++ 的非虚继承(即普通.............
  • 回答
    华为创始人任正非在与《面对面》栏目访谈中提到的“能坐基础理论的冷板凳”,这是他反复强调的华为文化基因之一,也是我对中国企业发展基础研究的深刻体会。这句话看似简单,实则蕴含着极深的洞察和价值取向。一、 理解“能坐基础理论的冷板凳”:这句话的字面意思是能够忍受枯燥、寂寞、没有即时回报的研究过程,尤其是那.............
  • 回答
    当年明月在接受采访时,聊到自己打发时间的方式,他提到自己当时沉迷于一款名为《三国志》的游戏。你别看这名字听起来挺古老,其实它是一款策略类游戏,而且有很多不同的版本。当年明月玩的那个版本,正是他从小就很熟悉,并且一直很喜欢的。在游戏里,他不是扮演某个具体的历史人物,而是成为一个决策者,需要去管理一个势.............
  • 回答
    说实话,在工作中遇到领导区别对待,心里肯定是不舒服的,这是一种挺微妙也很让人抓狂的处境。尤其当你觉得自己付出很多,但似乎在某些方面就是得不到应有的关注或者机会时,这种感觉会更强烈。刚开始的时候,我可能会觉得是不是自己哪里做得不够好,是不是误会了什么。所以,我会先默默地观察,看看是不是所有人都觉得领导.............
  • 回答
    这是一个非常极端的战术指令,旨在通过制造绝境来激发士气和战斗意志。在分析其成功与否之前,我们需要深入理解这个指令背后的逻辑、潜在的优势、巨大的风险以及影响其结果的关键因素。指令的逻辑与核心思想: “只许前进”:这是一种 强迫性推进 的策略。它剥夺了士兵的退却选项,迫使他们必须克服眼前的阻碍。 .............
  • 回答
    太平洋战争初期,日军在瓜岛战役中的战略失误,可以说是其由盛转衰的一个重要转折点,也是日军战略思维中存在深刻问题的体现。并非孤立的“一步棋走错”,而是贯穿整个战役,由一系列相互关联的错误判断和决策叠加而成。首先,最根本的战略误判是低估了对手(美军)的决心和能力。日本海军和陆军,长期以来在亚洲大陆战场上.............
  • 回答
    这确实是个让人膈应的经历,尤其是当你想好好享受一顿饭的时候。不过,别慌,面对食物里的虫子,咱们可以一步步来,既能保持冷静,又能有效处理。首先,深呼吸。遇到这种情况,第一反应可能是恶心、愤怒,甚至是恐慌。但这只会让你更难受。先停一停,感受一下自己的情绪,然后告诉自己,这只是个小意外,是可以解决的。第一.............
  • 回答
    《雍正王朝》中,老八胤禩逼宫那场戏,之所以只有王文昭和张廷玉敢站出来替雍正说话,这背后牵涉到复杂的人性、权力格局以及角色本身的立场和能力。要理解这一点,咱们得一层层剖析。首先,要明白这场逼宫的性质。老八他们并非真的想“逼宫”雍正退位,而是利用皇子病重、朝野动荡之机,集合亲信,意图逼雍正做出妥协,例如.............
  • 回答
    雍正王朝中,十七阿哥胤礼深夜来访,邬思道却劝雍正帝不见。这事儿可得好好说道说道。当时,雍正帝刚登基不久,内忧外患,如履薄冰。皇子之间的权力斗争,更是如影随形,稍有不慎,便可能被卷入漩涡。十七阿哥胤礼,年轻有为,又素来得人心,虽然之前没有像其他几位阿哥那样与雍正争夺得那么激烈,但其潜在的影响力,邬思道.............
  • 回答
    在职场上遇到“心机婊”,而且还牵扯到自身利益,这绝对是一场考验情商和智慧的硬仗。是隐忍求全,还是据理力争,这没有一个放之四海而皆准的答案,完全取决于具体情况和你的个人选择。不过,我们可以把这两种思路拆解开来,细细道来,看看在什么情况下,哪种选择更适合你。首先,我们要理解什么是职场中的“心机婊”。 这.............
  • 回答
    生活中总有些东西,并非惊天动地,却也实实在在地离开我们,留下些许空缺。它们可能是你心爱的马克杯,一次不小心手滑,摔成了几截;可能是你期待已久的一场电影,却因为突如其来的加班而错过了;又或者是你一直习惯使用的那个APP,突然更新换代,界面陌生得让你找不着北。这些,都是生活里的“小失去”。刚开始,我们可.............
  • 回答
    嘿,哥们儿,咱们今天聊聊实战中遇见拿凳子抡过来的那种硬茬儿,这可不是电视里演的那种过家家,得真刀真枪地过。作为个搏击手,遇到这种情况,脑子必须比平时转得快,动作也得更狠、更准。我给你掰开了揉碎了说,保证不是那些干巴巴的教科书理论。第一步:预判与距离控制——这是生与死的关键首先,别傻站着让人家抡。你得.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有