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);}
}
这个设计的问题在于:
- 脆弱的基类(Fragile Base Class):对
OrderProcessorBase
的任何修改(例如,添加一个新的虚拟方法,修改Process
方法的流程)都可能会无声地破坏所有子类的行为。子类与基类紧密耦合,基类变得“脆弱”。 - 爆炸性的子类:如果现在需要处理一种“在线且免税”的订单,你该怎么办?创建
OnlineTaxFreeOrderProcessor
?这会导致类层次结构爆炸,产生复杂的菱形继承问题(C#不支持多继承,此路不通)。 - 不灵活的复用:
TaxFreeOrderProcessor
无法复用基类CalculateTotal
的逻辑,只能重写,可能导致代码重复。 - 单一演化方向:继承强制子类沿着基类的单一维度进行演化。但现实世界的需求变化往往是多维度的(如支付方式、客户等级、订单来源等)。
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 组合的优势与架构师视角
- 灵活性(Flexibility):你可以动态地组合任何验证策略和任何计算策略,轻松应对“在线且免税”这种多维度的需求变化,而无需修改现有类或创建复杂的继承树。
- 解耦(Decoupling):
OrderProcessor
不再与任何具体的业务实现耦合,它只依赖于接口。各个策略之间也是相互独立的。 - 可测试性(Testability):可以轻松地为
OrderProcessor
编写单元测试,只需注入Mock的策略对象即可。 - 单一职责(Single Responsibility):每个策略类都只有一个非常明确的职责,符合SRP。
- 开闭原则(Open/Closed):添加新的验证或计算方式(新的策略实现)完全不需要修改
OrderProcessor
或其他策略,符合OCP。
何时使用继承?
“组合优于继承”并非要完全否定继承。继承在以下场景仍然有效:
- 建立严格的类型层次结构:当存在真正的“is-a”关系,且子类确实是基类的一个更具体的类型时(如
Cat : Animal
,Button : Control
)。 - 需要多态:当你需要让不同的子类对象对同一消息做出不同响应时。
- 框架设计:用于定义模板方法模式,其中基类控制主要流程,子类只负责实现特定的步骤。
决策指南:
- 默认优先选择组合,尤其是对于行为的扩展。
- 询问自己:“我是为了复用代码,还是为了建立类型关系?”如果主要是为了复用,组合通常是更安全、更灵活的选择。
- 如果使用继承,确保子类和基类的关系是稳定且永久的,避免设计深度过大的继承层次。
- 利用依赖注入框架来管理这些可组合的策略对象,这将使你的系统像由乐高积木组成一样,可以轻松地拆卸和重组。
总结:
从“继承思维”转变为“组合思维”是迈向高级软件设计的关键一步。它要求你从对象间的关系和行为的角度来思考,而不是仅从分类的角度。通过将系统分解为一系列精细、单一职责、可插拔的组件,并通过组合它们来构建复杂行为,你将创建一个真正灵活、健壮且易于演进的架构。这种思维方式是理解后续依赖注入(DI)和更高级架构模式的基础。