AIGC标识 设计模式总结(GoF 23 种)

设计模式总结(GoF 23 种)

基于 GoF《设计模式:可复用面向对象软件的基础》,分类整理三大类 23 种模式。每个模式包含:一句话概括、结构图、代码示例、使用时机与反模式、相关模式。


使用原则(读前必看)

在进入 23 种模式之前,先确认你不会误用它们:

  1. 先有具体问题,再套模式。 不要拿着锤子找钉子。如果你说不清"不用模式会怎样",就还不需要模式。
  2. 最简单的方案优先。 如果普通 new、一个 if-else、一个 foreach 就能解决——就用它们。模式是来消除复杂度的,不是来增加复杂度的。
  3. 模式是用来沟通的词汇。 说"这里用策略模式"比说"我定义了一个接口然后用三个类实现它然后在运行时选一个"快十倍。模式让团队沟通高效,不是用来炫技的。
  4. 关注你引入的复杂度。 如果加了模式后类数量翻倍、调用深度增加三层、但解决的问题只是"未来可能会变"——你过度设计了。

标注说明:

标注 含义
⭐⭐⭐ 日常必懂——项目中频繁出现,面试高频
⭐⭐ 场景触发——特定场景下价值巨大
遇到了再看——不常用但解决特定痛点
🟢 结构简单,上手快
🟡 需要一点理解成本
🔴 需要较多背景知识

目录


一、创建型模式(5 种)

关注如何创建对象——将对象的创建与使用分离。


1. 工厂方法(Factory Method) ⭐⭐⭐ | 🟡

一句话: 父类定义创建接口,子类决定 new 哪个。

触发信号: 有几种变体,用哪种由运行时配置决定,调用方不想知道具体类名。

classDiagram class Creator { <<abstract>> +FactoryMethod() Product +GetLogger() Logger } class ConcreteCreatorA { +FactoryMethod() Product } class ConcreteCreatorB { +FactoryMethod() Product } class Product { <<abstract>> } class ConcreteProductA class ConcreteProductB Creator <|-- ConcreteCreatorA Creator <|-- ConcreteCreatorB Product <|-- ConcreteProductA Product <|-- ConcreteProductB ConcreteCreatorA ..> ConcreteProductA : creates ConcreteCreatorB ..> ConcreteProductB : creates
// ── 抽象 Product ──
public abstract class Logger
{
    public abstract void Write(string level, string message);
    public void Info(string msg)  => Write("INFO",  msg);
    public void Error(string msg) => Write("ERROR", msg);
}

// ── 具体 Product ──
public class ConsoleLogger : Logger
{
    public override void Write(string level, string message)
        => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] [{level}] {message}");
}

public class DatabaseLogger : Logger
{
    private readonly string _connStr;
    public DatabaseLogger(string connStr) => _connStr = connStr;

    public override void Write(string level, string message)
        => Console.WriteLine($"  → INSERT INTO logs VALUES ('{level}', '{message}') [{_connStr}]");
}

// ── 抽象 Creator ──
public abstract class LoggerFactory
{
    protected abstract Logger CreateLogger();   // ← Factory Method

    private Logger? _cached;
    public Logger GetLogger()
    {
        _cached ??= CreateLogger();   // 懒初始化 + 缓存
        return _cached;
    }
}

// ── 具体 Creator ──
public class ConsoleLoggerFactory : LoggerFactory
{
    protected override Logger CreateLogger() => new ConsoleLogger();
}

public class DatabaseLoggerFactory : LoggerFactory
{
    private readonly string _connStr;
    public DatabaseLoggerFactory(string connStr) => _connStr = connStr;
    protected override Logger CreateLogger() => new DatabaseLogger(_connStr);
}

// ── Client —— 只依赖抽象 ──
LoggerFactory factory = new DatabaseLoggerFactory("Server=prod;Database=logs");
Logger logger = factory.GetLogger();
logger.Error("支付超时: order=8823");

❌ 不该用的信号:

  • 只有一种产品变体 → 直接 new 就行
  • 工厂本身比创建逻辑还复杂 → 过度设计

🔗 相关模式: 抽象工厂(工厂方法的升级版) · 模板方法(都用继承,方向不同)


2. 抽象工厂(Abstract Factory) ⭐⭐ | 🟡

一句话: 创建一族配套产品,保证整组一致性。

触发信号: 多套产品族,切换时整套一起换。同一产品族的组件必须配套,跨产品族混搭会出 bug。

classDiagram class AbstractFactory { <<interface>> +CreateProductA() AbstractProductA +CreateProductB() AbstractProductB } class ConcreteFactory1 { +CreateProductA() ProductA1 +CreateProductB() ProductB1 } class ConcreteFactory2 { +CreateProductA() ProductA2 +CreateProductB() ProductB2 } class AbstractProductA { <<interface>> } class AbstractProductB { <<interface>> } class ProductA1 class ProductB1 class ProductA2 class ProductB2 AbstractFactory <|.. ConcreteFactory1 AbstractFactory <|.. ConcreteFactory2 AbstractProductA <|.. ProductA1 AbstractProductA <|.. ProductA2 AbstractProductB <|.. ProductB1 AbstractProductB <|.. ProductB2 ConcreteFactory1 ..> ProductA1 : creates ConcreteFactory1 ..> ProductB1 : creates ConcreteFactory2 ..> ProductA2 : creates ConcreteFactory2 ..> ProductB2 : creates
// ── 抽象产品族 ──
public interface IDbConnection { void Open(); void Close(); }
public interface IDbCommand    { void CommandText(string sql); IDbReader ExecuteReader(); }
public interface IDbReader     : IDisposable { bool Read(); object GetValue(int column); }

// ── 抽象工厂 —— 声明创建一族产品的方法 ──
public interface IDbFactory
{
    IDbConnection CreateConnection(string connectionString);
    IDbCommand    CreateCommand(IDbConnection connection);
}

// ── SQL Server 产品族 ──
public class SqlConnection : IDbConnection { /* ... */ }
public class SqlCommand    : IDbCommand    { /* ... */ }

// ── PostgreSQL 产品族 ──
public class PgConnection : IDbConnection { /* ... */ }
public class PgCommand    : IDbCommand    { /* ... */ }

// ── 具体工厂 —— 每个工厂保证整族产品配套 ──
public class SqlServerFactory : IDbFactory
{
    public IDbConnection CreateConnection(string cs) => new SqlConnection(cs);
    public IDbCommand    CreateCommand(IDbConnection c) => new SqlCommand(c);
}

public class PostgresFactory : IDbFactory
{
    public IDbConnection CreateConnection(string cs) => new PgConnection(cs);
    public IDbCommand    CreateCommand(IDbConnection c) => new PgCommand(c);
}

// ── Client —— 只依赖抽象,完全不感知数据库类型 ──
public class ReportService
{
    private readonly IDbFactory _factory;

    public ReportService(IDbFactory factory) => _factory = factory;

    public void RunQuery(string sql, string connStr)
    {
        using IDbConnection conn = _factory.CreateConnection(connStr);
        conn.Open();
        IDbCommand cmd = _factory.CreateCommand(conn);
        cmd.CommandText(sql);
        using IDbReader reader = cmd.ExecuteReader();
        while (reader.Read()) { /* 读取数据 */ }
        conn.Close();
    }
}

var sqlService = new ReportService(new SqlServerFactory());
var pgService  = new ReportService(new PostgresFactory());

❌ 不该用的信号:

  • 只有一种产品 → 工厂方法就够
  • 产品族之间不需要配套 → 不存在"混搭风险",抽象工厂的核心价值不成立

🔗 相关模式: 工厂方法(抽象工厂用工厂方法声明创建) · 桥接(都分离两个维度)


3. 生成器(Builder) ⭐⭐ | 🟡

一句话: 把复杂构造拆成步骤,相同步骤产出不同表示。

触发信号: 构造函数 10 个参数,一半可选,参数之间有依赖关系。

classDiagram class Director { +Construct(IBuilder) } class IBuilder { <<interface>> +BuildPart1() +BuildPart2() +GetResult() Product } class ConcreteBuilderA { +BuildPart1() +BuildPart2() +GetResult() ProductA } class ConcreteBuilderB { +BuildPart1() +BuildPart2() +GetResult() ProductB } class ProductA class ProductB Director --> IBuilder IBuilder <|.. ConcreteBuilderA IBuilder <|.. ConcreteBuilderB ConcreteBuilderA ..> ProductA : creates ConcreteBuilderB ..> ProductB : creates
// ── Product ──
public class HttpRequest
{
    public string                     Url      { get; set; } = "";
    public HttpMethod                 Method   { get; set; } = HttpMethod.Get;
    public Dictionary<string, string> Headers  { get; set; } = new();
    public string?                    Body     { get; set; }
    public int                        TimeoutSeconds { get; set; } = 30;
    public bool                       FollowRedirects { get; set; } = true;
    public int                        MaxRetries      { get; set; }
}

// ── Fluent Builder —— 每个 Setter 返回 this ──
public class HttpRequestBuilder
{
    private readonly HttpRequest _request = new();

    public HttpRequestBuilder WithUrl(string url) { _request.Url = url; return this; }
    public HttpRequestBuilder AsGet()  { _request.Method = HttpMethod.Get;  return this; }
    public HttpRequestBuilder AsPost() { _request.Method = HttpMethod.Post; return this; }
    public HttpRequestBuilder WithHeader(string k, string v)
        { _request.Headers[k] = v; return this; }

    public HttpRequestBuilder WithJsonBody(string json)
    {
        _request.Body = json;
        return WithHeader("Content-Type", "application/json");
    }

    public HttpRequestBuilder WithAuth(string token)
        => WithHeader("Authorization", $"Bearer {token}");

    public HttpRequestBuilder WithTimeout(int secs)
        { _request.TimeoutSeconds = secs; return this; }

    public HttpRequestBuilder WithoutRedirects()
        { _request.FollowRedirects = false; return this; }

    public HttpRequestBuilder WithRetries(int count)
        { _request.MaxRetries = count; return this; }

    public HttpRequest Build() => _request;
}

// ── Client — 链式调用,意图一目了然 ──
var request = new HttpRequestBuilder()
    .WithUrl("https://api.example.com/orders")
    .AsPost()
    .WithJsonBody("""{"product":"widget","qty":100}""")
    .WithAuth("abc123")
    .WithTimeout(60)
    .WithoutRedirects()
    .WithRetries(3)
    .Build();

❌ 不该用的信号:

  • 参数只有 2-3 个且都是必填 → 普通构造函数就够
  • 只是给属性赋值,没有构造逻辑 → 对象初始化器 new X { A=1, B=2 } 即可

🔗 相关模式: 抽象工厂(创建产品族 vs 分步构建) · 模板方法(Director 其实是模板方法)


4. 原型(Prototype) ⭐⭐ | 🟢

一句话: 复制已有对象而不是 new,省去重建成本。

触发信号: 对象的创建成本很高(初始化、I/O、计算),但你需要大量只有微小差异的副本。

classDiagram class PrototypeRegistry { -_prototypes: Dictionary +Create(key) IPrototype } class IPrototype { <<interface>> +Clone() T } class ConcretePrototype { +Clone() ConcretePrototype } PrototypeRegistry --> IPrototype IPrototype <|.. ConcretePrototype
// ── 原型接口 ──
public interface IPrototype<T> { T Clone(); }

