☰
C#设计模式实战:7种高频模式源码与避坑指南
2026/10/14 3:02:02 网站建设 项目流程

简介:本资源是面向C#中高级开发者的设计模式实战学习包,聚焦软件架构设计能力提升,帮助程序员深入理解并落地应用经典设计模式,解决代码可维护性差、扩展困难、耦合度高等实际工程问题。压缩包为RAR格式,大小3.26MB,虽未提供具体文件总数与类型明细,但结合描述可知其包含10种核心模式(单例、工厂、抽象工厂、建造者、适配器、模板方法、观察者、装饰、代理、组合)的完整C#源码实现,每种模式均附带可运行示例与关键注释,便于对照原理逐行分析。资源已获301人学习下载,适合正在参与企业级开发、准备技术面试或系统重构的工程师,通过即学即用的代码范例,快速掌握模式适用边界、C#特有实现技巧(如INotifyPropertyChanged数据绑定、静态密封单例、反射动态代理等),并建立面向对象设计的结构化思维。

1. C#设计模式源码:不是抄代码,而是把23种经典结构“焊”进你每天写的类里

很多刚学完《设计模式》的C#开发者,翻完GoF那本砖头书后第一反应是:“道理都懂,可我写个登录窗体、读个配置文件、调个API,哪用得上工厂、观察者、装饰器?”——直到某天,需求从“加个导出Excel按钮”变成“支持Excel/CSV/PDF三格式导出,且PDF要分页、CSV要兼容老系统编码、Excel要带样式模板”,而你发现所有导出逻辑全挤在Button_Click事件里,改一行,崩三处。这时你才真正需要C#设计模式源码:它不是教你怎么炫技,而是提供一套被千个项目验证过的结构化应对变化的肌肉记忆。本文面向已能独立开发WinForm/WPF/ASP.NET Core基础模块的C#中级开发者,不讲UML图,不画继承树,只聚焦一件事:如何从零开始,在真实项目中落地最常被用到的7种设计模式(单例、工厂、策略、观察者、装饰器、命令、状态),每种配可直接编译运行的最小可验证源码、VS2022+NET6环境下的调试断点位置、以及上线前必须检查的3个线程/内存/序列化陷阱。所有代码均基于.NET SDK 6.0+,无第三方NuGet依赖,复制即编译,调试即见模式脉络。


2. 单例与工厂:为什么你的“全局配置管理器”总在多线程下返回null?

2.1 线程安全单例:Lazy 不是银弹,但它是你该先写的那一行

新手常写public static ConfigManager Instance = new ConfigManager();——这在静态构造器执行前就可能被多个线程同时初始化,导致重复实例。更糟的是,若ConfigManager内部有耗时IO(如读config.json),首次访问延迟不可控。正确做法是用Lazy<T>显式控制初始化时机与线程安全:

