csharp:gof23种设计模式

https://cloud.tencent.com/developer/article/2189712

<重写>

创建型模式工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型模式策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。

设计模式深度解析 (Design Patterns Deep Dive)

设计模式不仅仅是代码片段,它们是面向对象设计原则(SOLID)的具体体现。掌握它们能让开发者在面对复杂业务需求时,构建出可维护可扩展低耦合的系统。

前置知识:SOLID 原则

  • S - 单一职责原则 (SRP):一个类只负责一件事。
  • O - 开闭原则 (OCP):对扩展开放,对修改关闭(核心原则)。
  • L - 里氏替换原则 (LSP):子类必须能替换父类。
  • I - 接口隔离原则 (ISP):使用多个专门的接口,优于使用一个单一的总接口。
  • D - 依赖倒置原则 (DIP):依赖于抽象,而不是具体实现。

工厂模式旨在解决“对象创建”这一核心问题。它将“实例化谁”的逻辑从“使用对象”的逻辑中剥离出来。

虽然它不属于 GoF 23种设计模式,但它是理解工厂模式的起点。

1. 生活场景类比 你走进一家披萨店(工厂),直接跟服务员说:“我要一份芝士披萨”。你不需要知道披萨是怎么烤的,也不需要知道面粉哪里买的,你只需要给出一个指令(参数)。

2. 核心结构

  • Factory (工厂类):包含逻辑判断(Switch/If),决定创建哪种产品。
  • Product (抽象产品):所有产品的父类或接口。
  • ConcreteProduct (具体产品):被创建的具体对象。