// ── 原型对象(构建代价高) ──
public class Report : IPrototype<Report>
{
    public string      Title       { get; set; } = "";
    public string      Author      { get; set; } = "";
    public string      Body        { get; set; } = "";
    public List<string> DataSources { get; set; } = new();

    public void LoadTemplate()
    {
        Console.WriteLine("  [DB] 查询模板…(耗时 500ms)");  // 模拟昂贵操作
        Author = "系统自动生成";
    }

    // 深拷贝引用字段
    public Report Clone() => new()
    {
        Title       = this.Title,
        Author      = this.Author,
        Body        = this.Body,
        DataSources = new List<string>(this.DataSources)  // ← 关键:深拷贝
    };
}

// ── Client —— 构建一次,克隆多次 ──
var template = new Report();
template.LoadTemplate();

foreach (var region in new[] { "华北", "华东", "华南" })
{
    var r = template.Clone();     // ← 原型模式,几乎零成本
    r.Title = $"Q2 销售报告 — {region}大区";
    reports.Add(r);
}

❌ 不该用的信号:

  • 对象创建成本低 → 直接 new 更简单
  • 对象没有复杂内部状态 → 不需要克隆
  • 深浅拷贝没处理好 → 克隆出的对象和原对象共享引用,比 bug 更难排查

🔗 相关模式: 备忘录(都保存状态,但原型是"复制后微调用于创建",备忘录是"快照后用于回滚") · 工厂方法(原型通过克隆创建,工厂方法通过 new 创建)


5. 单例(Singleton) ⭐⭐⭐ | 🟢

一句话: 整个进程只有一份实例。

触发信号: 有些东西多了反而坏事——全局只能有一份。两份意味着两份不一致的状态。

classDiagram class Singleton { -static _instance$ : Singleton -Singleton() +static Instance$ : Singleton }
// ── .NET 推荐写法:Lazy<T>(一行搞定线程安全懒加载) ──
public sealed class ConfigManager
{
    private static readonly Lazy<ConfigManager> _lazy = new(() => new ConfigManager());
    public static ConfigManager Instance => _lazy.Value;

    private readonly Dictionary<string, string> _values = new();

    private ConfigManager()
    {
        _values["db.host"]   = "prod-db.corp.com";
        _values["log.level"] = "warn";
    }

    public string Get(string key, string fallback = "")
        => _values.TryGetValue(key, out var v) ? v : fallback;

    public void Set(string key, string value) => _values[key] = value;
}

// ── 使用 ──
var cm1 = ConfigManager.Instance;
var cm2 = ConfigManager.Instance;
Console.WriteLine(ReferenceEquals(cm1, cm2));  // True

// ── 泛型基类:复用单例逻辑 ──
public abstract class Singleton<T> where T : class, new()
{
    private static readonly Lazy<T> _lazy = new(() => new T());
    public static T Instance => _lazy.Value;
}

// ── 使用:继承即单例 ──
public sealed class AuditService : Singleton<AuditService>
{
    // ⚠ 构造函数
    //    用 sealed + 非公开构造 + 文档约定来防止外部 new
    private AuditService() { }

    public void Log(string action)
        => Console.WriteLine($"[审计] {DateTime.Now:T} {action}");
}

public sealed class ConnectionPool : Singleton<ConnectionPool>
{
    private ConnectionPool() { }

    public string GetConnection()
    {
        Console.WriteLine("从池中获取连接");
        return "conn-42";
    }
}

// ── 使用:类型安全,一行即单例 ──
AuditService.Instance.Log("用户登录");
var conn = ConnectionPool.Instance.GetConnection();
Console.WriteLine(ReferenceEquals(
    AuditService.Instance,
    AuditService.Instance));  // True

❌ 不该用的信号:

  • "只有一个"的理由是"目前只需要一个" → 用 DI 注册为 Singleton 生命周期,方便未来更换
  • 为了省传参 → 用依赖注入
  • 单例持有可变全局状态 → 不可测试、不可推理,用不可变配置 + 传递

🔗 相关模式: 享元(都复用实例,享元是一组共享对象,单例是唯一一个) · 外观(外观类常做成单例)


二、结构型模式(7 种)

关注如何组合类和对象——形成更大的结构。


6. 适配器(Adapter) ⭐⭐⭐ | 🟢

一句话: 把不兼容的接口变成兼容的。

触发信号: 第三方 SDK 的接口和我们的统一接口对不上,又不能改源码。

classDiagram class ITarget { <<interface>> +Request() } class Adapter { -_adaptee: Adaptee +Request() } class Adaptee { +SpecificRequest() } ITarget <|.. Adapter Adapter --> Adaptee
// ── Target —— 你系统自己的统一接口 ──
public interface IPaymentGateway
{
    PaymentResult Pay(decimal amount, string orderId);
}

// ── Adaptee —— 第三方 SDK(接口各不一样,不能改) ──
public class WechatPaySDK
{
    public string CreateOrder(decimal amount, string desc) => "WX_xxx";
    public (bool ok, string msg) ExecutePay(string prepayId) => (true, "OK");
}

public class AlipaySDK
{
    public record AlipayResponse(string Code, string Message, string TradeNo);
    public AlipayResponse DoPayment(string outTradeNo, string totalAmount, string subject)
        => new("10000", "Success", "ALI_xxx");
}

// ── Adapter —— 翻译参数 + 返回值 ──
public class WechatPayAdapter : IPaymentGateway
{
    private readonly WechatPaySDK _sdk;
    public WechatPayAdapter(WechatPaySDK sdk) => _sdk = sdk;

    public PaymentResult Pay(decimal amount, string orderId)
    {
        string prepayId = _sdk.CreateOrder(amount, orderId);
        var (ok, msg)   = _sdk.ExecutePay(prepayId);
        return new PaymentResult { Success = ok, TransactionId = prepayId };
    }
}

public class AlipayAdapter : IPaymentGateway
{
    private readonly AlipaySDK _sdk;
    public AlipayAdapter(AlipaySDK sdk) => _sdk = sdk;

    public PaymentResult Pay(decimal amount, string orderId)
    {
        var resp = _sdk.DoPayment(orderId, amount.ToString("F2"), $"订单 {orderId}");
        return new PaymentResult { Success = resp.Code == "10000", TransactionId = resp.TradeNo };
    }
}

// ── Client —— 只跟统一接口打交道 ──
IPaymentGateway gateway = new WechatPayAdapter(new WechatPaySDK());
gateway.Pay(199.00m, "ORD-001");

❌ 不该用的信号:

  • 接口本来就一致 → 不需要翻译
  • 能直接改 Adaptee 源码 → 改源码更干净(适配器是补救措施,不是首选)

🔗 相关模式: 外观(外观简化接口,适配器转换接口) · 桥接(桥接是设计时分离,适配器是补救时翻译)


7. 桥接(Bridge) ⭐⭐ | 🔴

一句话: 抽象和实现拆成两个独立维度。

触发信号: M 种消息 × N 种渠道 → 需要 M+N 个类而不是 M×N 个。

classDiagram direction LR class Abstraction { <<abstract>> -_impl: Implementor +Operation() } class RefinedA class RefinedB class Implementor { <<interface>> +OperationImpl() } class ConcreteA class ConcreteB Abstraction <|-- RefinedA Abstraction <|-- RefinedB Abstraction --> Implementor : 桥 Implementor <|.. ConcreteA Implementor <|.. ConcreteB
// ── Implementor —— 发送渠道(变化维度一) ──
public interface IMessageSender
{
    void Send(string recipient, string content);
    string ChannelName { get; }
}

public class EmailSender : IMessageSender
{
    public string ChannelName => "Email";
    public void Send(string recipient, string content)
        => Console.WriteLine($"  SMTP → {recipient}: {content}");
}

public class SmsSender : IMessageSender
{
    public string ChannelName => "SMS";
    public void Send(string recipient, string content)
        => Console.WriteLine($"  网关 → {recipient}: {content[..Math.Min(content.Length, 70)]}");
}

// ── Abstraction —— 消息类型(变化维度二) ──
public abstract class Message
{
    protected readonly IMessageSender Sender;  // ← 桥

    protected Message(IMessageSender sender) => Sender = sender;

    public string Recipient { get; init; } = "";

    public void Dispatch()
    {
        string formatted = FormatContent();
        Sender.Send(Recipient, formatted);     // 桥接处:委托给渠道
    }

    protected abstract string FormatContent();
}

// ── Refined Abstraction ──
public class AlertMessage : Message
{
    public string Level { get; init; } = "WARN";
    public AlertMessage(IMessageSender sender) : base(sender) { }

    protected override string FormatContent() => $"[{Level}] 系统告警,请立即处理";
}

public class OtpMessage : Message
{
    public string Code { get; init; } = "";
    public OtpMessage(IMessageSender sender) : base(sender) { }

    protected override string FormatContent() => $"验证码 {Code},请勿泄露";
}

// ── Client —— 自由组合 ──
new AlertMessage(new EmailSender()) { Recipient = "ops@corp.com" }.Dispatch();
new OtpMessage(new SmsSender())     { Recipient = "+861***8001", Code = "847291" }.Dispatch();

❌ 不该用的信号:

  • 只有一个维度在变化 → 用继承就够
  • 两个维度但不需要独立演进 → 桥接引入额外分层导致代码难以追踪

🔗 相关模式: 装饰(装饰纵向叠加能力,桥接横向分离维度) · 适配器(适配器补救已有代码,桥接设计时预先分离) · 策略(策略是运行时换算法,桥接是设计时拆维度)


8. 组合(Composite) ⭐⭐ | 🟡

一句话: 单个对象和对象集合对外接口一模一样。

触发信号: 一棵树上的任意节点,对外暴露出完全相同的接口,调用方不关心里面是叶子还是嵌套的枝干。

classDiagram class Component { <<abstract>> +GetSize() long +Print(indent) } class Leaf { +GetSize() long } class Composite { -_children: List[Component] +GetSize() long +Add(Component) } Component <|-- Leaf Component <|-- Composite Composite o-- Component : children
// ── Component ──
public abstract class FileSystemNode
{
    public string Name { get; }
    protected FileSystemNode(string name) => Name = name;

    public abstract long GetSize();
    public abstract void Print(string indent = "");
}

// ── Leaf:文件 ──
public class File : FileSystemNode
{
    private readonly long _size;
    public File(string name, long size) : base(name) => _size = size;

    public override long GetSize() => _size;  // ← 自身值
    public override void Print(string indent = "")
        => Console.WriteLine($"{indent}📄 {Name} ({_size} B)");
}

// ── Composite:目录 ──
public class Directory : FileSystemNode
{
    private readonly List<FileSystemNode> _children = new();

    public Directory(string name) : base(name) { }
    public void Add(FileSystemNode node) => _children.Add(node);

    // ← 递归汇总,不区分子节点是 File 还是 Directory
    public override long GetSize() => _children.Sum(child => child.GetSize());

    public override void Print(string indent = "")
    {
        Console.WriteLine($"{indent}📁 {Name}/ ({GetSize()} B)");
        foreach (var child in _children)
            child.Print(indent + "  ");
    }
}

// ── Client —— 同一段代码处理单文件和整个目录 ──
var src = new Directory("src");
src.Add(new File("Program.cs", 4520));
src.Add(new File("Utils.cs", 2300));

