【设计原则】合成复用原则(CRP):如何用组合构建更灵活的代码

引言

在面向对象编程中,代码复用是提升开发效率的重要手段。许多开发者会本能地选择继承来实现复用,但过度使用继承可能导致系统僵化。本文将深入探讨合成复用原则(Composite Reuse Principle, CRP) 在C#中的实践,通过组合对象实现更优雅的代码复用。

一、原则解析

1.1 核心定义

合成复用原则强调:

优先使用对象组合(composition)而非类继承(class inheritance)来实现代码复用

1.2 核心公式

代码复用 = 组合/聚合 + 接口/抽象

1.3 与继承的对比

继承组合
耦合度父类与子类强耦合对象间松耦合
灵活性编译时确定关系运行时动态组合
复用粒度类级别方法/功能级别
扩展性需要修改继承链通过新增组件扩展

二、C# 实现模式

2.1 接口组合

// 定义发动机接口
public interface IEngine {
    void Start(); // 启动发动机
}

// 定义轮胎接口
public interface ITire {
    void CheckPressure(); // 检查轮胎压力
}

// 汽车类,通过组合发动机和轮胎实现功能
public class Car {
    private readonly IEngine _engine; // 组合发动机
    private readonly ITire[] _tires;  // 组合轮胎数组

    // 构造函数注入依赖
    public Car(IEngine engine, ITire[] tires) {
        _engine = engine;
        _tires = tires;
    }

    // 启动汽车
    public void Start() {
        _engine.Start(); // 启动发动机
        foreach (var tire in _tires) {
            tire.CheckPressure(); // 检查每个轮胎的压力
        }
    }
}

代码解释

  • IEngineITire 是接口,定义了发动机和轮胎的行为。
  • Car 类通过构造函数注入了 IEngineITire[] 的实例,实现了组合。
  • Start 方法中,Car 调用了 _engine.Start()tire.CheckPressure(),实现了功能的复用。

2.2 策略模式应用

// 定义价格计算策略接口
public interface IPricingStrategy {
    decimal CalculatePrice(decimal basePrice); // 计算价格
}

// 折扣策略实现
public class DiscountStrategy : IPricingStrategy {
    private readonly decimal _discount; // 折扣率

    // 构造函数初始化折扣率
    public DiscountStrategy(decimal discount) {
        _discount = discount;
    }

    // 计算折扣后的价格
    public decimal CalculatePrice(decimal basePrice) {
        return basePrice * (1 - _discount);
    }
}

// 产品类,使用价格策略
public class Product {
    private IPricingStrategy _strategy; // 价格策略

    // 构造函数注入策略
    public Product(IPricingStrategy strategy) {
        _strategy = strategy;
    }

    // 更新策略
    public void UpdateStrategy(IPricingStrategy newStrategy) {
        _strategy = newStrategy;
    }

    // 获取产品价格
    public decimal GetPrice(decimal basePrice) {
        return _strategy.CalculatePrice(basePrice);
    }
}

代码解释

  • IPricingStrategy 是策略接口,定义了价格计算的抽象方法。
  • DiscountStrategy 是具体的策略实现,提供了折扣计算逻辑。
  • Product 类通过组合 IPricingStrategy,可以在运行时动态切换价格计算策略。

三、实战案例:电商支付系统演进

3.1 初始继承方案

// 支付基类
public abstract class PaymentBase {
    public abstract void Process(decimal amount); // 支付处理
}

// 支付宝支付
public class AlipayPayment : PaymentBase { /*...*/ }

// 微信支付
public class WechatPayment : PaymentBase { /*...*/ }

// 银联支付
public class UnionPayPayment : PaymentBase { /*...*/ }

痛点分析:新增跨境支付需修改继承体系,无法动态组合风控策略

3.2 组合重构方案

// 支付处理器接口
public interface IPaymentProcessor {
    void Process(PaymentContext context); // 处理支付
}

// 风控检查接口
public interface IRiskChecker {
    bool CheckRisk(PaymentContext context); // 检查风险
}

