当前位置: 首页 > news >正文

C#_面向对象设计的艺术


面向对象设计的艺术

优秀的软件架构并非诞生于对最新技术潮流的盲目追随,而是建立在历经时间考验的坚实设计原则之上。面向对象编程(OOP)提供了构建复杂系统的强大工具集,但仅仅知道“继承”、“封装”和“多态”是远远不够的。关键在于如何运用它们来实现高内聚、低耦合的模块,从而构建出能够从容应对变化的设计。

本章将探讨那些能够将你的代码从“可以工作”提升到“设计卓越”境界的核心原则。


2.1 SOLID原则在C#中的实践与误区

SOLID是五个首要面向对象设计原则的缩写,由Robert C. Martin提出。它们是指引我们构建可维护软件架构的明灯。然而,盲目套用或错误理解这些原则同样有害。我们将逐一剖析它们,并展示在C#中的具体实践。

S - 单一职责原则 (Single Responsibility Principle)

  • 核心思想:一个类应该有且仅有一个引起它变化的原因。
  • C#实践:这不是指一个类只能做一件事,而是指它的所有公共方法都应该是其核心职责的直接体现。如果一个类的修改源于数据库 schema 变化、报表格式变化和业务逻辑变化等多个不相关的原因,它就违反了SRP。
  • 反面示例
    public class OrderProcessor {public void Process(Order order) {// 职责1: 验证订单// 职责2: 计算价格// 职责3: 库存检查// 职责4: 持久化到数据库// 职责5: 发送确认邮件}
    }
    
    这个类承担了太多职责。任何上述环节的逻辑变化都需要修改它,测试也会变得异常复杂。
  • 重构方案
    public class OrderProcessor {private readonly IOrderValidator _validator;private readonly IPriceCalculator _calculator;private readonly IInventoryService _inventory;private readonly IOrderRepository _repository;private readonly INotificationService _notifier;// 依赖通过构造函数注入public OrderProcessor(...) { ... }public void Process(Order order) {_validator.Validate(order);order.Total = _calculator.Calculate(order);_inventory.Check(order);_repository.Save(order);_notifier.SendConfirmation(order);}
    }
    
    现在,OrderProcessor 的职责是协调各个专注于单一任务的组件来完成订单处理流程。每个组件的修改都不会影响其他组件。

O - 开闭原则 (Open/Closed Principle)

  • 核心思想:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
  • C#实践:这意味着你应该能够通过添加新的代码(如新类)来扩展系统的行为,而不是修改现有已经测试通过的代码。在C#中,这通常通过抽象(abstraction)多态(polymorphism) 实现。
  • 反面示例
    public class DiscountCalculator {public decimal Calculate(Order order, string customerType) {switch (customerType) {case "Regular":return order.Total * 0.95m;case "Premium":return order.Total * 0.85m;case "VIP":return order.Total * 0.75m;default:return order.Total;}}
    }
    
    每次新增一个客户类型,都需要修改这个类的 Calculate 方法,违反了OCP。
  • 重构方案
    // 抽象策略
    public interface IDiscountStrategy {bool IsMatch(Customer customer); // 判断是否适用此策略decimal CalculateDiscount(Order order);
    }// 具体策略
    public class RegularDiscountStrategy : IDiscountStrategy { ... }
    public class PremiumDiscountStrategy : IDiscountStrategy { ... }
    public class VipDiscountStrategy : IDiscountStrategy { ... }// 上下文
    public class DiscountCalculator {private readonly IEnumerable<IDiscountStrategy> _strategies;public DiscountCalculator(IEnumerable<IDiscountStrategy> strategies) {_strategies = strategies;}public decimal Calculate(Order order, Customer customer) {// 找到匹配的策略并应用折扣var strategy = _strategies.FirstOrDefault(s => s.IsMatch(customer));return strategy?.CalculateDiscount(order) ?? order.Total;}
    }
    
    现在,要添加一个新的折扣类型(如“节日折扣”),你只需要创建一个新的 IDiscountStrategy 实现类并将其注册到DI容器中。DiscountCalculator 的核心逻辑无需任何修改。这符合对扩展开放,对修改关闭的原则。

L - 里氏替换原则 (Liskov Substitution Principle)

  • 核心思想:子类型必须能够替换掉它们的基类型,而不改变程序的正确性。
  • C#实践:这不仅仅是语法上的“能编译”,更是行为上的兼容。子类不应该强化前置条件(要求更多)或弱化后置条件(承诺更少),也不应该改变基类声明的可观测行为。
  • 反面示例
    public class Rectangle {public virtual int Width { get; set; }public virtual int Height { get; set; }public int Area => Width * Height;
    }public class Square : Rectangle {private int _side;public override int Width {get => _side;set { _side = value; }}public override int Height {get => _side;set { _side = value; }}
    }// 使用
    Rectangle rect = new Square();
    rect.Width = 5;
    rect.Height = 10; // 用户认为这是一个Rectangle,期望面积为50
    Console.WriteLine(rect.Area); // 输出 100!行为与预期不符,违反了LSP。
    
    从数学上讲,正方形是一种矩形,但在行为上,这个 Square 类并不能替换 Rectangle 而不引起错误。
  • 建议:LSP是关于契约可预期行为的。在设计继承 hierarchy 时,要时刻问自己:“客户端代码是否能够毫不知情地使用任何子类?” 如果答案是否定的,那么继承很可能是错误的模型,组合可能是更好的选择。

I - 接口隔离原则 (Interface Segruation Principle)

  • 核心思想:不应该强迫客户端依赖于它们不使用的接口方法。
  • C#实践:与其创建一个庞大的“胖接口”,不如将其拆分为多个更小、更具体的接口。这让客户端只关注它们真正关心的契约。
  • 反面示例
    public interface IOrderService {void CreateOrder(Order order);Order GetOrder(int id);void UpdateOrder(Order order);void DeleteOrder(int id);void ApproveOrder(int id); // 只有管理员需要void RejectOrder(int id);  // 只有管理员需要void ShipOrder(int id);    // 只有物流人员需要
    }
    
    一个普通的客户服务如果实现了这个接口,将被迫抛出 NotImplementedException 或者对 ApproveOrder, ShipOrder 等方法提供无意义的实现。
  • 重构方案
    public interface IOrderBasicService {void CreateOrder(Order order);Order GetOrder(int id);void UpdateOrder(Order order);void DeleteOrder(int id);
    }public interface IOrderAdminService {void ApproveOrder(int id);void RejectOrder(int id);
    }public interface IOrderFulfillmentService {void ShipOrder(int id);
    }// 不同的客户端(用户、管理员、物流系统)可以依赖不同的、精确的接口。
    

D - 依赖倒置原则 (Dependency Inversion Principle)

  • 核心思想
    1. 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
    2. 抽象不应该依赖于细节。细节应该依赖于抽象。
  • C#实践:这是实现松耦合系统的关键。高层策略性代码(如业务逻辑)不应该直接依赖于底层实现细节(如数据库访问、文件IO),而应该依赖于抽象的接口。这使你可以轻松更换底层实现(如将SQL Server换成PostgreSQL,或将SMTP邮件发送换成SendGrid API)而无需修改高层业务逻辑。
  • 反面示例
    // 高层模块直接依赖于低层细节
    public class BusinessLogic {private readonly SqlServerDatabase _database; // 具体依赖!public BusinessLogic() {_database = new SqlServerDatabase(); // 紧耦合!}public void PerformTask() {var data = _database.GetData();// ... 处理逻辑}
    }
    
  • 重构方案
    // 定义抽象(高层策略依赖于此)
    public interface IRepository {Data GetData();
    }// 高层模块
    public class BusinessLogic {private readonly IRepository _repository; // 依赖抽象// 抽象通过构造函数注入(依赖注入)public BusinessLogic(IRepository repository) {_repository = repository; // 解耦!}public void PerformTask() {var data = _repository.GetData(); // 仅依赖契约,不关心实现// ... 处理逻辑}
    }// 低层细节依赖于抽象
    public class SqlServerRepository : IRepository { ... }
    public class FileRepository : IRepository { ... }
    public class CloudRepository : IRepository { ... }
    
    现在,BusinessLogic 完全不知道也不关心数据从哪里来。它只关心契约 IRepository。这极大地提高了系统的可测试性(我们可以注入一个Mock的IRepository进行单元测试)和可维护性。

误区与警告
SOLID原则是强大的指导方针,但不是教条。过度工程和创建大量不必要的抽象和小类同样会损害代码的可读性和可维护性。架构师的角色是权衡:在设计的简洁性和未来的灵活性之间找到平衡点。通常,对于预期会频繁变化的部分应用这些原则收益最大,而对于稳定的部分则可以保持简单。

http://www.dtcms.com/a/343812.html

相关文章:

  • [特殊字符] 高可用高并发微服务架构设计:Nginx 与 API Gateway 的协同实践
  • Oracle DB 10g 升级至 11.2.0.4报错-ORA-00132
  • 论文阅读:Do As I Can, Not As I Say: Grounding Language in Robotic Affordances
  • 大模型微调训练资源占用查询:Windows 10 查看 NVIDIA 显卡GPU状态教程(替代 Ubuntu 下 watch nvidia-smi)
  • 从零开始:C语言配置文件解析实战(二)—— 数据解析与键值获取
  • 相机曝光调节与自动曝光控制详解
  • 11、Informer论文笔记
  • 高通Camx相机dump yuv和raw图的抓取方式和查看
  • Linux 软件编程(九)网络编程:IP、端口与 UDP 套接字
  • Jmeter混合业务负载测试指南
  • 定制吹塑服务:塑料成型领域的“柔性制造”新范式
  • 计算机网络:TCP、UDP
  • 管理型交换机通过VLAN划分实现不同IP跨网段通信配置方法
  • JavaEE 初阶第二十期:网络编程“通关记”(二)
  • 微前端qiankun框架,子页面图标样式错乱问题,显示为X
  • Halcon那些事:什么是动态阈值,如何用dyn_threshold分割图片
  • Elasticsearch Rails 实战全指南(elasticsearch-rails / elasticsearch-model)
  • 集成电路学习:什么是K-NN最近邻算法
  • Seaborn数据可视化实战:Seaborn图表定制与数据可视化入门
  • AI+虚拟仿真:以科技之光照亮希望的田野
  • 课小悦系列智能耳机上市,用硬核科技为教育赋能
  • 学习嵌入式第二十三天——数据结构——栈
  • Qt5 文件与数据处理详解
  • NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标,或使用支持
  • 【FPGA Interlaken协议】
  • 服务器与客户端
  • AI服务器介绍
  • FPGA设计中的信号完整性量化与优化:探索高速数字系统的关键路径
  • 20.9 QLoRA微调实战:1.5B参数Whisper-large-v2在24GB显存实现中文语音识别,CER骤降50%!
  • 企业微信新版搞了个AI功能