简介:这份C#设计模式源码资源面向具备一定C#基础的开发者与软件设计学习者,围绕单例、工厂、抽象工厂、建造者、适配器、模板方法、观察者、装饰、代理、组合等经典模式提供可运行的代码实现,帮助读者在真实项目中理解模式的应用场景与优缺点,提升代码的可读性、可维护性与可扩展性。资源以rar压缩包形式提供,整体约3.26MB,内容以源码文件为主,便于直接编译调试与逐类对照分析。目前已有302人学习下载,适合作为设计模式入门到进阶的实践参考。通过阅读源码,读者可以掌握线程安全单例、工厂方法的产品创建、建造者模式的导演与建造者协作、观察者模式结合INotifyPropertyChanged的数据绑定,以及静态代理与动态代理的实现差异,从而在架构设计与重构中更灵活地选用合适模式。
1. 从一次代码评审说起:为什么C#设计模式源码值得逐行拆
某次内部代码评审,一位同事提交了一个订单状态流转模块,用了一长串if-else判断状态,新增一种状态就要改动三处逻辑。评审时有人问了一句:“这里为什么不用状态模式?”回答是“知道有这个东西,但不知道怎么落地”。这个场景很典型——设计模式的概念大家都背过,但真正到了C#项目里,能把模式用对、用准、用出可维护性的人并不多。这份C#设计模式源码要解决的正是这个断层:它不是概念速查表,而是一套可以直接跑、可以改、可以对照业务场景复用的代码集合。适合正在做课程设计、毕业设计,或者需要在实际项目中引入设计模式却缺少参考实现的开发者。接下来的内容会按“需求拆解→结构设计→模块实现→避坑→验收”的顺序展开,把这份源码怎么用、怎么改、怎么讲清楚,一次说透。
2. 需求拆解与技术选型:为什么是C#加设计模式这套组合
2.1 从业务痛点反推模式需求
设计模式不是凭空选的,得先看业务里反复出现什么问题。常见的几类痛点包括:对象创建逻辑散落在各处导致改一处崩三处、算法频繁切换却不想动调用方代码、子系统接口太杂需要统一入口、状态或行为随条件动态变化。这些问题在C#项目里尤其突出,因为C#是强类型语言,接口和抽象类的约束比动态语言更严格,一旦结构没设计好,后期重构成本很高。
这份源码覆盖的模式大致可以按创建型、结构型、行为型三类来组织。创建型解决“对象怎么来”的问题,比如单例、工厂方法、抽象工厂、建造者;结构型解决“对象怎么组合”的问题,比如适配器、装饰器、代理、外观;行为型解决“对象怎么协作”的问题,比如观察者、策略、状态、命令、责任链。每个模式都对应一个具体的业务场景,不是孤立的Demo。
通常的做法是,先列出项目里所有“变化点”——哪些地方经常改、哪些地方扩展频繁,然后对照模式清单找匹配。比如支付方式经常新增,就用策略模式;订单状态流转复杂,就用状态模式;日志、缓存这类横切逻辑,就用装饰器或代理。
2.2 技术栈与运行环境说明
这份源码基于C#语言,运行在.NET平台上。项目结构通常按模式分类建文件夹,每个模式一个独立可运行的控制台或类库项目,方便单独调试。核心依赖只有标准库,不引入第三方框架,这样做的目的是让读者把注意力放在模式本身的结构上,而不是被框架的API干扰。
代码里会用到接口、抽象类、泛型、委托、事件这些C#特性。比如观察者模式会用event和delegate实现,策略模式会用接口加泛型约束,工厂模式会用反射或配置文件来解耦。这些特性在C#里是原生支持的,不需要额外安装包。
提示:如果本地没有Visual Studio,用
dotnetCLI也能编译运行,命令是dotnet run,前提是已安装对应版本的.NET SDK。
2.3 项目目录结构与模块划分
源码的目录结构一般长这样:
DesignPatterns/ ├── Creational/ │ ├── Singleton/ │ ├── FactoryMethod/ │ ├── AbstractFactory/ │ └── Builder/ ├── Structural/ │ ├── Adapter/ │ ├── Decorator/ │ ├── Proxy/ │ └── Facade/ ├── Behavioral/ │ ├── Observer/ │ ├── Strategy/ │ ├── State/ │ ├── Command/ │ └── ChainOfResponsibility/ └── Shared/ └── Interfaces/每个模式文件夹下至少包含三个文件:接口定义、具体实现、调用示例。调用示例通常放在Program.cs或单独的Client.cs里,用Main方法演示完整流程。Shared文件夹放公共接口,比如IOrder、IPayment这类跨模式复用的抽象。
这种划分的好处是:想学哪个模式就进哪个文件夹,不用在几百个文件里翻。想对比两个模式的差异,直接并排打开两个文件夹就行。
3. 核心模式实现:从创建型到行为型的代码落地
3.1 创建型模式:单例与工厂的C#写法
单例模式在C#里有几种写法,最推荐的是静态内部类或Lazy<T>方式,线程安全且延迟加载。下面是一个用Lazy<T>实现的单例:
public sealed class ConfigManager { // 使用 Lazy<T> 保证线程安全且延迟初始化 private static readonly Lazy<ConfigManager> _instance = new Lazy<ConfigManager>(() => new ConfigManager()); public static ConfigManager Instance => _instance.Value; // 私有构造函数,防止外部 new private ConfigManager() { // 模拟加载配置 LoadConfig(); } private void LoadConfig() { // 实际项目中这里读文件或数据库 Console.WriteLine("配置已加载"); } }这段代码的关键点是sealed防止继承、私有构造函数防止外部实例化、Lazy<T>保证线程安全。参数上没有什么可调的,但要注意:如果配置加载很耗时,Lazy<T>的LazyThreadSafetyMode可以改成PublicationOnly来放宽线程约束,不过一般用默认的ExecutionAndPublication就够了。
工厂方法模式则用来解耦对象的创建。比如一个支付场景,不同支付方式实现同一个接口,工厂根据类型返回具体实例:
public interface IPayment { void Pay(decimal amount); } public class AlipayPayment : IPayment { public void Pay(decimal amount) => Console.WriteLine($"支付宝支付 {amount} 元"); } public class WechatPayment : IPayment { public void Pay(decimal amount) => Console.WriteLine($"微信支付 {amount} 元"); } public static class PaymentFactory { public static IPayment Create(string type) { return type switch { "alipay" => new AlipayPayment(), "wechat" => new WechatPayment(), _ => throw new ArgumentException("不支持的支付类型") }; } }调用时只需要PaymentFactory.Create("alipay").Pay(100),新增支付方式时改工厂的switch即可,调用方不动。这就是开闭原则的体现——对扩展开放,对修改关闭。
3.2 结构型模式:装饰器与代理的差异与选择
装饰器和代理在代码结构上很像,都是包装一个对象,但意图不同。装饰器是动态给对象加功能,代理是控制对对象的访问。下面是一个装饰器的例子,给订单服务加日志和缓存:
public interface IOrderService { void PlaceOrder(string orderId); } public class OrderService : IOrderService { public void PlaceOrder(string orderId) { Console.WriteLine($"下单成功:{orderId}"); } } // 日志装饰器 public class LoggingDecorator : IOrderService { private readonly IOrderService _inner; public LoggingDecorator(IOrderService inner) => _inner = inner; public void PlaceOrder(string orderId) { Console.WriteLine($"[日志] 开始处理订单 {orderId}"); _inner.PlaceOrder(orderId); Console.WriteLine($"[日志] 订单 {orderId} 处理完毕"); } } // 缓存装饰器 public class CachingDecorator : IOrderService { private readonly IOrderService _inner; private readonly HashSet<string> _cache = new(); public CachingDecorator(IOrderService inner) => _inner = inner; public void PlaceOrder(string orderId) { if (_cache.Contains(orderId)) { Console.WriteLine($"订单 {orderId} 已存在,跳过"); return; } _inner.PlaceOrder(orderId); _cache.Add(orderId); } }使用时可以层层包装:new LoggingDecorator(new CachingDecorator(new OrderService()))。这样日志和缓存逻辑跟业务逻辑完全分离,想加就加,想撤就撤。
代理模式则更适合远程调用、权限控制这类场景。比如一个文件访问接口,代理层先检查权限再决定是否调用真实对象。两者的选择标准很简单:加功能用装饰器,控访问用代理。
3.3 行为型模式:观察者与策略的事件驱动实现
观察者模式在C#里用event实现最自然。比如订单状态变化时通知多个模块:
public class Order { public event Action<string> OnStatusChanged; private string _status; public string Status { get => _status; set { _status = value; OnStatusChanged?.Invoke(value); // 触发通知 } } } // 调用方 var order = new Order(); order.OnStatusChanged += status => Console.WriteLine($"邮件通知:订单状态变为 {status}"); order.OnStatusChanged += status => Console.WriteLine($"短信通知:订单状态变为 {status}"); order.Status = "已发货";这里OnStatusChanged是委托事件,+=注册多个订阅者,?.Invoke保证没有订阅者时不报错。参数status是状态字符串,实际项目里可以换成枚举或自定义类。
策略模式则用来封装可互换的算法。比如运费计算,不同地区不同规则:
public interface IShippingStrategy { decimal Calculate(decimal weight); } public class StandardShipping : IShippingStrategy { public decimal Calculate(decimal weight) => weight * 5; } public class ExpressShipping : IShippingStrategy { public decimal Calculate(decimal weight) => weight * 10 + 20; } public class ShippingContext { private IShippingStrategy _strategy; public ShippingContext(IShippingStrategy strategy) => _strategy = strategy; public decimal Execute(decimal weight) => _strategy.Calculate(weight); }调用时传入不同策略即可:new ShippingContext(new ExpressShipping()).Execute(3)。新增运费规则时只加一个类,不改上下文。
4. 避坑与常见问题:设计模式落地时最容易翻车的五个点
4.1 模式过度使用导致类爆炸
现象:一个简单功能引入了五六个接口和抽象类,代码量翻倍,维护反而更累。原因:把模式当成了目标,而不是手段。解决:先问“这里真的会变吗”,如果变化概率很低,直接用简单写法。模式只在变化点明确时才引入。
4.2 单例模式在多线程下失效
现象:单例对象被创建了两次,配置加载重复执行。原因:用了非线程安全的懒加载写法,比如if (_instance == null)没有加锁。解决:用Lazy<T>或静态构造函数,这两种方式由CLR保证线程安全。
4.3 装饰器顺序影响结果
现象:日志装饰器放在缓存装饰器外面,导致缓存命中时也打印日志,日志量暴增。原因:装饰器是层层包装的,外层先执行。解决:明确每个装饰器的职责,缓存应该在最外层拦截,日志放在内层只记录真实调用。
4.4 观察者模式忘记取消订阅导致内存泄漏
现象:长时间运行后内存持续增长,GC回收不掉。原因:事件订阅后没有-=取消,发布者一直持有订阅者的引用。解决:在Dispose或生命周期结束时取消订阅,或者用弱事件模式。
4.5 策略模式与工厂模式混淆
现象:把策略的创建逻辑写在了策略类内部,导致策略类既管算法又管实例化。原因:职责没分清。解决:策略只负责算法,创建交给工厂或依赖注入容器。两者配合使用,但不要混在一起。
5. 验收与答辩:怎么证明这套源码真的能用
5.1 功能验收:每个模式独立可运行
验收的第一步是确认每个模式都能单独跑起来。通常的做法是,每个模式文件夹下都有一个Program.cs,用dotnet run执行后能看到预期输出。比如单例模式输出“配置已加载”且只输出一次,工厂模式输出不同支付方式的调用结果,观察者模式输出多条通知。如果某个模式跑不起来,先检查.NET SDK版本是否匹配,再看接口和实现类的命名空间是否一致。
5.2 扩展验收:新增一个模式或修改一个模式
光跑通还不够,得证明这套结构是可扩展的。我一般会让学生做两件事:第一,新增一个策略实现类,比如“次日达运费”,看是否只需要加一个类、改一处工厂配置;第二,给现有装饰器再加一层“重试装饰器”,看是否不影响原有代码。如果能做到只加不改,说明开闭原则落地了。
5.3 答辩演示:用对比法讲清楚模式的价值
答辩时不要逐个念代码,而是用对比法。先展示一段没有用模式的“坏代码”,比如一长串if-else或重复的创建逻辑,然后展示用模式重构后的代码,重点讲三个点:变化点在哪里、模式怎么隔离变化、新增需求时改哪里。评委通常关心的是“你为什么选这个模式”和“不用行不行”,提前准备好这两个问题的答案。
下面是一个简单的验收检查表,可以照着过一遍:
| 检查项 | 通过标准 |
|---|---|
| 编译运行 | 每个模式独立dotnet run无报错 |
| 输出正确 | 控制台输出与预期一致 |
| 扩展测试 | 新增一个实现类,调用方代码不变 |
| 线程安全 | 单例模式多线程测试无重复实例 |
| 内存检查 | 观察者模式取消订阅后无泄漏 |
5.4 一个我踩过的坑
早期做代码评审时,我看到一个项目里用了七八种设计模式,觉得结构很漂亮。结果需求一变,发现模式之间耦合太紧,改一个工厂方法导致三个装饰器报错。从那以后我每次引入模式前都强制走一遍“变化点清单”:先列出所有可能变的地方,再决定用哪个模式,绝不为模式而模式。希望这套源码和上面的验收方法能帮你少走同样的弯路。
本文还有配套的精品资源,点击获取