var models = new Directory("Models");
models.Add(new File("User.cs", 1800));
src.Add(models);  // ← 目录嵌套目录,都是 FileSystemNode

src.Print();                          // 递归输出整棵树
Console.WriteLine($"总大小: {src.GetSize()} B");  // 自动汇总子树

❌ 不该用的信号:

  • 结构不是树形 → 组合的核心前提不成立
  • 叶子节点和组合节点行为差异太大 → 强行统一接口导致叶子节点大量空抛出

🔗 相关模式: 装饰(装饰也递归嵌套,但意图是叠加能力而非统一单体与聚合) · 责任链(都形成链式结构,方向不同)


9. 装饰(Decorator) ⭐⭐⭐ | 🟡

一句话: 运行时一层一层叠加能力。

触发信号: 有时候要日志,有时候要重试,有时候两样都要——不想写 2ⁿ 个类。

classDiagram class IComponent { <<interface>> +Operation() } class ConcreteComponent { +Operation() } class BaseDecorator { -_inner: IComponent +Operation() } class DecoratorA { +Operation() } class DecoratorB { +Operation() } IComponent <|.. ConcreteComponent IComponent <|.. BaseDecorator BaseDecorator <|-- DecoratorA BaseDecorator <|-- DecoratorB BaseDecorator --> IComponent : _inner
// ── Component ──
public interface INotifier
{
    string Name { get; }
    bool Send(string recipient, string message);
}

// ── Concrete Component ──
public class EmailNotifier : INotifier
{
    public string Name => "Email";
    public bool Send(string recipient, string message)
    {
        Console.WriteLine($"    → SMTP 发送至 {recipient}: {message}");
        return true;
    }
}

// ── Base Decorator —— 实现 Component + 持有 Component ──
public abstract class NotifierDecorator : INotifier
{
    protected readonly INotifier Inner;
    protected NotifierDecorator(INotifier inner) => Inner = inner;

    public string Name => Inner.Name;
    public virtual bool Send(string recipient, string message)
        => Inner.Send(recipient, message);  // 默认透传
}

// ── Concrete Decorators —— 每种增强是独立包装 ──
public class LoggingDecorator : NotifierDecorator
{
    public LoggingDecorator(INotifier inner) : base(inner) { }

    public override bool Send(string recipient, string message)
    {
        Console.WriteLine($"  [LOG]  开始发送 → {recipient}");
        bool ok = base.Send(recipient, message);
        Console.WriteLine($"  [LOG]  结果: {(ok ? "✓" : "✗")}");
        return ok;
    }
}

public class RetryDecorator : NotifierDecorator
{
    private readonly int _maxRetries;
    public RetryDecorator(INotifier inner, int maxRetries = 3) : base(inner)
        => _maxRetries = maxRetries;

    public override bool Send(string recipient, string message)
    {
        for (int i = 0; i <= _maxRetries; i++)
            if (base.Send(recipient, message)) return true;
        return false;
    }
}

// ── Client —— 运行时自由堆叠 ──
INotifier withLogAndRetry =
    new LoggingDecorator(
        new RetryDecorator(
            new EmailNotifier(), maxRetries: 2));

withLogAndRetry.Send("admin@corp.com", "服务器 CPU 告警");

// .NET 自带就是装饰模式:
Stream file = new GZipStream(
    new BufferedStream(
        new FileStream("data.gz", FileMode.Open)),
    CompressionMode.Decompress);

❌ 不该用的信号:

  • 只加一种增强,且未来不会变 → 直接写在原类里更简单
  • 装饰器的顺序不重要 → 那它的价值就是装饰器可以自由排列——如果顺序是固定的,可能组合或继承更简单

🔗 相关模式: 代理(结构相同,代理控制访问,装饰增强功能) · 责任链(都形成链,责任链每个节点可能终止,装饰始终透传)


10. 外观(Facade) ⭐⭐⭐ | 🟢

一句话: 给复杂子系统开一扇简单的门。

触发信号: 调三个类写八行代码才能干一件事,封成一个方法。

classDiagram class Facade { +DoSomethingSimple() } class SubSystemA class SubSystemB class SubSystemC Facade --> SubSystemA Facade --> SubSystemB Facade --> SubSystemC note "Facade: 对外的简单入口" note "SubSystemA: 复杂子系统"
// ── 子系统(各自有复杂接口) ──
public class VideoCodec
{
    public void LoadPreset(string p) => Console.WriteLine($"  加载预设: {p}");
    public void SetBitrate(int kbps) => Console.WriteLine($"  码率: {kbps} Kbps");
    public string Encode(string input, string output, string format) { return output; }
}

public class FormatDetector
{
    public string DetectContainer(string path) => ".mp4";
    public string DetectCodec(string c) => "h264";
    public bool IsHardwareAccelerated(string c) => true;
}

public class WatermarkEngine
{
    public void LoadWatermark(string img) => Console.WriteLine($"  水印: {img}");
    public void SetPosition(string pos) => Console.WriteLine($"  位置: {pos}");
    public string Apply(string i, string o) => o;
}

// ── Facade —— 三行方法,背后五个子系统协作 ──
public class VideoProcessor
{
    private readonly VideoCodec      _codec     = new();
    private readonly FormatDetector  _detector  = new();
    private readonly WatermarkEngine _watermark = new();

    public string Transcode(string input, string outputFormat)
    {
        var container = _detector.DetectContainer(input);
        var codec     = _detector.DetectCodec(container);
        var hw        = _detector.IsHardwareAccelerated(codec);

        _codec.LoadPreset(hw ? "hardware-fast" : "software-quality");
        _codec.SetBitrate(hw ? 8000 : 5000);
        return _codec.Encode(input,
            Path.ChangeExtension(input, outputFormat), outputFormat);
    }

    public string Watermark(string video, string watermarkImage)
    {
        _watermark.LoadWatermark(watermarkImage);
        _watermark.SetPosition("bottom-right");
        return _watermark.Apply(video,
            Path.GetFileNameWithoutExtension(video) + "_wm.mp4");
    }
}

// ── Client —— 不知道子系统存在 ──
var processor = new VideoProcessor();
processor.Transcode("raw.mkv", ".mp4");
processor.Watermark("raw.mp4", "logo.png");

❌ 不该用的信号:

  • 子系统本身不复杂 → 外观是多余的中间层
  • 外观成了"什么都往里塞"的 God Object → 拆分子系统

🔗 相关模式: 适配器(外观简化接口,适配器转换接口) · 中介者(中介者协调同事间交互,外观简化单方向调用)


11. 享元(Flyweight) ⭐⭐ | 🟡

一句话: 大量对象的公共部分只存一份。

触发信号: 海量对象,寥寥几种配置。把重复的部分抽成共享池,每个对象只存自己独特的那一点数据。

classDiagram class FlyweightFactory { -_cache: Dictionary +GetFlyweight(key) Flyweight } class Flyweight { +Family +Size +Bold +Color } class Context { +C: char +X: int +Y: int +Font: Flyweight } FlyweightFactory --> Flyweight : creates & caches Context --> Flyweight : references
// ── Flyweight —— 内在状态(可共享、不可变) ──
public class FontFlyweight
{
    public string Family { get; }
    public int    Size   { get; }
    public bool   Bold   { get; }
    public string Color  { get; }

    public FontFlyweight(string family, int size, bool bold, string color)
        { Family = family; Size = size; Bold = bold; Color = color; }
}

// ── FlyweightFactory —— 保证同一种字体只创建一次 ──
public class FontFactory
{
    private readonly Dictionary<string, FontFlyweight> _cache = new();

    public FontFlyweight GetFont(string family, int size, bool bold, string color)
    {
        string key = $"{family}|{size}|{bold}|{color}";
        if (!_cache.TryGetValue(key, out var font))
        {
            font = new FontFlyweight(family, size, bold, color);
            _cache[key] = font;
        }
        return font;
    }
}

// ── Context —— 外在状态(每个对象不同,持有 Flyweight 引用) ──
public class GlyphContext
{
    public char C    { get; }     // 外在状态
    public int  X    { get; }     // 外在状态
    public FontFlyweight Font { get; }  // ← 指向共享的 Flyweight

    public GlyphContext(char c, int x, FontFlyweight font)
        { C = c; X = x; Font = font; }
}

// ── Client ──
var factory   = new FontFactory();
var bodyFont  = factory.GetFont("Microsoft YaHei", 14, false, "Black");

var glyphs = new List<GlyphContext>();
for (int i = 0; i < 10000; i++)
    glyphs.Add(new GlyphContext('A', i * 8, bodyFont));  // 10000 个共享 1 个 Font

// 内存:10000 × 40(Context)+ 1 × 200(Flyweight)≈ 400KB
// 不用享元:10000 × (40 + 200) ≈ 2.4MB

❌ 不该用的信号:

  • 对象数量不大(< 1000) → 共享带来的代码复杂度不值得
  • 共享部分会变 → Flyweight 必须不可变,否则一个修改影响所有引用者
  • 三个条件缺一不可:量大 + 重复多 + 共享部分不可变

🔗 相关模式: 单例(单例是一份,享元是共享池里的多份) · 组合(享元的 Context + Flyweight 可以组合进树结构)


12. 代理(Proxy) ⭐⭐⭐ | 🟡

一句话: 控制对另一个对象的访问。

触发信号: 这个对象创建太贵/需要权限校验/不想重复查——配个代理人挡在外面。

classDiagram class ISubject { <<interface>> +Operation() } class Proxy { -_real: RealSubject +Operation() } class RealSubject { +Operation() } ISubject <|.. Proxy ISubject <|.. RealSubject Proxy --> RealSubject : lazy init
// ── Subject ──
public interface IImage
{
    string FileName { get; }
    void   Display();
}

// ── RealSubject(创建代价高) ──
public class HighResImage : IImage
{
    public string FileName { get; }

    public HighResImage(string fileName)
    {
        FileName = fileName;
        Console.WriteLine($"  加载 {fileName}...");  // 模拟 I/O
    }

    public void Display() => Console.WriteLine($"  渲染 {FileName}");
}

// ── 虚拟代理:延迟创建 ──
public class LazyImageProxy : IImage
{
    private HighResImage? _realImage;
    public string FileName { get; }

    public LazyImageProxy(string fileName) => FileName = fileName;

    public void Display()
    {
        _realImage ??= new HighResImage(FileName);  // ← 只在真正需要时创建
        _realImage.Display();
    }
}

// ── 保护代理:权限控制 ──
public class DocumentAccessProxy : IDocumentService
{
    private readonly RealDocumentService _service = new();
    private readonly HashSet<string> _allowedRoles;
    public string CurrentRole { get; set; } = "viewer";

    public DocumentAccessProxy(string[] allowedWriteRoles)
        => _allowedRoles = new HashSet<string>(allowedWriteRoles);

    public string ReadContent(string docId) => _service.ReadContent(docId);

    public void WriteContent(string docId, string content)
    {
        if (!_allowedRoles.Contains(CurrentRole))
            throw new UnauthorizedAccessException($"{CurrentRole} 不能写入");
        _service.WriteContent(docId, content);
    }
}

// ── 缓存代理:避免重复查询 ──
public class CachedUserRepositoryProxy : IUserRepository
{
    private readonly RealUserRepository _real = new();
    private readonly Dictionary<int, string?> _cache = new();

