设计模式总结(GoF 23 种)
设计模式总结(GoF 23 种)
基于 GoF《设计模式:可复用面向对象软件的基础》,分类整理三大类 23 种模式。每个模式包含:一句话概括、结构图、代码示例、使用时机与反模式、相关模式。
使用原则(读前必看)
在进入 23 种模式之前,先确认你不会误用它们:
- 先有具体问题,再套模式。 不要拿着锤子找钉子。如果你说不清"不用模式会怎样",就还不需要模式。
- 最简单的方案优先。 如果普通 new、一个 if-else、一个 foreach 就能解决——就用它们。模式是来消除复杂度的,不是来增加复杂度的。
- 模式是用来沟通的词汇。 说"这里用策略模式"比说"我定义了一个接口然后用三个类实现它然后在运行时选一个"快十倍。模式让团队沟通高效,不是用来炫技的。
- 关注你引入的复杂度。 如果加了模式后类数量翻倍、调用深度增加三层、但解决的问题只是"未来可能会变"——你过度设计了。
标注说明:
| 标注 | 含义 |
|---|---|
| ⭐⭐⭐ | 日常必懂——项目中频繁出现,面试高频 |
| ⭐⭐ | 场景触发——特定场景下价值巨大 |
| ⭐ | 遇到了再看——不常用但解决特定痛点 |
| 🟢 | 结构简单,上手快 |
| 🟡 | 需要一点理解成本 |
| 🔴 | 需要较多背景知识 |
目录
- 一、创建型模式(5 种)
- 二、结构型模式(7 种)
- 三、行为型模式(11 种)
- 四、全模式对照矩阵
- 五、最容易混淆的组合
- 六、快速辨别决策树
- 七、.NET 框架内建映射
- 八、综合案例:文档编辑器
- 九、陷阱与进阶
一、创建型模式(5 种)
关注如何创建对象——将对象的创建与使用分离。
1. 工厂方法(Factory Method) ⭐⭐⭐ | 🟡
一句话: 父类定义创建接口,子类决定 new 哪个。
触发信号: 有几种变体,用哪种由运行时配置决定,调用方不想知道具体类名。
// ── 抽象 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。
// ── 抽象产品族 ──
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 个参数,一半可选,参数之间有依赖关系。
// ── 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、计算),但你需要大量只有微小差异的副本。
// ── 原型接口 ──
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) ⭐⭐⭐ | 🟢
一句话: 整个进程只有一份实例。
触发信号: 有些东西多了反而坏事——全局只能有一份。两份意味着两份不一致的状态。
// ── .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 的接口和我们的统一接口对不上,又不能改源码。
// ── 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 个。
// ── 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) ⭐⭐ | 🟡
一句话: 单个对象和对象集合对外接口一模一样。
触发信号: 一棵树上的任意节点,对外暴露出完全相同的接口,调用方不关心里面是叶子还是嵌套的枝干。
// ── 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ⁿ 个类。
// ── 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) ⭐⭐⭐ | 🟢
一句话: 给复杂子系统开一扇简单的门。
触发信号: 调三个类写八行代码才能干一件事,封成一个方法。
// ── 子系统(各自有复杂接口) ──
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) ⭐⭐ | 🟡
一句话: 大量对象的公共部分只存一份。
触发信号: 海量对象,寥寥几种配置。把重复的部分抽成共享池,每个对象只存自己独特的那一点数据。
// ── 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) ⭐⭐⭐ | 🟡
一句话: 控制对另一个对象的访问。
触发信号: 这个对象创建太贵/需要权限校验/不想重复查——配个代理人挡在外面。
// ── 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,而且随时增减调序。
// ── 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) ⭐⭐ | 🟡
一句话: 把操作封装成对象,支持撤销、排队、日志。
触发信号: 想把"做一件事"本身存下来——不是立刻做,而是排队、记录、或者事后反悔(撤销)。
// ── 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。
// ═════ 非泛型版本 ═════
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 个。
// ── 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 的底层逻辑。
// ── 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、收到消息就通知所有监听者。
// ── 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) ⭐⭐ | 🟡
一句话: 行为随内部状态改变,状态对象自己决定何时切换。
触发信号: 待支付→已支付→已发货,每个状态下能做的事完全不同。
// ── 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) ⭐⭐⭐ | 🟢
一句话: 一族算法各自封装,运行时互换。
触发信号: 同一件事有几种做法,运行时按条件选一种——不同计费规则、不同折扣策略、不同排序方式。
// ── 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) ⭐⭐⭐ | 🟢
一句话: 父类定义骨架,子类填空步骤。
触发信号: 一套固定流程,多个实现变体——数据导入、报表生成、构建管道:骨架相同,填空不同。
// ── 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 加入。
// ── 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) ⭐ | 🔴
一句话: 定义一种语言的文法表示,并为每个文法规则创建一个解释器。
触发信号: 用户输入的是一段"小语言"——查询条件、计算公式、配置规则——需要解析成程序能执行的指令。
// ── 简单布尔表达式解释器(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 编辑器。
涉及的模式
| 模式 | 解决的问题 | 在代码中的体现 |
|---|---|---|
| 组合 | 文档是 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)
- 工厂方法(Factory Method) ⭐⭐⭐
- 抽象工厂(Abstract Factory) ⭐⭐
- 生成器(Builder) ⭐⭐
- 原型(Prototype) ⭐⭐
- 单例(Singleton) ⭐⭐⭐
结构型(7)
- 适配器(Adapter) ⭐⭐⭐
- 桥接(Bridge) ⭐⭐
- 组合(Composite) ⭐⭐
- 装饰(Decorator) ⭐⭐⭐
- 外观(Facade) ⭐⭐⭐
- 享元(Flyweight) ⭐⭐
- 代理(Proxy) ⭐⭐⭐
行为型(11)
- 责任链(Chain of Responsibility) ⭐⭐
- 命令(Command) ⭐⭐
- 解释器(Interpreter) ⭐
- 迭代器(Iterator) ⭐⭐⭐
- 中介者(Mediator) ⭐⭐
- 备忘录(Memento) ⭐⭐
- 观察者(Observer) ⭐⭐⭐
- 状态(State) ⭐⭐
- 策略(Strategy) ⭐⭐⭐
- 模板方法(Template Method) ⭐⭐⭐
- 访问者(Visitor) ⭐⭐
附录B:30 秒速查表
| 你想… | 用这个 | ⭐ |
|---|---|---|
| 运行时决定 new 哪个子类 | 工厂方法 | ⭐⭐⭐ |
| 保证一族产品配套不混搭 | 抽象工厂 | ⭐⭐ |
| 构造太复杂,分步组装 | 生成器 | ⭐⭐ |
| 复制模板微调,省去重建 | 原型 | ⭐⭐ |
| 全局唯一实例 | 单例 | ⭐⭐⭐ |
| 翻译不兼容的接口 | 适配器 | ⭐⭐⭐ |
| 拆开两个独立变化维度 | 桥接 | ⭐⭐ |
| 让叶子与树枝对外一致 | 组合 | ⭐⭐ |
| 套娃叠加能力 | 装饰 | ⭐⭐⭐ |
| 给复杂子系统开扇门 | 外观 | ⭐⭐⭐ |
| 大量对象共享公共部分 | 享元 | ⭐⭐ |
| 控制对对象的访问 | 代理 | ⭐⭐⭐ |
| 多个处理器排队处理 | 责任链 | ⭐⭐ |
| 撤销/重做/排队执行 | 命令 | ⭐⭐ |
| foreach 遍历不关心底层 | 迭代器 | ⭐⭐⭐ |
| 网状依赖收拢成星形 | 中介者 | ⭐⭐ |
| 状态快照回滚 | 备忘录 | ⭐⭐ |
| 一对多自动通知 | 观察者 | ⭐⭐⭐ |
| 行为随内部状态切换 | 状态 | ⭐⭐ |
| 运行时替换算法 | 策略 | ⭐⭐⭐ |
| 骨架固定填空步骤 | 模板方法 | ⭐⭐⭐ |
| 元素不动,操作扩展 | 访问者 | ⭐⭐ |
| 解析简单 DSL | 解释器 | ⭐ |
文章声明
内容准确性: 我会尽力确保所分享信息的准确性和可靠性,但由于个人知识有限,难免会有疏漏或错误。如果您在阅读过程中发现任何问题,请不吝赐教,我将及时更正。
AI: 文章部分内容参考了大语言模型生成的内容。
posted on 2026-07-28 17:54 wubing7755 阅读(34) 评论(0) 收藏 举报
浙公网安备 33010602011771号