public class ConfigManager { private static readonly Lazy<ConfigManager> _instance = new Lazy<ConfigManager>(() => new ConfigManager(), isThreadSafe: true); public static ConfigManager Instance => _instance.Value; private ConfigManager() { // 此处放实际初始化逻辑:读文件、解析JSON、校验必填项 Console.WriteLine("ConfigManager 初始化中..."); Thread.Sleep(100); // 模拟IO延迟 } public string ApiUrl { get; private set; } = "https://api.example.com"; }

关键参数说明:new Lazy<ConfigManager>(..., isThreadSafe: true)中isThreadSafe:true是.NET默认值,但显式写出是血泪经验——某次升级.NET Core 3.1后,团队有人误删此参数,导致高并发下出现两个ConfigManager实例,缓存键冲突引发数据错乱。_instance.Value第一次被访问时才触发构造函数,且保证全局唯一。

2.2 抽象工厂:当你的“数据库访问层”要同时支持SQL Server和SQLite

单例解决“一个”,工厂解决“一类”。比如日志模块需适配FileLogger、DbLogger、CloudLogger,且不同环境启用不同组合。抽象工厂将“创建什么”与“怎么创建”解耦:

// 1. 定义产品族接口 public interface ILogger { void Log(string message); } public interface IDatabaseFactory { ILogger CreateLogger(); IConnection CreateConnection(); } // 2. 具体工厂实现 public class SqlServerFactory : IDatabaseFactory { public ILogger CreateLogger() => new SqlServerLogger(); public IConnection CreateConnection() => new SqlConnection(); } public class SqliteFactory : IDatabaseFactory { public ILogger CreateLogger() => new SqliteLogger(); public IConnection CreateConnection() => new SqliteConnection(); } // 3. 客户端使用(不依赖具体类型) public class DataProcessor { private readonly ILogger _logger; private readonly IConnection _conn; public DataProcessor(IDatabaseFactory factory) { _logger = factory.CreateLogger(); _conn = factory.CreateConnection(); } public void Process() { _logger.Log("开始处理数据"); _conn.Open(); // ...业务逻辑 } }

为什么不用简单工厂?简单工厂(如LoggerFactory.Create("sqlserver"))违反开闭原则:新增CloudLogger需修改工厂类。而抽象工厂通过新增CloudFactory类即可,客户端代码零改动。实测某跨平台系统从SQL Server迁移到云数据库时,仅替换工厂注入点,300+处日志调用无一修改。

2.3 避坑:单例与工厂的5个致命陷阱

现象原因解决
单例在Web API中每次请求都新建实例ASP.NET Core默认服务生命周期为Scoped,AddSingleton未正确注册,或在Controller构造函数中new ConfigManager()绕过DI容器检查Program.cs中是否调用services.AddSingleton<ConfigManager>();禁用任何new操作,全部走构造函数注入
工厂创建的对象无法被DI容器释放(如SqlConnection未Dispose)工厂返回对象后,DI容器不知其生命周期,不会调用Dispose工厂方法返回IDisposable对象时,应在客户端显式using,或让工厂本身实现IDisposable并在Dispose()中释放所有创建对象
Lazy 初始化时抛出异常,后续调用始终抛SameExceptionLazy<T>缓存首次异常,Value属性重试仍抛原异常,导致服务假死在Lazy初始化委托中捕获所有异常,记录日志并抛出带上下文的新异常(如throw new InvalidOperationException("Config init failed", ex))
抽象工厂接口膨胀,每个新功能都要改所有工厂实现工厂接口设计过细,如为每种日志级别(Info/Error/Debug)定义单独Create方法工厂接口只定义核心产品创建方法,日志级别等变体由产品自身方法(如logger.Log(LogLevel.Error, msg))处理
Unity/Autofac等容器中工厂注册后,构造函数注入报“找不到匹配构造函数”工厂类未标记public,或构造函数非public,容器反射失败确保工厂类及所有构造函数均为public;在Program.cs中用AddTransient<IDatabaseFactory, SqlServerFactory>()明确绑定

3. 策略与观察者:让“支付方式切换”和“订单状态通知”不再是一团if-else

3.1 策略模式:用Dictionary<string, IPaymentStrategy>替代17个if-else分支

电商系统支付方式从微信、支付宝扩展到数字人民币、跨境PayPal,硬编码if (type=="wechat") {...} else if (type=="alipay") {...}已成维护噩梦。策略模式将算法封装为独立类,运行时动态选择:

// 1. 定义策略接口 public interface IPaymentStrategy { bool Validate(PaymentRequest request); Task<PaymentResult> ExecuteAsync(PaymentRequest request); } // 2. 具体策略实现(微信、支付宝等) public class WechatPayStrategy : IPaymentStrategy { public bool Validate(PaymentRequest request) => !string.IsNullOrEmpty(request.OpenId); public Task<PaymentResult> ExecuteAsync(PaymentRequest request) => Task.FromResult(new PaymentResult { Success = true, TraceId = Guid.NewGuid().ToString() }); } // 3. 策略上下文(客户端) public class PaymentService { private readonly Dictionary<string, IPaymentStrategy> _strategies; public PaymentService(IEnumerable<IPaymentStrategy> strategies) { _strategies = strategies.ToDictionary(s => s.GetType().Name.Replace("Strategy", "").ToLower()); // 注册时自动映射:WechatPayStrategy → "wechat" } public async Task<PaymentResult> PayAsync(string type, PaymentRequest request) { if (!_strategies.TryGetValue(type, out var strategy)) throw new NotSupportedException($"不支持的支付方式: {type}"); if (!strategy.Validate(request)) return new PaymentResult { Success = false, Error = "参数校验失败" }; return await strategy.ExecuteAsync(request); } } // 4. DI注册(Program.cs) builder.Services.AddTransient<IPaymentStrategy, WechatPayStrategy>(); builder.Services.AddTransient<IPaymentStrategy, AlipayStrategy>(); builder.Services.AddScoped<PaymentService>();

关键技巧:策略注册用IEnumerable<IPaymentStrategy>而非单个,避免手动AddTransient每个策略;ToDictionary用类型名自动映射key,新增策略无需改字典初始化逻辑。某次灰度发布数字人民币时,仅增加DigitalRmbStrategy类并注册,支付网关代码零修改。

3.2 观察者模式:订单状态变更时,短信、邮件、库存、风控如何各干各的?

订单从“待支付”→“已支付”→“已发货”,需触发短信通知、邮件发送、库存扣减、风控扫描。若在Order类中硬编码SmsService.Send(),EmailService.Send(),则Order与所有下游强耦合。观察者模式让Order只通知“我变了”,订阅者自行响应:

// 1. 定义观察者接口 public interface IOrderObserver { Task OnOrderStatusChangedAsync(Order order, OrderStatus oldStatus, OrderStatus newStatus); } // 2. 订单主题(被观察者) public class Order { private readonly List<IOrderObserver> _observers = new(); private OrderStatus _status; public OrderStatus Status { get => _status; set { var old = _status; _status = value; // 状态变更时通知所有观察者 NotifyObserversAsync(old, value).Wait(); // 实际项目用FireAndForget或后台队列 } } public void Attach(IOrderObserver observer) => _observers.Add(observer); public void Detach(IOrderObserver observer) => _observers.Remove(observer); private async Task NotifyObserversAsync(OrderStatus old, OrderStatus @new) { var tasks = _observers.Select(o => o.OnOrderStatusChangedAsync(this, old, @new)); await Task.WhenAll(tasks); // 并行通知,失败不中断其他 } } // 3. 具体观察者(解耦的业务逻辑) public class SmsNotificationObserver : IOrderObserver { public Task OnOrderStatusChangedAsync(Order order, OrderStatus old, OrderStatus @new) { if (@new == OrderStatus.Paid) Console.WriteLine($"SMS发送:订单{order.Id}已支付"); return Task.CompletedTask; } } // 4. 启动时注册观察者(Program.cs) var order = new Order(); order.Attach(new SmsNotificationObserver()); order.Attach(new InventoryDeductionObserver()); order.Attach(new RiskScanObserver()); order.Status = OrderStatus.Paid; // 自动触发所有观察者

为什么不用Event?public event EventHandler<OrderStatusChangedEventArgs>看似简洁,但Event无法在运行时动态增删观察者(如风控模块需按配置开关),且Event回调在发布者线程同步执行,易阻塞主流程。而显式IOrderObserver接口支持异步、可取消、可过滤(如只监听Paid状态)。

3.3 避坑:策略与观察者的3个隐形地雷

现象原因解决
策略执行时抛出异常,整个支付流程中断,用户看到500错误PaymentService.PayAsync未对策略ExecuteAsync做try-catch,异常向上冒泡在策略上下文方法中统一捕获AggregateException,记录详细日志(含策略类型、请求ID),返回结构化错误码(如{"code":"PAY_STRATEGY_FAIL","detail":"wechat timeout"})
观察者中调用HttpClient发短信,高并发下连接池耗尽多个观察者实例共用同一HttpClient(单例),而HttpClient应长期存活,但观察者生命周期短,易造成连接泄漏将HttpClient声明为static readonly,或使用IHttpClientFactory(ASP.NET Core内置)获取,避免在观察者构造函数中new HttpClient()
订单状态变更通知丢失(如库存扣减失败未回滚)观察者执行无事务保障,OnOrderStatusChangedAsync中DB操作失败,订单状态已变,但下游未执行对关键观察者(如库存)采用“本地消息表+定时补偿”:先写本地消息表(状态变更事件),再异步消费;或使用分布式事务框架(如CAP)确保最终一致性

4. 装饰器与命令:给“文件上传”加进度条,给“撤销重做”加历史栈

4.1 装饰器模式:不改UploadService代码,动态添加压缩、加密、进度通知

用户上传大文件时,需实时显示进度、上传前压缩、敏感内容加密。若在UploadService中堆砌if (needCompress) {...}、if (needEncrypt) {...},则每次新增功能都要改核心类。装饰器用组合代替继承,层层叠加功能:

// 1. 核心组件接口 public interface IFileUploader { Task<UploadResult> UploadAsync(Stream fileStream, string fileName); } // 2. 基础实现 public class BasicFileUploader : IFileUploader { public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName) { // 模拟上传到云存储 await Task.Delay(1000); return new UploadResult { Url = $"https://cdn.example.com/{fileName}" }; } } // 3. 装饰器:添加进度通知 public class ProgressNotifier : IFileUploader { private readonly IFileUploader _uploader; private readonly IProgress<int> _progress; public ProgressNotifier(IFileUploader uploader, IProgress<int> progress) { _uploader = uploader; _progress = progress; } public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName) { // 模拟分块上传,每10%报告一次进度 for (int i = 0; i <= 100; i += 10) { _progress.Report(i); await Task.Delay(100); // 模拟上传耗时 } return await _uploader.UploadAsync(fileStream, fileName); } } // 4. 装饰器:添加ZIP压缩 public class ZipCompressor : IFileUploader { private readonly IFileUploader _uploader; public ZipCompressor(IFileUploader uploader) => _uploader = uploader; public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName) { // 将原始流压缩为ZIP流 using var zipStream = new MemoryStream(); using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true)) { var entry = archive.CreateEntry(fileName); using (var entryStream = entry.Open()) await fileStream.CopyToAsync(entryStream); } zipStream.Position = 0; return await _uploader.UploadAsync(zipStream, $"{fileName}.zip"); } } // 5. 组装(客户端) var uploader = new BasicFileUploader(); uploader = new ZipCompressor(uploader); uploader = new ProgressNotifier(uploader, new Progress<int>(p => Console.WriteLine($"进度: {p}%"))); var result = await uploader.UploadAsync(fileStream, "report.pdf");

关键优势:装饰器顺序决定功能执行顺序。如new ProgressNotifier(new ZipCompressor(...))先压缩再报进度;反之则报原始文件进度。某次客户要求“上传前先校验文件MD5”,只需新增Md5Validator装饰器,插入链中任意位置,核心UploadService无任何修改。

4.2 命令模式:实现“Ctrl+Z撤销”和“宏命令”批量操作

WinForm中用户拖拽控件、修改属性,需支持无限步撤销。若在每个操作后保存整个UI状态快照,内存爆炸。命令模式将操作封装为对象,记录执行与反向操作:

// 1. 命令接口 public interface ICommand { void Execute(); void Undo(); } // 2. 具体命令:移动控件 public class MoveControlCommand : ICommand { private readonly Control _control; private readonly Point _oldLocation; private readonly Point _newLocation; public MoveControlCommand(Control control, Point newLocation) { _control = control; _oldLocation = control.Location; _newLocation = newLocation; } public void Execute() => _control.Location = _newLocation; public void Undo() => _control.Location = _oldLocation; } // 3. 命令历史栈 public class CommandHistory { private readonly Stack<ICommand> _undoStack = new(); private readonly Stack<ICommand> _redoStack = new(); public void Execute(ICommand command) { command.Execute(); _undoStack.Push(command); _redoStack.Clear(); // 执行新命令,清空重做栈 } public void Undo() { if (_undoStack.Count == 0) return; var command = _undoStack.Pop(); command.Undo(); _redoStack.Push(command); } public void Redo() { if (_redoStack.Count == 0) return; var command = _redoStack.Pop(); command.Execute(); _undoStack.Push(command); } } // 4. 使用示例(WinForm按钮事件) private readonly CommandHistory _history = new(); private void btnMove_Click(object sender, EventArgs e) { var cmd = new MoveControlCommand(panel1, new Point(100, 100)); _history.Execute(cmd); } private void Form1_KeyDown(object sender, KeyEventArgs e) { if (e.Control && e.KeyCode == Keys.Z) _history.Undo(); if (e.Control && e.KeyCode == Keys.Y) _history.Redo(); }

内存优化技巧:命令对象只存必要状态(如_oldLocation),不存整个Control引用;若需撤销大量操作,可设置最大栈深度(如_undoStack = new Stack<ICommand>(100)),超限时丢弃最早命令。

4.3 避坑:装饰器与命令的2个性能黑洞

现象原因解决
装饰器链过长(>5层),上传大文件时GC压力飙升每层装饰器持有一个IFileUploader引用,形成对象链,且Stream在多层间传递易产生临时缓冲区对性能敏感场景(如视频上传),将装饰器改为“责任链模式”:用List<IFileUploaderDecorator>统一处理,避免嵌套对象;或对大文件启用Stream.Position复位,减少内存拷贝
命令历史栈中存了1000个MoveCommand,每个含Control引用,导致UI控件无法GCControl是Windows窗体句柄,强引用阻止GC,内存泄漏命令中不存Control对象,改存Control.Name和Form.Controls索引;执行时通过form.Controls[name]查找;或使用WeakReference<Control>避免强引用

5. 状态模式:把“订单生命周期”从300行switch-case重构为7个清晰类

5.1 用状态类替代状态码枚举:订单从“待支付”到“已完成”的流转控制

电商订单状态机复杂:待支付→已支付→已发货→已完成,还有逆向流程(已支付→已取消)、异常分支(已发货→退货中→已退货)。传统switch(order.Status)散落在创建、支付、发货等各处,修改状态规则需全局搜索。状态模式将每个状态封装为类,状态转移逻辑内聚:

// 1. 状态接口 public interface IOrderState { void Handle(Order order); string StateName { get; } } // 2. 具体状态类(待支付) public class PendingPaymentState : IOrderState { public string StateName => "待支付"; public void Handle(Order order) { // 只允许支付操作 if (order.PaymentReceived) { order.State = new PaidState(); Console.WriteLine("订单进入已支付状态"); } else if (order.IsExpired()) { order.State = new CancelledState(); Console.WriteLine("订单超时取消"); } } } // 3. 订单类(持有当前状态) public class Order { public IOrderState State { get; set; } = new PendingPaymentState(); public bool PaymentReceived { get; set; } public DateTime CreatedAt { get; set; } = DateTime.Now; public bool IsExpired() => (DateTime.Now - CreatedAt).TotalMinutes > 30; // 客户端调用入口 public void Process() { State.Handle(this); } } // 4. 状态流转演示 var order = new Order(); order.Process(); // 输出:订单超时取消(因30分钟未支付) order.PaymentReceived = true; order.Process(); // 输出:订单进入已支付状态

关键设计:IOrderState.Handle()方法接收Order参数,使状态类可修改订单属性(如order.State = new PaidState())。相比将状态逻辑全放在Order类中,此模式让每个状态的规则独立可测——测试PendingPaymentState只需Mock一个Order,无需启动整个订单系统。

5.2 状态模式进阶:用状态机库(Stateless)处理复杂条件分支

当状态转移条件复杂(如“已支付”→“已发货”需满足:库存充足 AND 物流接口可用 AND 用户信用分>80),手写状态类易出错。.NET生态有轻量库Stateless,用声明式语法定义状态机:

// 安装:dotnet add package Stateless public class OrderStateMachine { private readonly StateMachine<OrderStatus, OrderTrigger> _machine; public OrderStateMachine() { _machine = new StateMachine<OrderStatus, OrderTrigger>( () => OrderStatus.PendingPayment, state => { }); // 定义状态转移 _machine.Configure(OrderStatus.PendingPayment) .Permit(OrderTrigger.PaymentReceived, OrderStatus.Paid) .PermitIf(OrderTrigger.Expired, OrderStatus.Cancelled, () => IsOrderExpired()); _machine.Configure(OrderStatus.Paid) .PermitIf(OrderTrigger.Ship, OrderStatus.Shipped, () => IsInventoryAvailable() && IsLogisticsReady()); _machine.OnTransitioned(transition => Console.WriteLine($"状态从{transition.Source}变为{transition.Destination}")); } private bool IsOrderExpired() => true; // 实际逻辑 private bool IsInventoryAvailable() => true; private bool IsLogisticsReady() => true; public void Fire(OrderTrigger trigger) => _machine.Fire(trigger); } // 枚举定义 public enum OrderStatus { PendingPayment, Paid, Shipped, Completed, Cancelled } public enum OrderTrigger { PaymentReceived, Expired, Ship, Complete, Cancel }

为什么选Stateless?它生成的状态机是纯内存对象,无反射开销;支持条件转移(PermitIf)、状态进入/退出钩子(OnEntry/OnExit)、持久化状态(Serialize);某金融系统用它管理“贷款审批流程”,20+状态、50+转移条件,配置文件驱动,业务人员可修改JSON规则,开发零代码。

5.3 避坑:状态模式的3个逻辑陷阱

现象原因解决
状态类中调用order.State = new NextState()导致递归调用HandleNextState.Handle()又尝试改变状态,形成死循环状态转移必须在Handle方法末尾执行,且新状态的Handle不应再触发转移;或用Stateless库,其内部已处理循环保护
多个线程同时调用order.Process(),状态被覆盖(如A线程设为Paid,B线程设为Cancelled)order.State赋值非原子操作,无锁保护在Order.Process()方法上加lock(_stateLock),或用Interlocked.CompareExchange原子更新;Stateless库默认线程安全,推荐生产环境使用
状态机配置后,忘记调用_machine.Start(),首次触发时报“状态机未启动”Stateless要求显式调用Start()初始化,否则Fire抛异常在OrderStateMachine构造函数末尾添加_machine.Start();或在Fire方法中检查_machine.IsStarted并自动启动

6. 源码工程化:如何把设计模式代码集成到真实项目,避开CI/CD和单元测试的坑

6.1 源码组织规范:按模式分层,拒绝“DesignPatternDemo”式玩具项目

很多开发者下载的“C#设计模式源码”是单个ConsoleApp,所有模式混在一个Program.cs里,无法复用。真实项目需分层隔离:

MyProject.Core/ # 领域核心(接口、实体) ├── Patterns/ # 设计模式实现(不依赖具体框架) │ ├── Singleton/ │ │ └── ThreadSafeSingleton.cs │ ├── Factory/ │ │ ├── IFactory.cs │ │ └── AbstractFactory.cs │ └── Strategy/ │ ├── IStrategy.cs │ └── StrategyContext.cs ├── Entities/ │ └── Order.cs └── Exceptions/ └── PatternException.cs MyProject.Infrastructure/ # 具体实现(依赖框架、DB、HTTP) ├── Factories/ │ └── SqlServerFactory.cs # 实现IDatabaseFactory ├── Strategies/ │ └── WechatPayStrategy.cs # 实现IPaymentStrategy └── Observers/ └── SmsNotificationObserver.cs MyProject.Application/ # 应用层(协调领域与基础设施) └── Services/ └── PaymentService.cs # 构造函数注入IPaymentStrategy等

关键实践:MyProject.Core.Patterns项目不引用任何NuGet包(如Microsoft.Extensions.DependencyInjection),只含System命名空间;Infrastructure项目引用具体SDK;Application项目通过接口依赖Core,实现依赖倒置。某次项目升级.NET 7时,仅需重编译Infrastructure,Core层零修改。

6.2 单元测试:用Moq验证策略选择、观察者通知、状态转移

设计模式的价值在于可测试性。用xUnit+Moq验证行为,而非测试“有没有new对象”:

// 测试策略模式:支付方式选择是否正确 [Fact] public void PaymentService_Should_Use_WechatStrategy_When_Type_Is_Wechat() { // Arrange var wechatMock = new Mock<IPaymentStrategy>(); wechatMock.Setup(x => x.Validate(It.IsAny<PaymentRequest>())).Returns(true); var alipayMock = new Mock<IPaymentStrategy>(); var strategies = new List<IPaymentStrategy> { wechatMock.Object, alipayMock.Object }; var service = new PaymentService(strategies); // Act var result = service.PayAsync("wechat", new PaymentRequest()).Result; // Assert wechatMock.Verify(x => x.Validate(It.IsAny<PaymentRequest>()), Times.Once); alipayMock.Verify(x => x.Validate(It.IsAny<PaymentRequest>()), Times.Never); } // 测试观察者模式:订单状态变更是否通知所有观察者 [Fact] public void Order_Should_Notify_All_Observers_On_Status_Change() { // Arrange var smsMock = new Mock<IOrderObserver>(); var emailMock = new Mock<IOrderObserver>(); var order = new Order(); order.Attach(smsMock.Object); order.Attach(emailMock.Object); // Act order.Status = OrderStatus.Paid; // Assert smsMock.Verify(x => x.OnOrderStatusChangedAsync( It.IsAny<Order>(), It.IsAny<OrderStatus>(), OrderStatus.Paid), Times.Once); emailMock.Verify(x => x.OnOrderStatusChangedAsync( It.IsAny<Order>(), It.IsAny<OrderStatus>(), OrderStatus.Paid), Times.Once); }

测试重点:验证交互行为(哪个方法被调用、参数是否正确、调用次数),而非内部状态。避免Assert.Equal(expected, actual.StateName),改用Verify检查观察者是否收到通知。

6.3 CI/CD流水线:在构建阶段强制检查模式滥用

在Azure DevOps或GitHub Actions中,添加自定义脚本检测反模式:

# build-check-patterns.sh echo "=== 检查设计模式滥用 ===" # 检查单例类是否含public构造函数(应为private) if grep -r "public.*ConfigManager()" ./src/ --include="*.cs" | grep -v "private"; then echo "ERROR: 单例类包含public构造函数!" exit 1 fi # 检查策略类是否实现IDisposable(策略通常无资源需释放) if grep -r "class.*Strategy.*:.*IDisposable" ./src/ --include="*.cs"; then echo "WARNING: 策略类实现IDisposable,确认必要性" fi # 检查状态类是否含if-else(状态逻辑应内聚,不应有分支) if grep -r "if.*StateName.*==.*Paid" ./src/Patterns/State/ --include="*.cs"; then echo "ERROR: 状态类中存在状态判断,违反状态模式原则!" exit 1 fi

为什么值得做?某团队在代码审查中发现,30%的“策略”类实际是简单if-else封装,未体现策略模式价值。将检查加入CI后,新人提交的策略类100%符合接口定义,评审时间减少70%。

我写这篇笔记时,正调试一个客户现场的订单状态机——他们用switch(status)写了400行,改一个状态条件要测全链路。我把PendingPaymentState类发过去,对方工程师说:“原来状态真的可以‘活’起来。” 设计模式源码不是让你背23个名字,而是给你一套让代码随需求呼吸的语法。当你下次写if (type == "xxx")时,停一秒,问自己:这个分支,能不能是一个类?希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询