14-C#

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())
  • 开闭原则:对扩展开放,对修改关闭
  • 组合优于继承:继承是强侵入性,组合更灵活
  • 单一职责:每个类只做一件事,变化的逻辑封装转移出去
  • 里氏替换原则:子类可以替换父类使用
相关推荐
勇敢牛牛_2 小时前
【aiway】基于 Rust 开发的 API + AI 网关
开发语言·后端·网关·ai·rust
khddvbe2 小时前
C++中的代理模式实战
开发语言·c++·算法
计算机安禾2 小时前
【C语言程序设计】第31篇:指针与函数
c语言·开发语言·数据结构·c++·算法·leetcode·visual studio
kaikaile19952 小时前
庞加莱截面计算MATLAB程序
开发语言·matlab
ECT-OS-JiuHuaShan2 小时前
朱梁万有递归元定理,解构西方文明中心论幻觉
开发语言·人工智能·php
Aubrey-J2 小时前
练习开发Skill——网页内容抓取Skill(website-content-fetch)
开发语言·人工智能
handler012 小时前
基础算法:分治
c语言·开发语言·c++·笔记·学习·算法·深度优先
2501_924952693 小时前
设计模式在C++中的实现
开发语言·c++·算法
大傻^3 小时前
LangChain4j 1.4.0 快速入门:JDK 11+ 基线迁移与首个 AI Service 构建
java·开发语言·人工智能