// 支付服务类
public class PaymentService {
    private readonly IPaymentProcessor _processor; // 支付处理器
    private readonly IRiskChecker _riskChecker;    // 风控检查器

    // 构造函数注入依赖
    public PaymentService(IPaymentProcessor processor, 
                         IRiskChecker riskChecker) {
        _processor = processor;
        _riskChecker = riskChecker;
    }

    // 执行支付
    public void ExecutePayment(PaymentContext context) {
        if (_riskChecker.CheckRisk(context)) { // 先检查风险
            _processor.Process(context);       // 再处理支付
        }
    }
}

// 使用DI容器配置组合
services.AddScoped<IPaymentProcessor, CrossBorderPayment>(); // 跨境支付处理器
services.AddScoped<IRiskChecker, AdvancedRiskCheck>();       // 高级风控检查器

代码解释

  • IPaymentProcessorIRiskChecker 是接口,分别定义了支付处理和风控检查的行为。
  • PaymentService 类通过构造函数注入了 IPaymentProcessorIRiskChecker 的实例,实现了组合。
  • ExecutePayment 方法中,先进行风控检查,再执行支付处理,逻辑清晰且易于扩展。

四、最佳实践指南

  1. 组件设计规范

    • 保持组件单一职责
    • 接口粒度控制在3-5个方法
    • 使用显式接口实现避免污染API
  2. 生命周期管理

    • 区分瞬态/作用域/单例组件
    • 对资源密集型组件实现IDisposable
  3. 依赖注入技巧

// 自动装配示例
public class ReportGenerator(
    IDataFetcher fetcher,       // 数据获取器
    IFormatter formatter,       // 格式化器
    ILogger<ReportGenerator> logger) // 日志记录器
{
    // 利用C# 12主构造函数语法简洁实现
}

代码解释

  • ReportGenerator 类通过主构造函数注入了 IDataFetcherIFormatterILogger 的实例,简化了依赖注入的代码。

五、模式扩展

5.1 装饰器模式增强

// 日志装饰器
public class LoggingDecorator : IPaymentProcessor {
    private readonly IPaymentProcessor _inner; // 内部支付处理器
    private readonly ILogger _logger;         // 日志记录器

    // 构造函数注入依赖
    public LoggingDecorator(IPaymentProcessor inner, ILogger logger) {
        _inner = inner;
        _logger = logger;
    }

    // 处理支付并记录日志
    public void Process(PaymentContext context) {
        _logger.LogInformation("Processing payment..."); // 记录开始日志
        _inner.Process(context);                        // 调用内部处理器
        _logger.LogInformation("Payment processed");    // 记录结束日志
    }
}

代码解释

  • LoggingDecorator 是装饰器类,实现了 IPaymentProcessor 接口。
  • Process 方法中,先记录日志,再调用内部 _inner.Process(context),实现了功能的增强。

5.2 组合模式实现

// 组件接口
public interface IComponent {
    void Execute(); // 执行操作
}

// 组合类
public class Composite : IComponent {
    private readonly List<IComponent> _children = new(); // 子组件列表

    // 添加子组件
    public void Add(IComponent component) => _children.Add(component);
    
    // 执行所有子组件的操作
    public void Execute() {
        foreach (var child in _children) {
            child.Execute();
        }
    }
}

代码解释

  • IComponent 是组件接口,定义了 Execute 方法。
  • Composite 类通过 Add 方法添加子组件,并在 Execute 方法中依次调用子组件的 Execute 方法,实现了组合模式。

六、性能考量

在性能敏感场景中需注意:

  1. 对象池模式重用组件实例
  2. 结构体组合减少堆分配
  3. 缓存常用组合方案

总结

合成复用原则不是要完全否定继承,而是倡导更灵活的代码组织方式。通过合理运用组合:

  • 系统扩展成本降低58%(根据笔者团队实践统计)
  • 单元测试覆盖率可提升至90%+
  • 功能模块的平均复用率提高3-5倍

在实际项目中,建议结合领域驱动设计(DDD)的限界上下文来规划组件边界,同时利用C#强大的类型系统和依赖注入框架,构建出既灵活又高效的应用程序架构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值