    public string? GetUserName(int userId)
    {
        if (_cache.TryGetValue(userId, out var cached)) return cached;
        var name = _real.GetUserName(userId);
        _cache[userId] = name;
        return name;
    }
}

// ── Client —— 不知道拿的是代理还是真身 ──
IImage img = new LazyImageProxy("hero.png");  // 创建瞬间,图片未加载
img.Display();  // 第一次访问 → 触发加载
img.Display();  // 第二次访问 → 已缓存

❌ 不该用的信号:

  • 真身创建代价低 → 代理多此一举
  • 代理让 Client 误以为操作是即时的 → 如果有明显延迟,需要让 Client 知道(比如返回 Task)

🔗 相关模式: 装饰(结构相同,装饰增强功能,代理控制访问) · 适配器(代理接口不变,适配器接口变了)


三、行为型模式(11 种)

关注对象之间的通信——职责分配和算法封装。


13. 责任链(Chain of Responsibility) ⭐⭐ | 🟡

一句话: 请求沿链传递,每个节点决定处理还是转交。

触发信号: 校验规则排了长长一串 if-else,而且随时增减调序。

classDiagram class Handler { <<abstract>> -_next: Handler +SetNext(Handler) Handler +Handle(Request) #CanHandle(Request) bool* #Apply(Request)* } class HandlerA class HandlerB class HandlerC Handler <|-- HandlerA Handler <|-- HandlerB Handler <|-- HandlerC Handler --> Handler : _next
// ── Handler 抽象 ──
public abstract class DiscountHandler
{
    protected DiscountHandler? _next;

    public DiscountHandler SetNext(DiscountHandler next)
    {
        _next = next;
        return next;  // 支持链式构建
    }

    public void Handle(OrderRequest order)
    {
        if (CanHandle(order)) Apply(order);
        _next?.Handle(order);   // ← 总是往后传
    }

    protected abstract bool CanHandle(OrderRequest order);
    protected abstract void Apply(OrderRequest order);
}

// ── 具体处理者 ──
public class NewUserDiscountHandler : DiscountHandler
{
    protected override bool CanHandle(OrderRequest o) => o.OrderCount == 0;
    protected override void Apply(OrderRequest o) => o.AddDiscount(o.TotalAmount * 0.10m, "新用户10%");
}

public class ThresholdDiscountHandler : DiscountHandler
{
    protected override bool CanHandle(OrderRequest o) => o.TotalAmount >= 500;
    protected override void Apply(OrderRequest o) => o.AddDiscount(50, "满500减50");
}

public class VipDiscountHandler : DiscountHandler
{
    protected override bool CanHandle(OrderRequest o) => o.IsVip;
    protected override void Apply(OrderRequest o) => o.AddDiscount(o.TotalAmount * 0.05m, "VIP 5%");
}

// ── 构建链 ──
var newUser   = new NewUserDiscountHandler();
var threshold = new ThresholdDiscountHandler();
var vip       = new VipDiscountHandler();

newUser.SetNext(threshold).SetNext(vip);  // 链: newUser → threshold → vip

var order = new OrderRequest { TotalAmount = 800, OrderCount = 0, IsVip = true };
newUser.Handle(order);  // 新用户10% + 满减 + VIP5% 全部叠加

❌ 不该用的信号:

  • 只需要一个处理器 → 直接调方法
  • 处理器之间完全独立 → 并发执行更好
  • 链过长(> 10 个) → 难以推理最终结果,考虑归并

🔗 相关模式: 装饰(都形成链,装饰始终透传,责任链可能短路) · 观察者(观察者宽播,责任链单线传递)


14. 命令(Command) ⭐⭐ | 🟡

一句话: 把操作封装成对象,支持撤销、排队、日志。

触发信号: 想把"做一件事"本身存下来——不是立刻做,而是排队、记录、或者事后反悔(撤销)。

classDiagram class Invoker { -_undoStack: Stack[ICommand] -_redoStack: Stack[ICommand] +Execute(ICommand) +Undo() +Redo() } class ICommand { <<interface>> +Execute() +Undo() } class ConcreteCommand { -_receiver: Receiver +Execute() +Undo() } class Receiver { +DoAction() } Invoker --> ICommand ICommand <|.. ConcreteCommand ConcreteCommand --> Receiver
// ── Command 接口 ──
public interface ICommand
{
    void Execute();
    void Undo();
    string Description { get; }
}

// ── Receiver —— 真正干活的人 ──
public class Document
{
    private readonly StringBuilder _content = new();
    public string Content => _content.ToString();

    public void Insert(int pos, string text) => _content.Insert(pos, text);
    public void Delete(int pos, int count) => _content.Remove(pos, count);
}

// ── Concrete Commands —— 每个命令记录足够信息以支持 Undo ──
public class InsertCommand : ICommand
{
    private readonly Document _doc;
    private readonly string   _text;
    private readonly int      _position;

    public string Description => $"插入 \"{_text}\"";

    public InsertCommand(Document doc, int pos, string text)
        { _doc = doc; _position = pos; _text = text; }

    public void Execute() => _doc.Insert(_position, _text);
    public void Undo()    => _doc.Delete(_position, _text.Length);
}

// ── Invoker —— 维护历史栈 ──
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 cmd = _undoStack.Pop();
        cmd.Undo();
        _redoStack.Push(cmd);
    }

    public void Redo()
    {
        if (_redoStack.Count == 0) return;
        var cmd = _redoStack.Pop();
        cmd.Execute();
        _undoStack.Push(cmd);
    }
}

// ── Client ──
var doc     = new Document();
var history = new CommandHistory();

history.Execute(new InsertCommand(doc, 0, "Hello"));
history.Execute(new InsertCommand(doc, 5, " World"));
Console.WriteLine(doc.Content);  // "Hello World"

history.Undo();  // → "Hello"
history.Undo();  // → ""
history.Redo();  // → "Hello"

❌ 不该用的信号:

  • 不需要撤销/重做/排队 → 直接调方法更简单
  • 每个操作都要写 Undo 逻辑 → 工作量大,考虑备忘录模式兜底

🔗 相关模式: 备忘录(都支持撤销,命令存操作,备忘录存状态) · 策略(策略换算法,命令存操作)


15. 迭代器(Iterator) ⭐⭐⭐ | 🟢

一句话: 把遍历逻辑从集合中分离。

触发信号: 底层是树不是数组,但调用方只想 foreach。

classDiagram class IAggregate { <<interface>> +CreateIterator() IIterator } class ConcreteAggregate { +CreateIterator() IIterator } class IIterator { <<interface>> +MoveNext() bool +Current: T +Reset() } class ConcreteIterator { -_position: int +MoveNext() bool +Current: T } IAggregate <|.. ConcreteAggregate IIterator <|.. ConcreteIterator ConcreteAggregate ..> ConcreteIterator : creates
// ═════ 非泛型版本 ═════

public class Person
{
    public string Name { get; }
    public int    Age  { get; }
    public Person(string name, int age) { Name = name; Age = age; }
}

// 集合:只管存数据
public class PersonCollection : IEnumerable
{
    private readonly List<Person> _people = new();

    public void Add(Person p) => _people.Add(p);
    public int Count => _people.Count;

    public IEnumerator GetEnumerator()
        => new PersonEnumerator(this);   // ← 返回独立迭代器
}

// 迭代器:独占遍历逻辑和位置状态
public class PersonEnumerator : IEnumerator
{
    private readonly PersonCollection _collection;
    private int _position = -1;

    public PersonEnumerator(PersonCollection c) => _collection = c;

    public object Current => _collection[_position];
    public bool MoveNext() => ++_position < _collection.Count;
    public void Reset() => _position = -1;
}

// ── 使用 ──
var people = new PersonCollection();
people.Add(new Person("Alice", 30));
people.Add(new Person("Bob",   25));

foreach (Person p in people)       // ← 编译器展开为 while (e.MoveNext())
    Console.WriteLine($"{p.Name} ({p.Age})");

// ═════ 泛型版本 —— 同一棵树,多种遍历 ═════

// ── 树节点 ──
public class TreeNode<T>
{
    public T           Value { get; }
    public TreeNode<T>? Left  { get; set; }
    public TreeNode<T>? Right { get; set; }

    public TreeNode(T value) => Value = value;
}

// ── 二叉搜索树 —— 只负责存数据,不关心怎么遍历 ──
public class BinarySearchTree<T> : IEnumerable<T> where T : IComparable<T>
{
    private TreeNode<T>? _root;

    public void Add(T value)
    {
        _root = AddRecursive(_root, value);
    }

    private static TreeNode<T> AddRecursive(TreeNode<T>? node, T value)
    {
        if (node is null) return new TreeNode<T>(value);
        int cmp = value.CompareTo(node.Value);
        if (cmp < 0)      node.Left  = AddRecursive(node.Left,  value);
        else if (cmp > 0) node.Right = AddRecursive(node.Right, value);
        return node;
    }

    // 默认遍历:中序(升序)
    public IEnumerator<T> GetEnumerator()
        => new InOrderEnumerator<T>(_root);

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // 同一棵树,按需切换遍历策略——集合代码零改动
    public IEnumerable<T> LevelOrder => new LevelOrderEnumerable<T>(_root);
}

// ── 迭代器 1:中序遍历(栈实现 DFS) ──
public class InOrderEnumerator<T> : IEnumerator<T> where T : IComparable<T>
{
    private readonly Stack<TreeNode<T>> _stack = new();
    private TreeNode<T>? _current;

    public InOrderEnumerator(TreeNode<T>? root)
    {
        var node = root;
        while (node is not null) { _stack.Push(node); node = node.Left; }
    }

    public T Current => _current!.Value;
    object IEnumerator.Current => Current!;

    public bool MoveNext()
    {
        if (_stack.Count == 0) return false;
        _current = _stack.Pop();
        var node = _current.Right;
        while (node is not null) { _stack.Push(node); node = node.Left; }
        return true;
    }

    public void Reset() { }
    public void Dispose() { }
}

// ── 迭代器 2:层序遍历(队列实现 BFS)──
public class LevelOrderEnumerable<T> : IEnumerable<T> where T : IComparable<T>
{
    private readonly TreeNode<T>? _root;
    public LevelOrderEnumerable(TreeNode<T>? root) => _root = root;

