合成/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)
——面向对象设计的“灵活复用”准则
一、定义与核心思想
-
核心定义
优先通过组合(Composition)或聚合(Aggregation) 关系实现代码复用,而非继承。其核心是:通过包含其他对象的功能来实现代码复用,而非通过继承父类。
例如:- 用户服务类通过组合日志组件实现功能,而非继承日志基类。
- 插件系统通过聚合接口扩展功能,而非继承主程序类。
-
核心思想
- 弱耦合:通过对象组合减少类之间的直接依赖。
- 黑箱复用:复用对象功能无需了解其内部细节。
二、组合与聚合的区别
组合(Composition) | 聚合(Aggregation) |
---|---|
强拥有关系,整体与部分生命周期一致(如“人”与“手臂”)。 | 弱拥有关系,部分可独立存在(如“人”与“人群”)。 |
通过构造函数或初始化直接创建部分对象。 | 通过外部传入或动态绑定部分对象。 |
三、为何优先选择组合/聚合?
-
继承的缺陷
- 破坏封装性:子类依赖父类实现细节。
- 灵活性差:父类变更可能导致子类级联修改。
- 多继承问题:部分语言不支持多重继承。
-
组合/聚合的优势
- 低耦合:组件可独立替换(如更换数据库驱动)。
- 高扩展性:新增功能通过组合新对象实现(如动态添加日志模块)。
- 符合开闭原则:扩展功能无需修改现有代码。
四、实现方式与示例
-
依赖注入(DI)
- 构造函数注入:通过构造参数传递依赖对象。
class OrderService { private Payment payment; public OrderService(Payment payment) { this.payment = payment; // 依赖注入 } }
- Setter方法注入:动态设置依赖。
- 构造函数注入:通过构造参数传递依赖对象。
-
接口与抽象类
- 定义功能接口:如
Logger
接口,FileLogger
和DatabaseLogger
分别实现。
- 定义功能接口:如
-
工厂模式
- 通过工厂类创建组合对象,解耦对象创建逻辑。
五、应用场景
- 分层架构
- 数据访问层通过接口实现,业务层依赖接口而非具体数据库类。
- 插件化系统
- 主程序通过聚合插件接口扩展功能(如 IDE 插件机制)。
- 微服务通信
- 服务间通过 API 接口交互,而非直接依赖实现细节。
六、与其他原则的协同
- 开闭原则(OCP)
- 组合/聚合通过扩展实现功能变化,支持OCP。
- 里氏替换原则(LSP)
- 组合避免继承带来的行为不一致问题。
- 单一职责原则(SRP)
- 组合将功能拆分到不同对象,确保职责单一。
七、注意事项
- 避免过度设计
- 简单场景(如工具类)可直接使用继承。
- 性能权衡
- 多层组合可能增加对象创建开销,高频场景需优化。
- 明确组合关系
- 区分组合与聚合的适用场景,避免混淆。
总结
合成/聚合复用原则通过对象组合与功能委托,构建了灵活、低耦合的软件架构。其本质是面向对象设计的“搭积木”思维,核心价值在于提升代码复用性、扩展性和可维护性。实际开发中需结合场景灵活选择组合或继承,避免教条化应用。