3. 代码实现 (C#)

// --- 抽象产品 ---
public abstract class Pizza
{
    public abstract void Prepare();
    public void Bake() => Console.WriteLine("正在烘烤披萨...");
    public void Cut() => Console.WriteLine("正在切分披萨...");
    public void Box() => Console.WriteLine("正在打包披萨...");
}
 
// --- 具体产品 A ---
public class CheesePizza : Pizza
{
    public override void Prepare() => Console.WriteLine("准备芝士和面团");
}
 
// --- 具体产品 B ---
public class PepperoniPizza : Pizza
{
    public override void Prepare() => Console.WriteLine("准备意大利香肠和面团");
}
 
// --- 简单工厂 (上帝类) ---
public class SimplePizzaFactory
{
    // 静态方法通常是简单工厂的特征
    public static Pizza CreatePizza(string type)
    {
        Pizza pizza = null;
 
        // 缺点:如果想加一种“海鲜披萨”,必须修改这里的源代码
        // 这违反了“开闭原则”
        if (type.Equals("cheese"))
        {
            pizza = new CheesePizza();
        }
        else if (type.Equals("pepperoni"))
        {
            pizza = new PepperoniPizza();
        }
        else
        {
            throw new ArgumentException("我们没有这种披萨");
        }
 
        return pizza;
    }
}
 
// --- 客户端代码 ---
// 客户端只依赖 Factory 和抽象 Pizza,不依赖具体 Pizza 类
// Pizza pizza = SimplePizzaFactory.CreatePizza("cheese");
// pizza.Prepare();
// pizza.Bake();

4. 深度分析

  • 优点:客户端免除了创建对象的责任。
  • 缺点:工厂类集中了所有产品的创建逻辑,一旦工厂类出问题,整个系统瘫痪。且添加新产品必须修改工厂源码。

1. 核心定义 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法把实例化推迟到子类。

2. 场景升级 披萨店生意太好了,开了分店。

  • 纽约分店:喜欢做薄底的披萨。
  • 芝加哥分店:喜欢做厚底的披萨。
  • 如果用简单工厂,`switch` 语句会变得极其复杂。现在我们让每个分店(子工厂)自己决定怎么做披萨。

3. 结构变化

  • AbstractFactory:声明创建产品的接口(`CreatePizza`)。
  • ConcreteFactory:实现接口,创建具体的产品(纽约工厂产纽约披萨)。

4. 代码实现 (C#)

// --- 抽象产品 (同上,略) ---
 
// --- 具体产品:纽约风味 ---
public class NYStyleCheesePizza : Pizza
{
    public override void Prepare() => Console.WriteLine("准备纽约薄底面团和酱料");
}
 
// --- 具体产品:芝加哥风味 ---
public class ChicagoStyleCheesePizza : Pizza
{
    public override void Prepare() => Console.WriteLine("准备芝加哥厚底面团和酱料");
}
 
// --- 抽象工厂 (核心层) ---
public abstract class PizzaStore
{
    // 这是一个具体的方法,包含了业务逻辑
    public Pizza OrderPizza(string type)
    {
        Pizza pizza;
 
        // 【关键点】CreatePizza 是抽象的,具体由子类实现
        // 这里体现了“依赖倒置”:高层模块不依赖低层模块
        pizza = CreatePizza(type);
 
        pizza.Prepare();
        pizza.Bake();
        return pizza;
    }
 
    // 工厂方法:由子类实现具体的实例化逻辑
    protected abstract Pizza CreatePizza(string type);
}
 
// --- 具体工厂:纽约店 ---
public class NYPizzaStore : PizzaStore
{
    protected override Pizza CreatePizza(string type)
    {
        if (type.Equals("cheese")) return new NYStyleCheesePizza();
        // 可以扩展其他纽约风味...
        return null;
    }
}
 
// --- 具体工厂:芝加哥店 ---
public class ChicagoPizzaStore : PizzaStore
{
    protected override Pizza CreatePizza(string type)
    {
        if (type.Equals("cheese")) return new ChicagoStyleCheesePizza();
        return null;
    }
}
 
// --- 客户端 ---
// PizzaStore nyStore = new NYPizzaStore();
// Pizza pizza = nyStore.OrderPizza("cheese"); // 得到纽约风味

5. 深度分析

  • 符合开闭原则:如果想开一家“加州分店”,只需新建一个 `CaliforniaPizzaStore` 类,无需修改现有代码。
  • 复杂度:每增加一种产品,就需要增加一个具体产品类和一个具体工厂类,导致类数量爆炸。

1. 核心定义 提供一个接口,用于创建相关或依赖对象的家族,而不需要指定它们具体的类。

2. 场景升级:产品族 披萨店不仅要造披萨,还要提供配套的原料(酱料、面团、蛤蜊)。

  • 纽约工厂:生产(薄面团 + 大蒜酱 + 新鲜蛤蜊)。
  • 芝加哥工厂:生产(厚面团 + 番茄酱 + 冷冻蛤蜊)。
  • 约束:你不能在纽约披萨里用芝加哥的厚面团。这叫产品族约束

3. 代码实现 (C#)

// --- 抽象原料家族 ---
public interface IDough { string GetName(); }
public interface ISauce { string GetName(); }
 
// --- 具体原料 ---
public class ThinCrustDough : IDough { public string GetName() => "薄脆面团"; }
public class ThickCrustDough : IDough { public string GetName() => "厚实面团"; }
public class MarinaraSauce : ISauce { public string GetName() => "大蒜番茄酱"; }
public class PlumTomatoSauce : ISauce { public string GetName() => "小番茄酱"; }
 
// --- 抽象工厂 (定义产品族) ---
// 注意:这里有多个方法,每个方法创建一个产品
public interface IPizzaIngredientFactory
{
    IDough CreateDough();
    ISauce CreateSauce();
}
 
// --- 具体工厂:纽约原料工厂 ---
public class NYPizzaIngredientFactory : IPizzaIngredientFactory
{
    public IDough CreateDough() => new ThinCrustDough();
    public ISauce CreateSauce() => new MarinaraSauce();
}
 
// --- 具体工厂:芝加哥原料工厂 ---
public class ChicagoPizzaIngredientFactory : IPizzaIngredientFactory
{
    public IDough CreateDough() => new ThickCrustDough();
    public ISauce CreateSauce() => new PlumTomatoSauce();
}
 
// --- 客户端 (披萨类) ---
public class CheesePizza : Pizza
{
    IPizzaIngredientFactory _ingredientFactory;
 
    // 通过构造函数注入工厂,披萨不需要知道原料具体是哪里的
    public CheesePizza(IPizzaIngredientFactory ingredientFactory)
    {
        _ingredientFactory = ingredientFactory;
    }
 
    public override void Prepare()
    {
        Console.WriteLine("准备中..." + _ingredientFactory.CreateDough().GetName());
        Console.WriteLine("加酱料..." + _ingredientFactory.CreateSauce().GetName());
    }
}

4. 深度分析

  • 区别:工厂方法模式针对的是一个产品的等级结构;抽象工厂模式针对的是多个产品的等级结构(产品族)。
  • 缺点扩展困难。如果在产品族中增加一个新产品(比如增加 `CreateCheese()`),则需要修改抽象工厂接口以及所有的具体工厂实现类,这严重违反了开闭原则。

单例模式看似简单,但在多线程环境下极易出错。

  • 资源共享:数据库连接池、线程池。
  • 配置管理:读取 `appsettings.json` 的配置对象,全局只需要一份。
  • 硬件控制:控制打印机或串口通信,避免指令冲突。

不要再手写 `Double-Check Locking` (双重检查锁) 了,除非你在使用非常古老的 .NET 版本。现代 C# 使用 `System.Lazy<T>`。

public sealed class Singleton
{
    // 1. 使用 Lazy<T> 实现延迟加载和线程安全
    // LazyThreadSafetyMode.ExecutionAndPublication 确保只有一个线程能初始化值
    private static readonly Lazy<Singleton> _lazy = 
        new Lazy<Singleton>(() => new Singleton());
 
    // 2. 公共访问点
    public static Singleton Instance { get { return _lazy.Value; } }
 
    // 3. 私有构造函数:阻止外部 new Singleton()
    private Singleton()
    {
        Console.WriteLine("单例实例被初始化 (Thread ID: " + Environment.CurrentManagedThreadId + ")");
    }
 
    // 业务方法
    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

为什么用 sealed (密封类)? 防止其他类继承单例类,因为派生类可能会通过调用基类构造函数来创建新的实例,从而破坏单例的唯一性。

* 推模式:主题 (Subject) 主动将数据发送给观察者。

  • *优点*:观察者能立刻获取数据。
  • *缺点*:如果数据量大,或者观察者不需要全部数据,会造成浪费。

* 拉模式:主题只通知“有更新了”,观察者自己去主题里取数据。

  • *优点*:观察者按需索取。

在 .NET 中,我们很少手动实现 `Attach/Detach` 列表,而是使用内置的委托事件机制。

// 1. 定义传递的数据 (EventArgs)
public class StockPriceChangedEventArgs : EventArgs
{
    public decimal Price { get; set; }
    public DateTime Time { get; set; }
}
 
// 2. 主题 (Subject)
public class StockMarket
{
    // 声明一个事件
    // EventHandler<T> 是 .NET 标准委托
    public event EventHandler<StockPriceChangedEventArgs> PriceChanged;
 
    public void UpdatePrice(decimal newPrice)
    {
        Console.WriteLine($"股市更新价格: {newPrice}");
 
        // 触发事件 (检查是否有人订阅)
        PriceChanged?.Invoke(this, new StockPriceChangedEventArgs 
        { 
            Price = newPrice, 
            Time = DateTime.Now 
        });
    }
}
 
// 3. 观察者 (Observer)
public class Investor
{
    public string Name { get; set; }
 
    public void OnPriceChanged(object sender, StockPriceChangedEventArgs e)
    {
        if (e.Price < 100)
        {
            Console.WriteLine($"{Name} 发现价格低至 {e.Price},正在买入!");
        }
        else
        {
            Console.WriteLine($"{Name} 正在观望...");
        }
    }
}
 
// --- 客户端 ---
// StockMarket market = new StockMarket();
// Investor buffett = new Investor { Name = "巴菲特" };
// 
// // 订阅 (Attach)
// market.PriceChanged += buffett.OnPriceChanged;
// 
// market.UpdatePrice(99.0m);
// // 取消订阅 (Detach)
// market.PriceChanged -= buffett.OnPriceChanged;
模式 描述 优点 缺点 适用场景
简单工厂 一个工厂类根据参数创建不同产品。 简单,隐藏创建细节。 违背开闭原则,工厂类职责过重。 产品少,逻辑简单,不常修改。
工厂方法 定义创建接口,子类实现具体创建。 符合开闭原则,高内聚。 类数量膨胀,增加系统复杂度。 产品种类多,且经常增加新产品。
抽象工厂 创建一系列相关或依赖对象的接口。 保证产品族一致性,解耦。 难以支持新种类的产品(接口改动大)。 系统需要多个产品族,且产品之间有约束关系。

GoF 23种设计模式详解

设计模式(Design Patterns)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

GoF(Gang of Four)提出的23种设计模式主要分为三大类:

  • 创建型模式 (Creational Patterns):共5种,关注对象的创建过程。
  • 结构型模式 (Structural Patterns):共7种,关注类和对象的组合。
  • 行为型模式 (Behavioral Patterns):共11种,关注对象之间的通信。

创建型模式将对象的创建与使用分离,降低系统的耦合度。

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

应用场景

  • 数据库连接池。
  • Spring 中的 Bean 默认是单例。
  • 线程池。

代码示例 (双重检查锁):

public class Singleton {
    private volatile static Singleton uniqueInstance;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
  • 在系统增加新的产品时,只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
// 抽象产品
interface Product { void use(); }
 
// 具体产品
class ConcreteProductA implements Product {
    public void use() { System.out.println("Use Product A"); }
}
 
// 抽象工厂
interface Factory { Product createProduct(); }
 
// 具体工厂
class ConcreteFactoryA implements Factory {
    public Product createProduct() { return new ConcreteProductA(); }
}

定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

区别:工厂方法模式针对的是一个产品等级结构;抽象工厂模式针对的是多个产品等级结构(产品族)。

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

应用场景

  • 对象内部结构复杂,属性很多(如 StringBuilder)。
  • 需要生成的对象具有复杂的内部结构。
public class User {
    private String name;
    private int age;
 
    // 私有构造函数
    private User(UserBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }
 
    public static class UserBuilder {
        private String name;
        private int age;
 
        public UserBuilder(String name) { this.name = name; }
        public UserBuilder age(int age) { this.age = age; return this; }
        public User build() { return new User(this); }
    }
}
// 使用: User u = new User.UserBuilder("John").age(25).build();

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

核心:实现 `Cloneable` 接口,重写 `clone()` 方法。注意深拷贝与浅拷贝的区别。

结构型模式关注如何将类或对象结合在一起形成更大的结构。

定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

角色

  • Target(目标接口)
  • Adaptee(被适配者)
  • Adapter(适配器)
// 目标接口
interface Target { void request(); }
 
// 被适配者
class Adaptee { public void specificRequest() { System.out.println("Specific logic"); } }
 
// 适配器
class Adapter implements Target {
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
    public void request() { adaptee.specificRequest(); }
}

定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

场景:如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系。例如:不同品牌的电脑(抽象)安装不同的操作系统(实现)。

定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

场景:文件系统(文件夹和文件)、菜单树。

定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

场景:Java I/O 流 (`new BufferedReader(new FileReader(…))`)。

interface Component { void operation(); }
 
class ConcreteComponent implements Component {
    public void operation() { System.out.println("Base Operation"); }
}
 
class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) { this.component = component; }
    public void operation() { component.operation(); }
}
 
class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component component) { super(component); }
    public void operation() {
        super.operation();
        System.out.println("Added Behavior");
    }
}

定义:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

场景:Controller 层调用 Service 层(Service 层可能聚合了多个 DAO 或其他 Service)。

定义:运用共享技术有效地支持大量细粒度的对象。

场景

  • Java 中的 String 常量池。
  • 数据库连接池(某种意义上)。
  • 围棋游戏中的棋子(颜色相同,位置不同)。

定义:为其他对象提供一种代理以控制对这个对象的访问。

分类

  • 静态代理:代码编译时确定代理关系。
  • 动态代理:运行时生成代理类(JDK 动态代理、CGLIB)。

场景:Spring AOP(面向切面编程),RPC 远程调用。

行为型模式关注对象之间的职责分配和算法的封装。

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

场景:Servlet Filter 过滤器链、OA 审批流程。

定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

场景:GUI 按钮点击事件、事务的回滚。

定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

场景:正则表达式编译、SQL 解析、Spring EL 表达式。

定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。

现状:Java 中的 `Iterator` 接口及 `foreach` 循环已经内置了此模式,通常不需要手动实现。

定义:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

场景:MVC 框架中的 Controller(C)就是 Model(M)和 View(V)的中介者。

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

场景:游戏存档、Ctrl+Z 撤销操作。

定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

场景:事件驱动系统、Vue 的双向绑定原理、消息队列(Pub/Sub)。

// 主题(被观察者)
class Subject {
    private List<Observer> observers = new ArrayList<>();
    public void attach(Observer observer) { observers.add(observer); }
    public void notifyObservers() {
        for (Observer o : observers) o.update();
    }
}
 
// 观察者接口
interface Observer { void update(); }
 
// 具体观察者
class ConcreteObserver implements Observer {
    public void update() { System.out.println("Received update!"); }
}

定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

场景:订单状态流转(待支付 → 已支付 → 发货 → 完成)。

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

场景:支付方式选择(支付宝、微信、银联)、排序算法选择。

// 策略接口
interface Strategy { int doOperation(int num1, int num2); }
 
// 具体策略
class OperationAdd implements Strategy {
    public int doOperation(int num1, int num2) { return num1 + num2; }
}
 
class OperationMultiply implements Strategy {
    public int doOperation(int num1, int num2) { return num1 * num2; }
}
 
// 上下文
class Context {
    private Strategy strategy;
    public Context(Strategy strategy) { this.strategy = strategy; }
    public int executeStrategy(int num1, int num2) {
        return strategy.doOperation(num1, num2);
    }
}

定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

场景:Spring 的 `JdbcTemplate`、Servlet 的 `doGet`/`doPost` 流程。

定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

特点:最复杂的设计模式之一。适用于数据结构相对稳定,但作用于结构上的操作经常变化的场景。

模式类型 模式名称 核心关键词
创建型 单例 (Singleton) 唯一实例
工厂方法 (Factory Method) 延迟实例化
抽象工厂 (Abstract Factory) 产品族
建造者 (Builder) 复杂对象构建
原型 (Prototype) 克隆
结构型 适配器 (Adapter) 接口转换
桥接 (Bridge) 抽象与实现分离
组合 (Composite) 树形结构
装饰器 (Decorator) 动态增强
外观 (Facade) 统一入口
享元 (Flyweight) 共享对象
代理 (Proxy) 控制访问
行为型 责任链 (Chain of Responsibility) 传递请求
命令 (Command) 请求封装
解释器 (Interpreter) 语法解析
迭代器 (Iterator) 顺序访问
中介者 (Mediator) 降低耦合
备忘录 (Memento) 状态保存
观察者 (Observer) 通知依赖
状态 (State) 状态改变行为
策略 (Strategy) 算法替换
模板方法 (Template Method) 算法骨架
访问者 (Visitor) 操作分离
请输入您的评论. 可以使用维基语法:
 

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/gof23种设计模式.txt
  • 最后更改: 2025/12/22 10:38
  • 张叶安