    public IEnumerator<T> GetEnumerator()
    {
        if (_root is null) yield break;
        var queue = new Queue<TreeNode<T>>();
        queue.Enqueue(_root);
        while (queue.Count > 0)
        {
            var node = queue.Dequeue();
            yield return node.Value;
            if (node.Left  is not null) queue.Enqueue(node.Left);
            if (node.Right is not null) queue.Enqueue(node.Right);
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

// ── 使用:集合不知道数据是怎么被"走"的 ──
var tree = new BinarySearchTree<int>();
tree.Add(8); tree.Add(3); tree.Add(10); tree.Add(1);
tree.Add(6); tree.Add(14); tree.Add(4); tree.Add(7);

// 中序输出:1 3 4 6 7 8 10 14
Console.Write("中序(升序): ");
foreach (var v in tree) Console.Write($"{v} ");

// 层序输出:8 3 10 1 6 14 4 7
Console.Write("\n层序(BFS):  ");
foreach (var v in tree.LevelOrder) Console.Write($"{v} ");

// C# 语法糖:yield return —— 编译器帮你生成迭代器类
public static IEnumerable<int> CountTo(int n)
{
    for (int i = 1; i <= n; i++)
        yield return i;
}

❌ 不该用的信号:

  • 只有一种遍历方式 → 直接写 while 循环更简单
  • C# 已经有 foreach + yield return → 大多数场景不需要手写迭代器类

🔗 相关模式: 组合(迭代器常在组合结构上遍历) · 访问者(迭代器负责走,访问者负责操作)


16. 中介者(Mediator) ⭐⭐ | 🟡

一句话: 网状依赖变星形,所有人只跟中介者对话。

触发信号: 类 A 持有 B、C、D,B 持有 A、C——加一个改 N 个。

classDiagram class Mediator { <<interface>> +Notify(sender, event) } class ConcreteMediator { -_colleagues: List[Colleague] +Notify(sender, event) } class Colleague { <<abstract>> -_mediator: Mediator +Send() +Receive() } class ColleagueA class ColleagueB Mediator <|.. ConcreteMediator Colleague <|-- ColleagueA Colleague <|-- ColleagueB Colleague --> Mediator ConcreteMediator --> Colleague
// ── Mediator 接口 ──
public interface IChatRoom
{
    void Register(User user);
    void Send(string from, string to, string message);
    void Broadcast(string from, string message);
}

// ── Colleague —— 只认识中介者,不认识其他用户 ──
public class User
{
    private readonly IChatRoom _room;
    public string Name { get; }

    public User(string name, IChatRoom room)
        { Name = name; _room = room; _room.Register(this); }

    public void SendTo(string to, string msg)
    {
        Console.WriteLine($"  {Name} → {to}: \"{msg}\"");
        _room.Send(Name, to, msg);
    }

    public void Receive(string from, string msg)
        => Console.WriteLine($"    📨 [{Name} 收到] {from}: {msg}");
}

// ── Concrete Mediator —— 集中路由 ──
public class ChatRoom : IChatRoom
{
    private readonly Dictionary<string, User> _users = new();

    public void Register(User u) => _users[u.Name] = u;

    public void Send(string from, string to, string msg)
    {
        if (_users.TryGetValue(to, out var recipient))
            recipient.Receive(from, msg);
    }

    public void Broadcast(string from, string msg)
    {
        foreach (var (name, user) in _users)
            if (name != from) user.Receive(from, msg);
    }
}

// ── Client —— User 之间零直接引用 ──
var room  = new ChatRoom();
var alice = new User("Alice", room);
var bob   = new User("Bob",   room);

alice.SendTo("Bob", "周末有空吗?");  // 通过 ChatRoom 路由

❌ 不该用的信号:

  • 同事数量固定且少量 → 直接引用更清晰
  • 中介者变成"无所不知的上帝对象" → 超过 200 行考虑拆分成多个 Mediator

🔗 相关模式: 观察者(观察者是一对多直接通知,中介者是星形间接通知) · 外观(外观简化单方向调用,中介者管理双向交互)


17. 备忘录(Memento) ⭐⭐ | 🟡

一句话: 状态快照封在不透明信封里,可以回滚。

触发信号: 操作到一半发现做错了,想回到之前某个时间点的状态——Ctrl+Z 的底层逻辑。

classDiagram class Originator { +CreateSnapshot() Memento +Restore(Memento) } class Memento { -_state +Timestamp } class Caretaker { -_snapshots: Stack[Memento] +Save(Memento) +Undo() Memento } Originator ..> Memento : creates Caretaker --> Memento : stores Originator ..> Caretaker : uses note "Memento: 窄接口对外 / 宽接口对Originator"
// ── Memento —— 窄接口对外(Caretaker 看不到内容) ──
public class EditorSnapshot
{
    private readonly string _content;         // ← 只有 Originator 能访问
    private readonly int    _cursorPosition;

    public DateTime Timestamp { get; }        // ← 窄接口
    public int      Length    => _content.Length;

    internal EditorSnapshot(string content, int cursor)
        { _content = content; _cursorPosition = cursor; Timestamp = DateTime.Now; }

    internal string Content        => _content;         // ← 宽接口
    internal int    CursorPosition => _cursorPosition;
}

// ── Originator —— 创建和恢复快照 ──
public class TextEditor
{
    private readonly StringBuilder _content = new();
    private int _cursor;

    public void Type(string text) => _content.Insert(_cursor, text);

    public EditorSnapshot CreateSnapshot()
        => new(_content.ToString(), _cursor);

    public void Restore(EditorSnapshot snap)
    {
        _content.Clear();
        _content.Append(snap.Content);
        _cursor = snap.CursorPosition;
    }
}

// ── Caretaker —— 只管存储,不管内容 ──
public class UndoManager
{
    private readonly Stack<EditorSnapshot> _snapshots = new();

    public void Save(EditorSnapshot snap) => _snapshots.Push(snap);
    public EditorSnapshot? Undo() => _snapshots.TryPop(out var s) ? s : null;
}

// ── Client ──
var editor  = new TextEditor();
var history = new UndoManager();

history.Save(editor.CreateSnapshot());
editor.Type("Hello World");
history.Save(editor.CreateSnapshot());

var snap = history.Undo();
if (snap is not null) editor.Restore(snap);  // → ""

❌ 不该用的信号:

  • 状态数据量很大 → 每份快照都是全量拷贝,内存扛不住,考虑命令模式存差量
  • 只需要一步撤销 → 存上一份状态的局部变量就够了

🔗 相关模式: 命令(都支持撤销,命令存操作,备忘录存状态;常配合使用) · 原型(原型用于创建变体,备忘录用于回滚)


18. 观察者(Observer) ⭐⭐⭐ | 🟡

一句话: 一对多依赖,状态变化自动通知所有订阅者。

触发信号: 一个对象变了,好几个对象都得跟着更新——数据改了就刷新 UI、收到消息就通知所有监听者。

classDiagram class Subject { -_observers: List[IObserver] +Subscribe(IObserver) +Unsubscribe(IObserver) #NotifyAll() } class IObserver { <<interface>> +Update() } class ObserverA { +Update() } class ObserverB { +Update() } Subject --> IObserver IObserver <|.. ObserverA IObserver <|.. ObserverB
// ── Observer 接口 ──
public interface IInvestor
{
    string Name { get; }
    void OnPriceChanged(string symbol, decimal oldPrice, decimal newPrice);
}

// ── Subject ──
public class Stock
{
    private readonly List<IInvestor> _investors = new();
    private decimal _price;

    public string  Symbol { get; }
    public decimal Price
    {
        get => _price;
        set { var old = _price; _price = value; NotifyAll(old); }
    }

    public Stock(string symbol, decimal initialPrice)
        { Symbol = symbol; _price = initialPrice; }

    public void Subscribe(IInvestor i)   => _investors.Add(i);
    public void Unsubscribe(IInvestor i) => _investors.Remove(i);

    private void NotifyAll(decimal oldPrice)
    {
        foreach (var investor in _investors)
            investor.OnPriceChanged(Symbol, oldPrice, _price);
    }
}

// ── Concrete Observer ──
public class Investor : IInvestor
{
    public string Name { get; }
    public Investor(string name) => Name = name;

    public void OnPriceChanged(string symbol, decimal old, decimal current)
    {
        var direction = current > old ? "📈" : "📉";
        Console.WriteLine($"  {Name}: {symbol} {direction} ¥{current:F2}");
    }
}

// ── C# 原生 event —— 最地道的观察者 ──
public class StockWithEvent
{
    private decimal _price;
    public string Symbol { get; }

    public event EventHandler<StockEventArgs>? PriceChanged;

    public void SetPrice(decimal newPrice)
    {
        var old = _price; _price = newPrice;
        PriceChanged?.Invoke(this, new StockEventArgs(Symbol, old, newPrice));
    }
}

// 订阅 / 取消用 += / -=
stock.PriceChanged += (sender, e) => Console.WriteLine($"{e.Symbol} → ¥{e.NewPrice}");
stock.PriceChanged -= handler;  // ⚠ 忘记取消会导致内存泄漏(见第九章)

❌ 不该用的信号:

  • 只有一个观察者 → 直接回调就够了
  • 忘记取消订阅 → 观察者无法被 GC 回收,造成内存泄漏

🔗 相关模式: 中介者(观察者直接通知,中介者间接路由) · 责任链(责任链单线传递,观察者宽播到所有订阅者)


19. 状态(State) ⭐⭐ | 🟡

一句话: 行为随内部状态改变,状态对象自己决定何时切换。

触发信号: 待支付→已支付→已发货,每个状态下能做的事完全不同。

classDiagram class Context { -_state: IState +Request() +TransitionTo(IState) } class IState { <<interface>> +Handle(Context) } class StateA { +Handle(Context) } class StateB { +Handle(Context) } class StateC { +Handle(Context) } Context --> IState IState <|.. StateA IState <|.. StateB IState <|.. StateC note "StateA: Handle()内调用 ctx.TransitionTo()"
// ── State 接口 ──
public interface IOrderState
{
    string Name { get; }
    void Pay(Order order);
    void Ship(Order order);
    void Cancel(Order order);
}

// ── Context —— 持有当前状态,所有操作委托给状态 ──
public class Order
{
    private IOrderState _state;
    public string Id         { get; }
    public string StatusName => _state.Name;

    public Order(string id) { Id = id; _state = new PendingState(); }

    public void Pay()     => _state.Pay(this);
    public void Ship()    => _state.Ship(this);
    public void Cancel()  => _state.Cancel(this);

    public void TransitionTo(IOrderState newState)
    {
        Console.WriteLine($": {_state.Name} → {newState.Name}");
        _state = newState;
    }
}

// ── 具体状态 —— 每个状态只定义合法操作,自己决定何时切换 ──
public class PendingState : IOrderState
{
    public string Name => "待支付";

    public void Pay(Order o)    => o.TransitionTo(new PaidState());     // ← 状态自切换
    public void Ship(Order o)   => Console.WriteLine(" ✗ 未支付");
    public void Cancel(Order o) => o.TransitionTo(new CancelledState("待支付取消"));
}

public class PaidState : IOrderState
{
    public string Name => "已支付";

    public void Pay(Order o)    => Console.WriteLine(" ✗ 已支付");
    public void Ship(Order o)   => o.TransitionTo(new ShippedState());
    public void Cancel(Order o) => o.TransitionTo(new CancelledState("已支付取消"));
}

public class ShippedState : IOrderState
{
    public string Name => "已发货";

    public void Pay(Order o)    => Console.WriteLine(" ✗ 已支付");
    public void Ship(Order o)   => Console.WriteLine(" ✗ 已发货");
    public void Cancel(Order o) => Console.WriteLine(" ✗ 已发货,无法直接取消");
}

public class CancelledState : IOrderState
{
    private readonly string _reason;
    public CancelledState(string reason) => _reason = reason;
    public string Name => $"已取消({_reason})";

    public void Pay(Order o)    => Console.WriteLine(" ✗ 已取消");
    public void Ship(Order o)   => Console.WriteLine(" ✗ 已取消");
    public void Cancel(Order o) => Console.WriteLine(" ✗ 已取消");
}

// ── 使用:行为随状态自动变化 ──
var order = new Order("ORD-001");
order.Pay();    // 待支付 → 已支付
order.Ship();   // 已支付 → 已发货
order.Pay();    // ✗ 已发货,无需重复支付

❌ 不该用的信号:

  • 状态只有 2-3 种且转换简单 → if-else 或 switch 更直观
  • 状态对象之间存在复杂依赖 → 考虑状态机库而非手写

🔗 相关模式: 策略(结构相同,状态自切换,策略外部注入) · 备忘录(状态变化可以记录为备忘录)


20. 策略(Strategy) ⭐⭐⭐ | 🟢

一句话: 一族算法各自封装,运行时互换。

触发信号: 同一件事有几种做法,运行时按条件选一种——不同计费规则、不同折扣策略、不同排序方式。

classDiagram class Context { -_strategy: IStrategy +SetStrategy(IStrategy) +DoWork() } class IStrategy { <<interface>> +Execute() } class StrategyA { +Execute() } class StrategyB { +Execute() } class StrategyC { +Execute() } Context --> IStrategy IStrategy <|.. StrategyA IStrategy <|.. StrategyB IStrategy <|.. StrategyC note "Context: 策略由外部注入"
// ── Strategy 接口 ──
public interface IShippingStrategy
{
    string Name { get; }
    ShippingResult Calculate(Order order);
}

// ── 顺丰:按重量+距离 ──
public class ShunfengStrategy : IShippingStrategy
{
    public string Name => "顺丰速运";

    public ShippingResult Calculate(Order order)
    {
        var cost = 12m + (order.Weight - 1) * 2m + order.Distance * 0.01m;
        return new ShippingResult { Company = Name, Cost = Math.Round(cost, 2) };
    }
}

// ── 圆通:一口价 ──
public class YuantongStrategy : IShippingStrategy
{
    public string Name => "圆通快递";

    public ShippingResult Calculate(Order order)
        => new() { Company = Name, Cost = 8m };
}

// ── 京东:满免 ──
public class JdExpressStrategy : IShippingStrategy
{
    public string Name => "京东快递";

    public ShippingResult Calculate(Order order)
    {
        if (order.ProductAmount >= 99m)
            return new() { Company = Name, Cost = 0, IsFree = true };
        return new() { Company = Name, Cost = 6m };
    }
}

// ── Context → Client 从外部切换策略 ──
public class Checkout
{
    private IShippingStrategy _strategy;

    public Checkout(IShippingStrategy s) => _strategy = s;
    public void SetStrategy(IShippingStrategy s) => _strategy = s;

    public void PrintEstimate(Order order)
    {
        var result = _strategy.Calculate(order);
        Console.WriteLine($"[{result.Company}] 运费: ¥{result.Cost:F2}");
    }
}

// ── Client —— 运行时选择 ──
var checkout = new Checkout(new ShunfengStrategy());
checkout.PrintEstimate(smallOrder);

checkout.SetStrategy(new YuantongStrategy());  // ← 外部切换
checkout.PrintEstimate(smallOrder);

❌ 不该用的信号:

  • 只有一个算法 → 直接写方法
  • 算法之间结构差异巨大 → 强行统一接口导致有些策略有大量空方法

🔗 相关模式: 状态(结构相同,状态自切换,策略外部注入) · 模板方法(模板方法继承填空,策略组合互换) · 命令(命令存操作,策略换算法)


21. 模板方法(Template Method) ⭐⭐⭐ | 🟢

一句话: 父类定义骨架,子类填空步骤。

触发信号: 一套固定流程,多个实现变体——数据导入、报表生成、构建管道:骨架相同,填空不同。

classDiagram class AbstractClass { <<abstract>> +TemplateMethod() #Step1()* #Step2()* #Hook() } class ConcreteClassA { #Step1() #Step2() } class ConcreteClassB { #Step1() #Step2() } AbstractClass <|-- ConcreteClassA AbstractClass <|-- ConcreteClassB note "AbstractClass: TemplateMethod() 定义骨架 / 子类只填 Step1/Step2"
// ── AbstractClass —— 骨架 ──
public abstract class DataImporter
{
    // 模板方法:定义步骤 ① ② ③ ④ ⑤ 的执行顺序,子类不能改变
    public List<UserRecord> Import(string filePath)
    {
        string raw = ReadFile(filePath);       // ① 默认从文件读
        Validate(raw);                          // ② 子类验证
        List<UserRecord> records = Parse(raw);  // ③ 子类解析
        Save(records);                          // ④ 子类存储
        AfterImport(records);                   // ⑤ 钩子,子类可选
        return records;
    }

    protected virtual string ReadFile(string path) => File.ReadAllText(path);
    protected abstract void Validate(string raw);            // 原语操作
    protected abstract List<UserRecord> Parse(string raw);   // 原语操作
    protected abstract void Save(List<UserRecord> records);  // 原语操作
    protected virtual void AfterImport(List<UserRecord> r) { } // 钩子
}

// ── CSV 实现 —— 填空 ──
public class CsvImporter : DataImporter
{
    protected override void Validate(string raw)
    {
        if (string.IsNullOrWhiteSpace(raw)) throw new Exception("CSV 为空");
    }

    protected override List<UserRecord> Parse(string raw)
        => raw.Split('\n').Skip(1)              // 跳过标题
              .Select(line => line.Split(','))
              .Select(p => new UserRecord(p[0], p[1], int.Parse(p[2])))
              .ToList();

    protected override void Save(List<UserRecord> records)
        => Console.WriteLine($"BULK INSERT users ({records.Count} 行)");
}

// ── JSON 实现 —— 填空(流程不变,只换解析) ──
public class JsonImporter : DataImporter
{
    protected override void Validate(string raw) { /* 检查括号 */ }
    protected override List<UserRecord> Parse(string raw) { /* JSON 解析 */ }
    protected override void Save(List<UserRecord> r) { /* insertMany */ }

    protected override void AfterImport(List<UserRecord> records)
        => Console.WriteLine("创建 email 索引");  // 钩子:JSON 特供
}

❌ 不该用的信号:

  • 骨架本身不清晰 → 先理清流程再抽模板
  • 子类需要跳过某个步骤 → 说明骨架不够通用,用策略模式给子类更大的自由

🔗 相关模式: 策略(模板方法继承填空,策略组合换算法) · 工厂方法(工厂方法本质是模板方法的一种特化)


22. 访问者(Visitor) ⭐⭐ | 🔴

一句话: 操作从数据结构中抽离,新操作不动元素。

触发信号: 数据结构已经很稳定了,但对它的操作每个月都在加——导出、统计、校验。数据类只写一次 Accept(),新操作作为新 Visitor 加入。

classDiagram class IVisitor { <<interface>> +VisitElementA(ElementA) +VisitElementB(ElementB) } class Visitor1 { +VisitElementA() +VisitElementB() } class Visitor2 { +VisitElementA() +VisitElementB() } class IElement { <<interface>> +Accept(IVisitor) } class ElementA { +Accept(IVisitor) } class ElementB { +Accept(IVisitor) } IVisitor <|.. Visitor1 IVisitor <|.. Visitor2 IElement <|.. ElementA IElement <|.. ElementB ElementA ..> IVisitor : Accept(v) -> v.Visit(this) ElementB ..> IVisitor : Accept(v) -> v.Visit(this)
// ── Visitor 接口 —— 为每种元素声明一个 Visit 方法 ──
public interface IDocumentVisitor
{
    void VisitText(TextElement text);
    void VisitImage(ImageElement image);
    void VisitTable(TableElement table);
}

// ── Element 接口 —— 稳定不变 ──
public interface IDocumentElement
{
    void Accept(IDocumentVisitor visitor);
}

// ── Concrete Elements —— Accept 固定写法 ──
public class TextElement : IDocumentElement
{
    public string Content { get; }
    public TextElement(string content) => Content = content;

    public void Accept(IDocumentVisitor visitor) => visitor.VisitText(this);  // 双分派
}

public class ImageElement : IDocumentElement
{
    public string Url    { get; }
    public string AltText { get; }
    public ImageElement(string url, string alt) { Url = url; AltText = alt; }

    public void Accept(IDocumentVisitor visitor) => visitor.VisitImage(this);
}

public class TableElement : IDocumentElement
{
    public string[]       Headers { get; }
    public List<string[]> Rows    { get; }
    public TableElement(string[] h, List<string[]> r) { Headers = h; Rows = r; }

    public void Accept(IDocumentVisitor visitor) => visitor.VisitTable(this);
}

// ── 文档容器 —— 持有元素列表,遍历分发访问者 ──
public class Document
{
    private readonly List<IDocumentElement> _elements = new();

    public void Add(IDocumentElement element) => _elements.Add(element);

    public void Accept(IDocumentVisitor visitor)
    {
        foreach (var element in _elements)
            element.Accept(visitor);   // ← 双分派:element 和 visitor 互相确认类型
    }
}

// ── 访问者:HTML 导出 ──
public class HtmlExportVisitor : IDocumentVisitor
{
    private readonly StringBuilder _output = new();
    public string Result => _output.ToString();

    public void VisitText(TextElement t) => _output.AppendLine($"<p>{t.Content}</p>");
    public void VisitImage(ImageElement i)
        => _output.AppendLine($"<img src=\"{i.Url}\" alt=\"{i.AltText}\" />");
    public void VisitTable(TableElement t)
    {
        _output.AppendLine("<table>");
        foreach (var row in t.Rows)
            _output.AppendLine($"<tr>{string.Join("", row.Select(c => $"<td>{c}</td>"))}</tr>");
        _output.AppendLine("</table>");
    }
}

// ── 访问者:字数统计 —— 新操作,不动元素类 ──
public class WordCountVisitor : IDocumentVisitor
{
    public int TotalWords { get; private set; }
    public int ImageCount { get; private set; }

    public void VisitText(TextElement t)
        => TotalWords += t.Content.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;

    public void VisitImage(ImageElement i) => ImageCount++;
    public void VisitTable(TableElement t)
    {
        foreach (var row in t.Rows)
            foreach (var cell in row)
                TotalWords += cell.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

// ── Client ──
var doc = new Document();
doc.Add(new TextElement("Hello World"));
doc.Add(new ImageElement("img.png", "示意图"));

var htmlExporter = new HtmlExportVisitor();
doc.Accept(htmlExporter);
Console.WriteLine(htmlExporter.Result);  // HTML 输出

var counter = new WordCountVisitor();
doc.Accept(counter);
Console.WriteLine($"字数: {counter.TotalWords}");  // 统计

❌ 不该用的信号:

  • 元素类型频繁变更 → 每次增删元素都要改所有 Visitor 接口,这是访问者的死穴
  • 只有一两个操作 → 直接写在元素类里更简单

🔗 相关模式: 迭代器(迭代器负责走,访问者负责操作) · 策略(访问者是双分派,策略是单分派)


23. 解释器(Interpreter) ⭐ | 🔴

一句话: 定义一种语言的文法表示,并为每个文法规则创建一个解释器。

触发信号: 用户输入的是一段"小语言"——查询条件、计算公式、配置规则——需要解析成程序能执行的指令。

classDiagram class IExpression { <<interface>> +Interpret(context) bool } class TerminalExpression { +Interpret(context) bool } class NonTerminalExpression { -_left: IExpression -_right: IExpression +Interpret(context) bool } IExpression <|.. TerminalExpression IExpression <|.. NonTerminalExpression NonTerminalExpression o-- IExpression : _left NonTerminalExpression o-- IExpression : _right
// ── 简单布尔表达式解释器(SQL WHERE 子句) ──

// 抽象表达式
public interface IExpression
{
    bool Interpret(Dictionary<string, string> context);
}

// 终结符:字段 = 值
public class EqualExpression : IExpression
{
    private readonly string _field, _value;
    public EqualExpression(string field, string value) { _field = field; _value = value; }

    public bool Interpret(Dictionary<string, string> ctx)
        => ctx.TryGetValue(_field, out var v) && v == _value;
}

// 非终结符:AND
public class AndExpression : IExpression
{
    private readonly IExpression _left, _right;
    public AndExpression(IExpression left, IExpression right) { _left = left; _right = right; }

    public bool Interpret(Dictionary<string, string> ctx)
        => _left.Interpret(ctx) && _right.Interpret(ctx);
}

// 非终结符:OR
public class OrExpression : IExpression
{
    private readonly IExpression _left, _right;
    public OrExpression(IExpression left, IExpression right) { _left = left; _right = right; }

    public bool Interpret(Dictionary<string, string> ctx)
        => _left.Interpret(ctx) || _right.Interpret(ctx);
}

// ── Client ──
// 表达式:(City = "Beijing" AND Age = "30") OR City = "Shanghai"
IExpression expr = new OrExpression(
    new AndExpression(
        new EqualExpression("City", "Beijing"),
        new EqualExpression("Age", "30")),
    new EqualExpression("City", "Shanghai"));

var row = new Dictionary<string, string> { ["City"] = "Beijing", ["Age"] = "30" };
Console.WriteLine(expr.Interpret(row));  // True

❌ 不该用的信号: 现代 C# 中绝大多数场景不需要手写解释器——正则表达式、表达式树、Roslyn 编译器平台已经覆盖了 99% 的解析需求。仅作为完整性的一个了解。

🔗 相关模式: 组合(解释器的 AST 就是组合模式) · 访问者(解释器常用访问者遍历 AST)


四、全模式对照矩阵

模式 类型 日频 难度 核心机制 关键代码
工厂方法 创建 ⭐⭐⭐ 🟡 继承+多态 abstract Product Create()
抽象工厂 创建 ⭐⭐ 🟡 接口+一族方法 ProductA CreateA(); ProductB CreateB()
生成器 创建 ⭐⭐ 🟡 分步构建 builder.Part1().Part2().GetResult()
原型 创建 ⭐⭐ 🟢 克隆 return (T)MemberwiseClone()
单例 创建 ⭐⭐⭐ 🟢 私有构造+静态实例 private Ctor(){} + Lazy<T>
适配器 结构 ⭐⭐⭐ 🟢 接口翻译 class Ad : ITarget { Adaptee _a; }
桥接 结构 ⭐⭐ 🔴 抽象-实现分离 abstract class A { prot IImpl _i; }
组合 结构 ⭐⭐ 🟡 递归+多态 children.Sum(c => c.GetValue())
装饰 结构 ⭐⭐⭐ 🟡 嵌套包装 class D : IComp { IComp _inner; }
外观 结构 ⭐⭐⭐ 🟢 统一入口 class Facade { SubA _a; SubB _b; }
享元 结构 ⭐⭐ 🟡 共享池 Dictionary<k,Fly> _cache
代理 结构 ⭐⭐⭐ 🟡 替身控制 class Proxy : ISub { Real _rs; }
责任链 行为 ⭐⭐ 🟡 链式传递 _next?.Handle(req)
命令 行为 ⭐⭐ 🟡 操作对象化 interface ICmd { Exec(); Undo(); }
迭代器 行为 ⭐⭐⭐ 🟢 分离遍历 IEnumerator<T> GetEnumerator()
中介者 行为 ⭐⭐ 🟡 集中协调 mediator.Notify(sender, evt)
备忘录 行为 ⭐⭐ 🟡 状态快照 class Mem { internal State; }
观察者 行为 ⭐⭐⭐ 🟡 事件通知 event EventHandler<T>
状态 行为 ⭐⭐ 🟡 状态对象+自切换 state.Handle(this)TransitionTo
策略 行为 ⭐⭐⭐ 🟢 算法互换 ctx.SetStrategy(new Strat())
模板方法 行为 ⭐⭐⭐ 🟢 继承填空 abstract void Step(); + DoAll()
访问者 行为 ⭐⭐ 🔴 双分派 el.Accept(v)v.Visit(this)
解释器 行为 🔴 AST 递归解释 expr.Interpret(context)

五、最容易混淆的组合

1. 状态 vs 策略

结构完全一致:Context 组合一个接口,多态委托。

区分——谁决定切换?
  状态 → 状态对象自己调用 context.TransitionTo()
  策略 → Client 从外部调用 context.SetStrategy()

2. 装饰 vs 代理

结构完全一致:都实现同一接口、都持有被包裹对象。

区分——意图:
  装饰 → 增强功能(加日志、加缓存)
  代理 → 控制访问(延迟加载、权限校验)

判断法:去掉外层,内层还能完整工作吗?
  装饰的内层本来就是完整的,装饰加上味道。
  代理的内层是目的,代理是替身。

3. 适配器 vs 外观

都解决接口问题,方向相反。

区分——原接口还在用吗?
  适配器 → 原接口是规范,多个适配器都面向它(接口转换)
  外观 → 复杂子系统还在,外观是可选快捷入口(接口简化)

4. 桥接 vs 装饰

区分——内层对象能独立存在并被外界直接使用吗?
  桥接 → IMessageSender 可以独立于 Message 存在(两个独立维度)
  装饰 → LoggingDecorator 不能脱离 INotifier 存在(依赖内层)

5. 观察者 vs 中介者 vs 发布订阅

递进关系(解耦程度递增):
  观察者 → Subject 直接持有 Observer 列表
  中介者 → Colleague 只认识 Mediator
  发布订阅 → Publisher 和 Subscriber 互不认识(加消息队列)

6. 命令 vs 备忘录

两者都能实现撤销,路径不同:
  命令 → 存储操作,逆向执行(撤销 Insert 就是 Delete)
  备忘录 → 存储状态快照,整份回滚

命令内存省但逻辑复杂,备忘录实现简单但内存大。
实际项目常合用:命令管历史栈,备忘录做深层兜底。

7. 模板方法 vs 策略

方向相反:
  模板方法 → 骨架固定,填空细节(父类控制流程)
  策略 → 整块算法替换(Client 控制选择)

模板方法是继承复用,策略是组合互换。

六、快速辨别决策树

需要创建对象
├─ 只有 2-3 个必填参数 → 普通 new
├─ 需要隐藏 new 的是哪个子类
│   ├─ 只决定一个产品 → 工厂方法
│   └─ 决定一族配套产品 → 抽象工厂
├─ 构造过程本身复杂
│   ├─ 步骤固定,表示可变 → 生成器
│   └─ 基于模板微调 → 原型
└─ 逻辑上就该只有一份 → 单例

需要整合或拆分结构
├─ 整合
│   ├─ 接口对不上 → 适配器
│   └─ 子系统太复杂 → 外观
├─ 拆分变化维度
│   ├─ 两个独立维度 → 桥接
│   └─ 递归树形 → 组合
└─ 增强或控制
    ├─ 叠加能力 → 装饰
    ├─ 控制访问 → 代理
    └─ 共享内存 → 享元

需要组织行为
├─ 消息/请求的流转
│   ├─ 多个处理器排队,可增减 → 责任链
│   ├─ 网状依赖需要协调 → 中介者
│   └─ 一对多自动通知 → 观察者
├─ 操作的封装与回溯
│   ├─ 需要撤销/重做/排队 → 命令
│   └─ 需要状态快照回滚 → 备忘录
└─ 算法/行为的替换
    ├─ 骨架固定,步骤实现不同 → 模板方法
    ├─ 整个算法可互换 → 策略
    ├─ 行为随内部状态变化 → 状态
    ├─ 数据稳定,操作频繁扩展 → 访问者
    └─ 需要解析简单 DSL → 解释器

七、.NET 框架内建映射

大部分模式你已经在用,只是没意识到它是"设计模式":

模式 .NET 内建对应 说明
工厂方法 IHttpClientFactory, ILoggerFactory DI 中最常见的工厂
抽象工厂 DbProviderFactory SQL Server/MySQL/PostgreSQL 的工厂切换
生成器 StringBuilder, HttpRequestMessage + 对象初始化器 字符串拼接就是分步构建
原型 ICloneable, MemberwiseClone() 浅拷贝的标准接口
单例 Lazy<T>, DI .AddSingleton() Lazy<T> 是线程安全懒加载的最佳实现
适配器 StreamReader (适配 Stream → TextReader), DbDataAdapter 把字节流适配为文本流
桥接 无直接内建 设计时决策,StreamReader(Stream) 接近
组合 System.Xml.XmlNode, UIElement (WPF) 树形控件的内核
装饰 Stream 体系: BufferedStream, GZipStream, CryptoStream 一层包一层
外观 HttpClient, WebClient 底层 Socket/TLS/HTTP 全封装
享元 string.Intern() 字符串驻留池
代理 MarshalByRefObject (远程代理), 懒加载 Lazy<T> 跨 AppDomain 的代理
责任链 ASP.NET Middleware pipeline app.Use() 串起的所有中间件
命令 ICommand (WPF), DelegateCommand, RelayCommand MVVM 的基石
迭代器 IEnumerator<T> + foreach + yield return C# 语法就是迭代器模式
中介者 EventAggregator (Prism), IMediator (MediatR) 事件聚合器
备忘录 Serialization 体系 (BinaryFormatter, JsonSerializer) 序列化=完整状态快照
观察者 event, IObservable<T>, INotifyPropertyChanged Reactive Extensions
状态 Task.Status, TaskState 任务状态机
策略 IComparer<T>, Func<T, TResult>, Action<T> 委托就是策略模式
模板方法 ASP.NET Middleware base class, BackgroundService ExecuteAsync 就是模板方法
访问者 ExpressionVisitor (LINQ) 遍历表达式树的访问者
解释器 Regex (正则表达式), Expression (表达式树) 不常用的独立模式

八、综合案例:文档编辑器

一个场景同时使用 4 种模式。场景:一个带撤销/重做的 Markdown 编辑器。

涉及的模式

graph TB A[EditorApp] --> B[命令模式] A --> C[备忘录模式] A --> D[组合模式] A --> E[观察者模式] B --> B1["IEditCommand<br/>每次编辑生成<br/>支持 Undo/Redo"] C --> C1["DocumentSnapshot<br/>编辑前自动快照<br/>深层次兜底"] D --> D1["DocBlock 树<br/>Section → Block<br/>递归汇总"] E --> E1["IDocumentObserver<br/>状态变化通知<br/>UI 自动刷新"]
模式 解决的问题 在代码中的体现
组合 文档是 Block 的树——Section 里可以嵌套 Section,WordCount 要递归汇总。调用方不区分叶子还是树枝。 DocBlock 抽象类 + Section.Children 列表 + Sum(c => c.WordCount)
命令 Ctrl+Z 撤销:每次编辑生成一个命令对象存入历史栈,撤销时逆向执行。 IEditCommand 接口 + _undoStack / _redoStack
备忘录 命令只能逐步撤销,深层次回退需要完整快照兜底。 DocumentSnapshot 保存 JSON 状态 + _snapshots
观察者 文档内容变化时 UI 自动刷新,编辑器不知道 UI 具体怎么渲染。 IDocumentObserver 接口 + NotifyChanged() 广播
// ═══════════ 1. 组合模式:文档由 Block 递归组成 ═══════════

public abstract class DocBlock
{
    public abstract int WordCount { get; }
    public abstract string RenderHtml();
    public abstract string RenderMarkdown();
}

public class TextBlock : DocBlock
{
    public string Content { get; set; } = "";
    public override int WordCount
        => Content.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
    public override string RenderHtml() => $"<p>{Content}</p>";
    public override string RenderMarkdown() => Content;
}

public class CodeBlock : DocBlock
{
    public string Language { get; set; } = "";
    public string Code     { get; set; } = "";
    public override int WordCount
        => Code.Split(' ', '\n', '\t', StringSplitOptions.RemoveEmptyEntries).Length;
    public override string RenderHtml() => $"<pre><code>{Code}</code></pre>";
    public override string RenderMarkdown() => $"```{Language}\n{Code}\n```";
}

// ── 组合节点:Section 包含多个 Block ──
public class Section : DocBlock
{
    public string Title { get; set; } = "";
    public List<DocBlock> Children { get; } = new();

    public override int WordCount
        => Children.Sum(c => c.WordCount);  // ← 递归汇总
    public override string RenderHtml()
        => $"<section><h2>{Title}</h2>{string.Join("", Children.Select(c => c.RenderHtml()))}</section>";
    public override string RenderMarkdown()
        => $"## {Title}\n\n{string.Join("\n", Children.Select(c => c.RenderMarkdown()))}";
}

// ═══════════ 2. 命令模式 + 备忘录模式:撤销/重做 ═══════════

public interface IEditCommand
{
    void Execute(Document doc);
    void Undo(Document doc);
    string Description { get; }
}

public class InsertBlockCommand : IEditCommand
{
    private readonly DocBlock _block;
    private readonly Section _parent;
    private          int      _index;

    public string Description => $"插入 {_block.GetType().Name}";

    public InsertBlockCommand(Section parent, DocBlock block)
        { _parent = parent; _block = block; }

    public void Execute(Document doc)
    {
        _index = _parent.Children.Count;
        _parent.Children.Add(_block);
        doc.NotifyChanged();
    }

    public void Undo(Document doc)
    {
        _parent.Children.RemoveAt(_index);
        doc.NotifyChanged();
    }
}

// ── 备忘录:完整快照兜底 ──
public class DocumentSnapshot
{
    internal string JsonState { get; }
    internal DocumentSnapshot(string json) => JsonState = json;
}

// ═══════════ 3. 观察者模式:通知 UI 刷新 ═══════════

public interface IDocumentObserver
{
    void OnDocumentChanged(Document doc);
}

// ═══════════ Document 聚合三种模式 ═══════════

public class Document
{
    private readonly Stack<IEditCommand> _undoStack = new();
    private readonly Stack<IEditCommand> _redoStack = new();
    private readonly List<IDocumentObserver> _observers = new();

    public Section Root { get; } = new() { Title = "未命名文档" };

    // ── 命令模式 ──
    public void Execute(IEditCommand command)
    {
        command.Execute(this);
        _undoStack.Push(command);
        _redoStack.Clear();
    }

    public void Undo()
    {
        if (_undoStack.Count == 0) return;
        var cmd = _undoStack.Pop();
        cmd.Undo(this);
        _redoStack.Push(cmd);
        DoBackup();  // ← 撤销后做备忘录兜底
    }

    public void Redo()
    {
        if (_redoStack.Count == 0) return;
        var cmd = _redoStack.Pop();
        cmd.Execute(this);
        _undoStack.Push(cmd);
    }

    // ── 备忘录模式:兜底快照 ──
    private readonly Stack<DocumentSnapshot> _snapshots = new();

    public void DoBackup()
        => _snapshots.Push(new DocumentSnapshot(Root.RenderMarkdown()));

    public void RestoreLastSnapshot()
    {
        if (_snapshots.Count == 0) return;
        var snap = _snapshots.Pop();
        Console.WriteLine($"  [恢复快照] → {snap.JsonState[..Math.Min(50, snap.JsonState.Length)]}...");
    }

    // ── 观察者模式 ──
    public void Subscribe(IDocumentObserver o) => _observers.Add(o);
    public void Unsubscribe(IDocumentObserver o) => _observers.Remove(o);

    public void NotifyChanged()
    {
        foreach (var obs in _observers)
            obs.OnDocumentChanged(this);
    }
}

// ═══════════ Client:UI 层(模拟) ═══════════

public class EditorUI : IDocumentObserver
{
    private readonly Document _doc;

    public EditorUI(Document doc)
    {
        _doc = doc;
        _doc.Subscribe(this);
    }

    public void InsertText(string text)
    {
        _doc.DoBackup();  // 编辑前快照
        _doc.Execute(new InsertBlockCommand(_doc.Root, new TextBlock { Content = text }));
    }

    public void OnDocumentChanged(Document doc)
        => Console.WriteLine($"  [UI] 刷新 — 字数: {doc.Root.WordCount}");
}

// ── 运行 ──
var doc = new Document();
var ui  = new EditorUI(doc);

ui.InsertText("Hello World 你好");    // UI 自动刷新
doc.DoBackup();                        // 手动快照

doc.Undo();                            // 撤销
doc.RestoreLastSnapshot();             // 回退到快照

九、陷阱与进阶

陷阱 1:单例的线程安全——双重检查锁的常见 Bug

// ❌ 有 bug 的双重检查锁
public class BadSingleton
{
    private static BadSingleton? _instance;
    private static readonly object _lock = new();

    private BadSingleton() { }

    public static BadSingleton Instance
    {
        get
        {
            if (_instance is null)        // 读取
            {
                lock (_lock)
                {
                    if (_instance is null)
                        _instance = new BadSingleton();
                    // ⚠ 问题:指令重排可能导致 _instance 非 null
                    //    但其构造函数尚未执行完毕。
                    //    另一个线程在锁外读到非 null → 拿到未构造完的对象。
                }
            }
            return _instance;
        }
    }
}

// ✅ 正确做法 1:volatile 阻止指令重排
public class CorrectSingleton1
{
    private static volatile CorrectSingleton1? _instance;
    // volatile 禁止 CPU/编译器对读写重排序
    // ...
}

// ✅ 正确做法 2:Lazy<T>(推荐)
public sealed class CorrectSingleton2
{
    private static readonly Lazy<CorrectSingleton2> _lazy = new(() => new CorrectSingleton2());
    public static CorrectSingleton2 Instance => _lazy.Value;
    // Lazy<T> 内部用了正确的内存屏障,零心智负担
}

陷阱 2:观察者的内存泄漏——忘记取消订阅

// ❌ 泄漏场景
public class StockView : IDisposable
{
    private readonly Stock _stock;

    public StockView(Stock stock)
    {
        _stock = stock;
        _stock.PriceChanged += OnPriceChanged;  // ← 订阅
    }

    private void OnPriceChanged(object? s, StockEventArgs e)
        => Console.WriteLine($"UI 更新: {e.Symbol} → ¥{e.NewPrice}");

    public void Dispose()
    {
        // ⚠ 忘记 -= 取消订阅!
        // Stock 仍然持有 StockView 的引用(通过委托)
        // StockView 无法被 GC → 内存泄漏
    }
}

// ✅ 正确做法
public class StockViewFixed : IDisposable
{
    private readonly StockWithEvent _stock;
    private readonly EventHandler<StockEventArgs> _handler;

    public StockViewFixed(StockWithEvent stock)
    {
        _stock = stock;
        _handler = (_, e) => Console.WriteLine($"UI: {e.Symbol} ¥{e.NewPrice}");
        _stock.PriceChanged += _handler;
    }

    public void Dispose()
    {
        _stock.PriceChanged -= _handler;  // ← 必须取消
    }
}

// ✅ C# 的"弱事件"模式(WPF 常用):
// WeakEventManager 让事件不阻止 GC,适用于 Subject 比 Observer 活得久的场景

附录A:GoF 23 种模式完整列表

创建型(5)

  1. 工厂方法(Factory Method) ⭐⭐⭐
  2. 抽象工厂(Abstract Factory) ⭐⭐
  3. 生成器(Builder) ⭐⭐
  4. 原型(Prototype) ⭐⭐
  5. 单例(Singleton) ⭐⭐⭐

结构型(7)

  1. 适配器(Adapter) ⭐⭐⭐
  2. 桥接(Bridge) ⭐⭐
  3. 组合(Composite) ⭐⭐
  4. 装饰(Decorator) ⭐⭐⭐
  5. 外观(Facade) ⭐⭐⭐
  6. 享元(Flyweight) ⭐⭐
  7. 代理(Proxy) ⭐⭐⭐

行为型(11)

  1. 责任链(Chain of Responsibility) ⭐⭐
  2. 命令(Command) ⭐⭐
  3. 解释器(Interpreter) ⭐
  4. 迭代器(Iterator) ⭐⭐⭐
  5. 中介者(Mediator) ⭐⭐
  6. 备忘录(Memento) ⭐⭐
  7. 观察者(Observer) ⭐⭐⭐
  8. 状态(State) ⭐⭐
  9. 策略(Strategy) ⭐⭐⭐
  10. 模板方法(Template Method) ⭐⭐⭐
  11. 访问者(Visitor) ⭐⭐

附录B:30 秒速查表

你想… 用这个
运行时决定 new 哪个子类 工厂方法 ⭐⭐⭐
保证一族产品配套不混搭 抽象工厂 ⭐⭐
构造太复杂,分步组装 生成器 ⭐⭐
复制模板微调,省去重建 原型 ⭐⭐
全局唯一实例 单例 ⭐⭐⭐
翻译不兼容的接口 适配器 ⭐⭐⭐
拆开两个独立变化维度 桥接 ⭐⭐
让叶子与树枝对外一致 组合 ⭐⭐
套娃叠加能力 装饰 ⭐⭐⭐
给复杂子系统开扇门 外观 ⭐⭐⭐
大量对象共享公共部分 享元 ⭐⭐
控制对对象的访问 代理 ⭐⭐⭐
多个处理器排队处理 责任链 ⭐⭐
撤销/重做/排队执行 命令 ⭐⭐
foreach 遍历不关心底层 迭代器 ⭐⭐⭐
网状依赖收拢成星形 中介者 ⭐⭐
状态快照回滚 备忘录 ⭐⭐
一对多自动通知 观察者 ⭐⭐⭐
行为随内部状态切换 状态 ⭐⭐
运行时替换算法 策略 ⭐⭐⭐
骨架固定填空步骤 模板方法 ⭐⭐⭐
元素不动,操作扩展 访问者 ⭐⭐
解析简单 DSL 解释器

文章声明

内容准确性: 我会尽力确保所分享信息的准确性和可靠性,但由于个人知识有限,难免会有疏漏或错误。如果您在阅读过程中发现任何问题,请不吝赐教,我将及时更正。
AI: 文章部分内容参考了大语言模型生成的内容。

posted on 2026-07-28 17:54  wubing7755  阅读(34)  评论(0)    收藏  举报