2.2 Composition over Inheritance:组合优于继承的实际应用

继承(Inheritance)是面向对象编程中最容易被过度使用和误用的特性之一。传统的教学往往让人们优先选择继承来实现代码复用和建立“是一个(is-a)”的关系。然而,在复杂的业务系统中,严格的继承层次结构常常会变得僵化、脆弱,最终难以维护。

“组合优于继承”(Composition over Inheritance)是一条经典的设计原则,它倡导通过将对象组合在一起,而不是通过继承来扩展功能。它旨在构建“有一个(has-a)”或“使用一个(uses-a)”的关系,从而获得更大的灵活性。

2.2.1 继承的陷阱:脆弱的基类问题

让我们看一个典型的滥用继承的例子:

// 一个看似合理的继承层次结构
public abstract class OrderProcessorBase {public virtual void Validate(Order order) {// 基础验证逻辑}public virtual decimal CalculateTotal(Order order) {// 计算总价的基础逻辑return order.Items.Sum(i => i.Price * i.Quantity);}public void Process(Order order) {Validate(order);order.Total = CalculateTotal(order);// ... 其他处理步骤,如保存、通知等}
}// 为特定场景扩展
public class OnlineOrderProcessor : OrderProcessorBase {public override void Validate(Order order) {base.Validate(order); // 先执行基础验证// 添加在线订单特有的验证,如配送地址必填if (string.IsNullOrEmpty(order.ShippingAddress))throw new InvalidOperationException("Shipping address is required.");}public override decimal CalculateTotal(Order order) {decimal baseTotal = base.CalculateTotal(order);// 添加在线订单特有的费用,如运费return baseTotal + 5.0m;}
}public class TaxFreeOrderProcessor : OrderProcessorBase {public override decimal CalculateTotal(Order order) {// 免税订单,需要完全重写计算逻辑,可能无法有效复用基类逻辑return order.Items.Sum(i => i.PriceBeforeTax * i.Quantity);}
}

这个设计的问题在于:

  1. 脆弱的基类(Fragile Base Class):对 OrderProcessorBase 的任何修改(例如,添加一个新的虚拟方法,修改 Process 方法的流程)都可能会无声地破坏所有子类的行为。子类与基类紧密耦合,基类变得“脆弱”。
  2. 爆炸性的子类:如果现在需要处理一种“在线且免税”的订单,你该怎么办?创建 OnlineTaxFreeOrderProcessor?这会导致类层次结构爆炸,产生复杂的菱形继承问题(C#不支持多继承,此路不通)。
  3. 不灵活的复用TaxFreeOrderProcessor 无法复用基类 CalculateTotal 的逻辑,只能重写,可能导致代码重复。
  4. 单一演化方向:继承强制子类沿着基类的单一维度进行演化。但现实世界的需求变化往往是多维度的(如支付方式、客户等级、订单来源等)。

2.2.2 组合的解决方案:策略模式与行为注入

让我们用“组合”来重新设计上面的例子。我们将不同的行为(验证、计算)抽取出来,定义为独立的策略接口。

// 1. 定义抽象策略接口
public interface IOrderValidationStrategy {void Validate(Order order);
}
public interface IOrderCalculationStrategy {decimal CalculateTotal(Order order);
}
// ... 可以定义更多策略接口,如 INotificationStrategy, IPersistenceStrategy// 2. 实现各种具体策略
public class StandardValidationStrategy : IOrderValidationStrategy {public void Validate(Order order) { /* 基础验证逻辑 */ }
}
public class OnlineOrderValidationStrategy : IOrderValidationStrategy {public void Validate(Order order) {// 包含基础验证和在线特有验证new StandardValidationStrategy().Validate(order);if (string.IsNullOrEmpty(order.ShippingAddress))throw new InvalidOperationException("Shipping address is required.");}
}
//---
public class StandardCalculationStrategy : IOrderCalculationStrategy {public decimal CalculateTotal(Order order) => order.Items.Sum(i => i.Price * i.Quantity);
}
public class OnlineCalculationStrategy : IOrderCalculationStrategy {public decimal CalculateTotal(Order order) => new StandardCalculationStrategy().CalculateTotal(order) + 5.0m;
}
public class TaxFreeCalculationStrategy : IOrderCalculationStrategy {public decimal CalculateTotal(Order order) => order.Items.Sum(i => i.PriceBeforeTax * i.Quantity);
}// 3. 核心订单处理器,通过组合各种策略来完成工作
public class OrderProcessor {private readonly IOrderValidationStrategy _validationStrategy;private readonly IOrderCalculationStrategy _calculationStrategy;// ... 其他策略// 策略通过构造函数注入。这提供了极大的灵活性!public OrderProcessor(IOrderValidationStrategy validationStrategy,IOrderCalculationStrategy calculationStrategy){_validationStrategy = validationStrategy;_calculationStrategy = calculationStrategy;}public void Process(Order order) {_validationStrategy.Validate(order);order.Total = _calculationStrategy.CalculateTotal(order);// ... 使用其他策略}
}

现在,如何创建那个令人头疼的“在线且免税”订单处理器?变得非常简单:

// 在应用程序的 composition root (通常是DI容器配置处) 进行组装
var processorForComplexOrder = new OrderProcessor(validationStrategy: new OnlineOrderValidationStrategy(),    // 使用在线验证calculationStrategy: new TaxFreeCalculationStrategy()      // 使用免税计算
);// 或者,你可以创建新的组合策略,而不是新的Processor类
public class OnlineTaxFreeCalculationStrategy : IOrderCalculationStrategy {public decimal CalculateTotal(Order order) {decimal baseTotal = new StandardCalculationStrategy().CalculateTotal(order);return baseTotal + 5.0m; // 在线费用,但免税逻辑已融入?这里设计需要斟酌}
}
// 更好的方式是:认识到“加运费”和“免税”是两个正交的关切点,可能需要更细粒度的策略组合。

2.2.3 组合的优势与架构师视角

  1. 灵活性(Flexibility):你可以动态地组合任何验证策略和任何计算策略,轻松应对“在线且免税”这种多维度的需求变化,而无需修改现有类或创建复杂的继承树。
  2. 解耦(Decoupling)OrderProcessor 不再与任何具体的业务实现耦合,它只依赖于接口。各个策略之间也是相互独立的。
  3. 可测试性(Testability):可以轻松地为 OrderProcessor 编写单元测试,只需注入Mock的策略对象即可。
  4. 单一职责(Single Responsibility):每个策略类都只有一个非常明确的职责,符合SRP。
  5. 开闭原则(Open/Closed):添加新的验证或计算方式(新的策略实现)完全不需要修改 OrderProcessor 或其他策略,符合OCP。

何时使用继承?
“组合优于继承”并非要完全否定继承。继承在以下场景仍然有效:

  • 建立严格的类型层次结构:当存在真正的“is-a”关系,且子类确实是基类的一个更具体的类型时(如 Cat : Animal, Button : Control)。
  • 需要多态:当你需要让不同的子类对象对同一消息做出不同响应时。
  • 框架设计:用于定义模板方法模式,其中基类控制主要流程,子类只负责实现特定的步骤。

决策指南:

  • 默认优先选择组合,尤其是对于行为的扩展。
  • 询问自己:“我是为了复用代码,还是为了建立类型关系?”如果主要是为了复用,组合通常是更安全、更灵活的选择。
  • 如果使用继承,确保子类和基类的关系是稳定且永久的,避免设计深度过大的继承层次。
  • 利用依赖注入框架来管理这些可组合的策略对象,这将使你的系统像由乐高积木组成一样,可以轻松地拆卸和重组。

总结:
从“继承思维”转变为“组合思维”是迈向高级软件设计的关键一步。它要求你从对象间的关系和行为的角度来思考,而不是仅从分类的角度。通过将系统分解为一系列精细、单一职责、可插拔的组件,并通过组合它们来构建复杂行为,你将创建一个真正灵活、健壮且易于演进的架构。这种思维方式是理解后续依赖注入(DI)和更高级架构模式的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919778.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919778.shtml
英文地址,请注明出处:http://en.pswp.cn/news/919778.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失 broker在故障后的消息丢失 消费者在消费过程中的消息丢失ACK机制 ack有3个可选值,分别是1,0,-1。 ack0:生产者在生产过程中的消息丢失 简单来说就是,producer发送一次就不再发送了&#…

盼之代售 231滑块 csessionid 分析

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向分析 部分python代码 url "…

STL关联式容器解析:map与set详解

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.2 set的使用 3.2 map 3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.3.2 multiset的使用 3.4 multimap 3.4.1 multimap的介绍 3.4.2 multimap的使用 4.红黑树模拟实现…

贪吃蛇--C++实战项目(零基础)

视频地址:C语言必学项目:贪吃蛇! 贪吃蛇游戏框架 ├── 基础框架 │ ├── 头文件引入 │ ├── 常量和宏定义 │ └── 窗口初始化 │ ├── 数据结构系统 │ ├── Pos结构体(位置和颜色) │ ├── Snake结构体(蛇的属性) │ ├──…

unity资源领取反作弊工具加密器

https://assetstore.unity.com/packages/tools/utilities/anti-cheat-pro-2025-3006260元购码GUARDINGPEARSOFTWARE

FPGA设计中的信号完整性量化与优化:探索高速数字系统的关键路径

在高速FPGA设计中,信号完整性(Signal Integrity, SI)已成为确保系统稳定、可靠运行的核心要素之一。随着数据传输速率的不断提升和电路规模的日益复杂,信号在传输过程中受到的干扰和畸变问题日益凸显。因此,如何有效量…

`strncasecmp` 字符串比较函数

1) 函数的概念与用途 strncasecmp 是 C 语言中一个非常实用的字符串处理函数,它执行不区分大小写的字符串比较,但只比较前 n 个字符。这个函数的名字来源于"string n case-compare"(字符串前n个字符不区分大小写比较)。…

软件安装教程(一):Visual Studio Code安装与配置(Windows)

文章目录前言一、Visual Studio Code下载二、安装步骤(Windows)1. GUI安装2. 命令行安装三、首次启动后建议的快速配置(几分钟完成)四、常见问题 & 小贴士总结前言 Visual Studio Code(VS Code)是一款…

JavaSSM框架从入门到精通!第三天(MyBatis(二))!

四、Mapper 的动态代理1. 引入 在上面的 CURD 例子中,我们发现:Dao 层的实现类的每一个方法仅仅是通过 SqlSession 对象的相关 API 定位到映射文件 mapper 中的 SQL 语句,真正对数据库操作的工作实际上是有 Mybatis 框架通过 mapper 中的 SQL…

大模型应用发展与Agent前沿技术趋势(下)

Agent技术的行业应用与实践案例 金融领域的Agent应用 金融行业是大模型Agent技术应用最为广泛的领域之一,涵盖了风险评估、投资决策、客户服务等多个方面。在金融风控领域,Agent系统通过结合大模型的语义理解能力和强化学习的决策优化能力,能…

94. 城市间货物运输 I, Bellman_ford 算法, Bellman_ford 队列优化算法

94. 城市间货物运输 IBellman_ford 算法Bellman_ford 算法 与 dijkstra算法 相比通用性更强。dijkstra算法解决不了负权边的问题,因为Dijkstra基于贪心策略,一旦一个节点被从队列中取出(标记为已解决),它就假定已经找到…

如何使用Prometheus + Grafana + Loki构建一个现代化的云原生监控系统

如何使用 Prometheus + Grafana + Loki 构建一个现代化的云原生监控系统。这套组合被誉为监控领域的“瑞士军刀”,功能强大且生态极佳。 一、核心组件概念介绍 在搭建之前,深刻理解每个组件的角色和职责至关重要。 1. Prometheus(指标监控与时序数据库) 角色:系统的“核…

JavaScript Object 操作方法及 API

一、对象创建方式1.字面量创建(最常用)const obj { name: "张三", age: 25 };2.构造函数创建const obj new Object(); obj.name "李四";3.Object.create()(指定原型)const proto { greet: () > "…

pta乙级题目day1

第1天:输入输出与运算(6题)1001 害死人不偿命的(3n1)猜想(基础运算)★1006 换个格式输出整数(格式化输出)★1016 部分AB(数字提取)★★1046 划拳(多输入处理&…

在VSCode中配置.NET项目的tasks.json以实现清理、构建、热重载和发布等操作

在 VS Code 中配置 .NET 开发任务的完整指南 引言 重要提醒:对于 .NET 开发,强烈推荐使用 Visual Studio,它提供了最完整和稳定的开发体验。如果你像我一样"蛋疼"想要尝试 VS Code,请确保安装了 C# 开发扩展包&#x…

EmEditor文本编辑器v25.3.0专业版,专业文本编辑,高亮显示,无限撤消

[软件名称]: EmEditor文本编辑器v25.3.0专业版 [软件大小]: 37.7 MB [软件大小]: 夸克网盘 | 百度网盘 软件介绍 EmEditor 是一款功能强大且非常实用的文本编辑器。它启动速度快,完全可以替代 Windows 自带的记事本,轻松应对日常文本编辑任务。它对 …

【spring security】权限管理组件执行流程详解

🎯 权限管理组件执行流程详解 🏗️ 组件架构图 ┌─────────────────────────────────────────────────────────────┐ │ HTTP请求 …

redis怎么保障双写一致性

redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与red…

异常值检测:孤立森林模型(IsolationForest)总结

目录一、前言二、孤立森林算法2.1 算法简介2.2 基本原理2.3 算法步骤2.4 异常分数计算方式2.5 python调用方式三、python代码示例四、小结五、参考学习一、前言 近期在研究构建寿命预测模型,相信很多数据人都懂建模的过程,其实有80%的时间都是在和数据处…

Docker容器化部署实战:Tomcat与Nginx服务配置指南

部署Tomcat搜索镜像 使用以下命令搜索可用的Tomcat镜像:docker search tomcat拉取镜像 拉取官方Tomcat镜像:docker pull tomcat创建专用目录 为Tomcat配置和数据创建专用目录:mkdir tomcat运行临时容器并复制配置文件 启动临时容器以复制配置…