C#.Net-设计模式-学习笔记
基于 GOF 23 种设计模式,结合 War3 / 银行 / 请假审批等实际场景讲解。
分三大类:创建型 、结构型 、行为型。
一、创建型设计模式
1. 简单工厂(Simple Factory)
核心思想 :把 new 对象的逻辑集中到一个工厂类,调用方只需传参,不用关心具体类型。
场景 :War3 游戏选种族,调用方不想写 new Human() / new Undead(),只想说"给我一个人族"。
csharp
// 调用方
IRace race = SimpleFactory.CreateInstance(SimpleFactory.RaceType.Human);
race.ShowKing();
csharp
// 工厂内部
public static IRace CreateInstance(RaceType raceType)
{
switch (raceType)
{
case RaceType.Human: return new Human();
case RaceType.Undead: return new Undead();
case RaceType.NE: return new NE();
case RaceType.ORC: return new ORC();
default: throw new Exception("wrong raceType");
}
}
进阶:配置文件 + 反射,连枚举都不用改,直接读配置文件里的类名,用反射创建实例:
csharp
Assembly assembly = Assembly.Load(configString.Split(',')[1]);
Type type = assembly.GetType(configString.Split(',')[0]);
return (IRace)Activator.CreateInstance(type);
缺点:新增种族必须改工厂类的 switch,违反开闭原则(对修改关闭)。简单工厂转移了矛盾,但没有消除矛盾,而且还集中了矛盾。
2. 工厂方法(Factory Method)
核心思想:定义一个工厂接口,每个具体产品对应一个具体工厂类,新增产品只需新增工厂,不改原有代码。
csharp
public interface IFactory
{
IRace CreateInstance();
}
public class HumanFactory : IFactory
{
public virtual IRace CreateInstance() => new Human(123, DateTime.Now, "123");
}
public class UndeadFactory : IFactory
{
public IRace CreateInstance() => new Undead();
}
调用方:
csharp
IFactory factory = new HumanFactory();
IRace race = factory.CreateInstance();
优点 :扩展新种族只加新工厂类,不动旧代码。缺点:每个产品都要一个工厂类,类数量膨胀。
3. 抽象工厂(Abstract Factory)
核心思想 :一个工厂不只生产一种产品,而是生产一族相关产品(种族 + 军队 + 资源)。
csharp
public abstract class AbstractFactoryBase
{
public abstract IRace CreatRace();
public abstract IArmy CreateArmy();
public abstract IResource CreateResource();
}
public class HumanFactoryAbstract : AbstractFactoryBase
{
public override IRace CreatRace() => new Human();
public override IArmy CreateArmy() => new HumanArmy();
public override IResource CreateResource() => new HumanResource();
}
调用方:
csharp
AbstractFactoryBase factory = new HumanFactoryAbstract();
IRace race = factory.CreatRace();
IArmy army = factory.CreateArmy();
IResource res = factory.CreateResource();
优点 :扩展新种族(产品族)很方便,加一个工厂类即可。缺点 :扩展新产品类型(比如加个 IHero)需要改所有工厂类,这叫**"倾斜性可扩展"**------对产品族扩展友好,对产品类型扩展不友好。
4. 单例模式(Singleton)
核心思想:保证一个类在整个程序生命周期内只被实例化一次,节省资源。
理解单例的必要性,可以想象一个构造函数非常耗时耗资源的类(比如内部要循环计算、建立连接),如果每次用到都 new 一个,性能会很差。单例确保只构造一次,后续复用同一个实例。
常见实现方式:
csharp
// 饿汉式(线程安全,程序启动就创建)
public class Singleton
{
private static readonly Singleton _instance = new Singleton();
private Singleton() { }
public static Singleton Instance => _instance;
}
csharp
// 懒汉式 + 双重检查锁(延迟创建,线程安全)
public class Singleton
{
private static volatile Singleton _instance;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
}
注意 :获取对象实例的几种方式:new → 反射 → IOC(反射) → 克隆 → 反序列化。
二、结构型设计模式
5. 适配器模式(Adapter)
核心思想 :已有一个接口规范(IHelper),但引入的第三方类(RedisHelper)方法名不一样,不能直接用。适配器就是"包一层",让第三方类也能满足接口规范。
场景 :系统统一用 IHelper(Add/Delete/Update/Query),但 Redis SDK 的方法叫 AddRedis/DeleteRedis,需要适配。
两种实现方式:
① 类适配器(继承):
csharp
public class RedisHelperInherit : RedisHelper, IHelper
{
public void Add<T>() => base.AddRedis<T>();
public void Delete<T>() => base.DeleteRedis<T>();
public void Update<T>() => base.UpdateRedis<T>();
public void Query<T>() => base.QueryRedis<T>();
}
缺点:继承是强侵入性,子类会暴露父类所有方法(包括 AddRedis 这种不该暴露的)。
② 对象适配器(组合):
csharp
public class RedisHelperCombination : IHelper
{
private RedisHelper _redisHelper = new RedisHelper();
public void Add<T>() => _redisHelper.AddRedis<T>();
public void Delete<T>() => _redisHelper.DeleteRedis<T>();
// ...
}
优点:组合可以面向抽象(依赖接口而非具体类),更灵活。
结论 :组合优于继承。继承是强侵入性、灵活性差,组合依赖抽象更好。
6. 代理模式(Proxy)
核心思想:不直接访问真实对象,通过代理对象来访问,代理可以在前后加入额外逻辑(日志、缓存、鉴权、事务等)。
场景 :RealSubject 是火车站(构造耗时、查票耗时),ProxySubject 是票务代理,帮你加了缓存和异常处理。
csharp
public class ProxySubject : ISubject
{
// 单例代理:RealSubject 只实例化一次
private static ISubject _iSubject = new RealSubject();
public void DoSomething()
{
try
{
Console.WriteLine("Before DoSomething");
_iSubject.DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
public bool GetSomething()
{
// 缓存代理:第一次查询后缓存结果
string key = $"{nameof(ProxySubject)}_{nameof(GetSomething)}";
if (CustomCache.Exist(key))
return CustomCache.Get<bool>(key);
bool result = _iSubject.GetSomething();
CustomCache.Add(key, result);
return result;
}
}
代理的常见用途:单例代理、缓存代理、延迟加载、鉴权、事务、监控。
重要:AOP(面向切面编程)大部分都是基于代理实现的。
7. 装饰器模式(Decorator)
核心思想 :在不修改原有类的前提下,动态地给对象添加功能,且功能的数量和顺序可以随意组合。
场景:学员上课,基础是直播学习,可以按需叠加:看视频、在线答疑、作业巩固。
关键设计 :装饰器既继承了 AbstractStudent(装饰后还是一个学生),又组合了一个 AbstractStudent(业务功能来自内置的学生)。
csharp
public abstract class AbstractDecorator : AbstractStudent
{
private AbstractStudent _student;
public AbstractDecorator(AbstractStudent student) { _student = student; }
public override void Study() => _student.Study(); // 委托给内部对象
}
public class StudentDecoratorVideo : AbstractDecorator
{
public StudentDecoratorVideo(AbstractStudent student) : base(student) { }
public override void Study()
{
base.Study(); // 先执行原有逻辑
Console.WriteLine("获取视频代码..."); // 再追加新功能
}
}
使用方式(像套娃一样叠加):
csharp
AbstractStudent student = new StudentVip() { Name = "张三" };
student = new StudentDecoratorVideo(student); // 加视频
student = new StudentDecoratorAnswer(student); // 加答疑
student = new StudentDecoratorHomework(student); // 加作业
student.Study(); // 执行时会依次调用所有功能
和代理的区别:
- 代理:控制对对象的访问,通常代理和被代理是固定关系
- 装饰器:动态扩展功能,可以任意叠加、调整顺序
三、行为型设计模式
8. 责任链模式(Chain of Responsibility)
核心思想:把多个处理者串成一条链,请求沿链传递,每个处理者决定自己处理还是传给下一个,调用方只需把请求交给链头。
场景:员工请假审批,PM 能批 8 小时内,主管批 24 小时内,经理批 100 小时内,超出就往上传。
csharp
public abstract class AbstractAuditor
{
private AbstractAuditor _nextAuditor;
public void SetNext(AbstractAuditor auditor) => _nextAuditor = auditor;
protected void AuditNext(ApplyContext ctx)
{
_nextAuditor?.Audit(ctx); // 传给下一个
}
public abstract void Audit(ApplyContext ctx);
}
public class PM : AbstractAuditor
{
public override void Audit(ApplyContext ctx)
{
if (ctx.Hour <= 8)
ctx.AuditResult = true;
else
base.AuditNext(ctx); // 超出权限,往上传
}
}
组装链(在调用方):
csharp
PM pm = new PM() { Name = "王国建" };
AbstractAuditor ceo = new CEO() { Name = "李哥" };
pm.SetNext(ceo); // 可以任意定制流程
pm.Audit(context); // 只需交给链头
优点:
- 调用方不需要知道谁来处理,只管提交
- 链的组装可以灵活配置,随时调整审批层级
- 每个处理者只关心自己的逻辑,互不依赖
责任链模式是行为型设计模式中扩展性最强的一个,核心思想是无止境的行为封装转移------每个节点只负责自己的判断,超出范围就往后传。
9. 模板方法模式(Template Method)
核心思想:在父类中定义一个算法的骨架(流程),把其中变化的步骤延迟到子类实现。
场景:银行查询余额,流程固定(验证 → 查余额 → 算利息 → 展示),但不同客户类型利率不同、展示方式不同。
csharp
public abstract class AbstractClient
{
// 模板方法:固定流程
public void Query(int id, string name, string password)
{
if (CheckUser(id, password))
{
double balance = QueryBalance(id);
double interest = CalculateInterest(balance); // 变化点
Show(name, balance, interest); // 变化点
}
}
public bool CheckUser(int id, string password) => true; // 公共逻辑,写死
public double QueryBalance(int id) => new Random().Next(10000, 1000000); // 公共逻辑
public abstract double CalculateInterest(double balance); // 必须子类实现
public virtual void Show(string name, double balance, double interest) // 可选覆盖
{
Console.WriteLine($"尊敬的{name},余额:{balance},利息:{interest}");
}
}
子类只需实现变化的部分:
csharp
public class ClientVip : AbstractClient
{
public override double CalculateInterest(double balance) => balance * 0.005;
public override void Show(string name, double balance, double interest)
=> Console.WriteLine($"尊贵的{name} VIP客户,余额:{balance},利息:{interest}");
}
public class ClientRegular : AbstractClient
{
public override double CalculateInterest(double balance) => balance * 0.003;
// Show 不覆盖,用父类默认实现
}
三种方法的使用规则:
- 所有子类都相同 → 普通方法,写在父类
- 所有子类都不同 →
abstract方法,子类必须 override - 部分子类不同 →
virtual方法,按需 override
10. 观察者模式(Observer)
核心思想 :被观察者(Subject)维护一个观察者列表,当自身状态变化时,通知所有观察者。观察者和被观察者之间松耦合。
场景:猫叫一声,触发老鼠跑、狗叫、婴儿哭、小偷躲......
演进过程:
第一版(硬编码,强依赖):
csharp
public void Miao()
{
new Mouse().Run();
new Baby().Cry();
new Dog().Wang();
// 猫直接依赖所有观察者,耦合严重
}
第二版(观察者接口 + List):
csharp
public interface IObserver { void Action(); }
// Cat 内部
private List<IObserver> observerList = new List<IObserver>();
public void AddObserver(IObserver observer) => observerList.Add(observer);
public void MiaoObserver()
{
foreach (var observer in observerList)
observer.Action(); // 猫不知道谁在监听,只管通知
}
第三版(C# 委托/事件,最优雅):
csharp
public event Action MiaoHandler;
public void MiaoEvent()
{
foreach (Action observer in MiaoHandler?.GetInvocationList())
observer.Invoke();
}
// 调用方注册
cat.MiaoHandler += new Baby().Cry;
cat.MiaoHandler += new Dog().Wang;
cat.MiaoEvent();
优点:
- 观察者数量可以随意增减
- 顺序可以调整
- 被观察者不依赖任何具体观察者
四、三种模式的对比(容易混淆)
| 模式 | 关系 | 目的 |
|---|---|---|
| 适配器 | 包一层,接口转换 | 让不兼容的接口能一起工作 |
| 代理 | 包一层,控制访问 | 在访问前后加通用逻辑(日志/缓存/鉴权) |
| 装饰器 | 包一层,动态扩展 | 灵活叠加功能,顺序可调 |
适配器、代理、装饰器三者结构相似,区别在于意图不同。
五、核心设计原则(贯穿所有模式)
- 面向抽象编程 :依赖接口/抽象类,不依赖具体实现(
IRace iRace = new Human()而不是Human human = new Human()) - 开闭原则:对扩展开放,对修改关闭
- 组合优于继承:继承是强侵入性,组合更灵活
- 单一职责:每个类只做一件事,变化的逻辑封装转移出去
- 里氏替换原则:子类可以替换父类使用