设计模式

面向对象编程基本原则

S:单一职责原则 Single Responsibility Principle
一个类应该只有一个引起它变化的原因。即一个类只负责一项职责,避免臃肿和牵一发而动全身。

O:开闭原则 Open/Closed Principle
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即尽量通过增加新代码来扩展功能,而不是修改已有的核心代码。

L:里氏替换原则 Liskov Substitution Principle
子类对象必须能够替换掉它们的父类对象。即子类可以扩展父类的功能,但不能改变父类原有的功能(如重写方法时不能破坏父类定义的约定)。

I:接口隔离原则 Interface Segregation Principle
客户端不应该被迫依赖它不使用的方法。即应将庞大臃肿的接口拆分成更小、更具体的接口,避免“胖接口”带来的冗余依赖。

D:依赖倒置原则 Dependency Inversion Principle
高层模块不应依赖低层模块,两者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。即通过面向接口编程,降低模块间的耦合度。

设计模式

单例

一个类只有一个实例,并提供一个全局访问点

public class Singleton
{
    private static Singleton uniqueInstance; //私有变量(静态!),记录唯一实例
    //此处用到“自指”,Singleton的唯一实例放在此处
    
    private Singleton()
     {
        //私有构造函数
        //可为空,作用是为了在外界不能new实例
    }
    
    public static Singleton GetInstance() //公共方法:提供此类型的唯一全局访问点
    {
        if( uniqueInstance == null ) uniqueInstance = new Singleton();
        return uniqueInstance;
    }
}

与习惯不同的是,创建类的实例不在外界通过new关键字创建,而是在类里面私有创建。

多线程模式下,可能会创建多个实例,违背单例模式初衷。对此,确保线程同步

public static Singleton GetInstance()
{
    // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
    // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
    // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
    // 双重锁定:在实例已创建后避免每次调用都获取锁,减少同步带来的性能开销。
    if (uniqueInstance == null)
    	lock (locker)
    	{
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null) uniqueInstance = new Singleton();
        }
    return uniqueInstance;
}

现代写法:

public class Singleton
{
    private Singleton(){ }

    private static readonly Lazy<Singleton> _lazy = new Lazy<Singleton>( () => new Singleton() );
    
    public static Singleton Instance => _lazy.Value;
}

补充:

protected internal :同一程序集(dll/exe)或派生类都可以访问

private protected:同一程序集(dll/exe)的派生类才可以访问。

相比静态类,单例的优势是可以继承和实现接口,可以作为参数传递。使用场景:日志记录,配置、缓存管理、数据库连接池......

简单工厂模式

public abstract class Food { }
public class Tomato : Food { }
public class Potato : Food { }

public class SimpleFactory   //此处设计成静态类同样合理
{
    public static Food CreateFood()
    {
        Food food = new Tomato();
        ......
        return food;
    }
}
class Customer
{
    static void Main()
    {
        Food food = SimpleFactory.CreateFood();
        food.Method();
    }
}

外层是实例类时,内部方法选择静态方法的情况:

  • 方法不使用实例字段/属性
  • 方法是通用的,不是“对象行为”
  • 提供单例访问点

工厂方法模式

public abstract class Food {}
public class Tomato : Food {}
public class Potato : Food {}

public abstract class Factory
{
    public abstract Food Method();
}

public class TomatoFactory : Factory
{
    public override Food Method(){}
}

public class PotatoFactory : Factory
{
    public override Food Method(){}
}

class Client
{
    static void Main()
    {
        Factory factory1 = new TomatoFactory();
        Factory factory2 = new PotatoFactory();
        ......
    }
}

这种模式下,可以利用多态性完成系统扩展。符合了开放封闭原则(即对扩展开发,对修改封闭)

抽象工厂模式

// 抽象产品类
public abstract class Vegetable { }
public abstract class Fruit { }

// 具体产品类
public class Tomato : Vegetable { }
public class Potato : Vegetable { }
public class Apple : Fruit { }
public class PotatoFruit : Fruit { }

// 抽象工厂接口
public interface IFactory
{
    Vegetable CreateVegetable();
    Fruit CreateFruit();
}

// 具体工厂类
public class TomatoFactory : IFactory
{
    public Vegetable CreateVegetable() => new Tomato();
    public Fruit CreateFruit() => new Apple();
}

public class PotatoFactory : IFactory
{
    public Vegetable CreateVegetable() => new Potato();
    public Fruit CreateFruit() => new PotatoFruit();
}

class Client
{
    static void Main()
    {
        // 创建番茄工厂,可以生产番茄相关的所有产品
        IFactory tomatoFactory = new TomatoFactory();
        Vegetable tomatoVeg = tomatoFactory.CreateVegetable();
        Fruit Apple = tomatoFactory.CreateFruit();
        
        // 创建土豆工厂,可以生产土豆相关的所有产品
        IFactory potatoFactory = new PotatoFactory();
        Vegetable potatoVeg = potatoFactory.CreateVegetable();
        Fruit PotatoFruit = potatoFactory.CreateFruit();
        
        // 使用产品...
    }
}

相比于工厂模式,抽象工厂不只是创建一种产品,而是一个 产品族。

根据面向对象的基本原则,尽量使用接口/父类声明实例,而非具体类,符合开闭原则、降低耦合,可动态切换。

建造者模式

将复杂对象的构建过程和它的表示分离,使得相同的构建过程可以创建不同的表示。

  • 复杂:包含多个部分和子对象

  • 构建:将各个部分组装起来

  • 表示:最终创建出的对象的具体形态、配置、属性

流式API:方法都返回this,允许链式调用

流式API、嵌套建造者

  • 每个建造者只关注自己的配置项
  • 建造者内部私有字段存储中间状态
  • 嵌套建造者通过 _parentBuilder 访问主建造者的数据

用于复杂对象构建

/////////////////////////////////顶级语句/////////////////////
// 可运行的示例
var superPC = new ComputerBuilder()
    .Hardware
        .WithCPU("Intel Core i9 14900K")
    .Peripherals
        .AddPeripheral("Dell Monitor")
        .AddPeripheral("Logitech Mouse")
    .Build();

superPC.Display();

// 也可以这样调用
var pc2 = new ComputerBuilder()
    .Hardware
        .WithCPU("AMD Ryzen 9")
        .Build();  // 只有 CPU,没有外设

pc2.Display();

// 或者只添加外设
var pc3 = new ComputerBuilder()
    .Peripherals
        .AddPeripheral("Keyboard")
        .AddPeripheral("Headset")
    .Build();  // 只有外设,没有 CPU

pc3.Display();
/////////////////////////////////////////////////////////////

class Computer
{
    public string CPU { get; set; }
    public string[] Peripherals { get; set; }

    public void Display()
    {
        Console.WriteLine($"CPU: {CPU}");
        Console.WriteLine("Peripherals: " + string.Join(", ", Peripherals ?? []));
    }
}

class ComputerBuilder
{
    private string _cpu = string.Empty;
    private readonly List<string> _peripherals = [];

    // 只读属性,返回对应的建造者
    public HardwareBuilder Hardware => new(this);
    public PeripheralsBuilder Peripherals => new(this);

    // ComputerBuilder 自己的 Build 方法
    public Computer Build()
    {
        return new Computer
        {
            CPU = _cpu,
            Peripherals = _peripherals.ToArray()
        };
    }

    // 嵌套建造者类
    public class HardwareBuilder
    {
        private readonly ComputerBuilder _parentBuilder;

        public HardwareBuilder(ComputerBuilder parentBuilder)
        {
            _parentBuilder = parentBuilder;
        }

        public HardwareBuilder WithCPU(string cpu)
        {
            _parentBuilder._cpu = cpu;
            return this;
        }

        // 从 HardwareBuilder 切换到 PeripheralsBuilder
        public PeripheralsBuilder Peripherals => _parentBuilder.Peripherals;

        // 也可以直接 Build
        public Computer Build() => _parentBuilder.Build();
    }

    public class PeripheralsBuilder
    {
        private ComputerBuilder _parentBuilder;

        public PeripheralsBuilder(ComputerBuilder parentBuilder)
        {
            _parentBuilder = parentBuilder;
        }

        public PeripheralsBuilder AddPeripheral(string peripheral)
        {
            _parentBuilder._peripherals.Add(peripheral);
            return this;
        }

        // 允许继续添加外设后 Build
        public Computer Build() => _parentBuilder.Build();
    }
}

用接口约束配置次序

var client = Client.Create()
    .WithName("Richard")
    .WithPassword("password")
    .SetEnable(true)
    .Build();
client.Print();

var client2 = Client.Create()
    .WithName("Richard")
    .WithPassword("1111111111")
    .Build();
client2.Print();
//此例子中,必须要配置姓名和密码,并且先配置姓名再配置密码,不过可以不设置是否可用。
public class Client
{
    private Client() { }
    public string Name { get; private set; } = "";
    public string Password { get; private set; } = "";
    public bool IsEnable { get; private set; }

    public void Print()
    {
        if (IsEnable)
        {
            Console.WriteLine(Name);
            Console.WriteLine(Password);
        }
        else Console.WriteLine("Unenable");
    }
    public static INameBuilder Create() => new ClientBuilder();
    public class ClientBuilder : INameBuilder, IPasswordBuilder, IEnableBuilder, IBuildBuilder
    {
        private readonly Client _client = new Client();
        public IPasswordBuilder WithName(string name)
        {
            _client.Name = name;
            return this;
        }
        public IEnableBuilder WithPassword(string password)
        {
            _client.Password = password;
            return this;
        }
        public IBuildBuilder SetEnable(bool enable)
        {
            _client.IsEnable = enable;
            return this;
        }
        public Client Build() => _client;
    }
}

public interface INameBuilder
{
    IPasswordBuilder WithName(string name);
}
public interface IPasswordBuilder
{
    IEnableBuilder WithPassword(string password);
}

public interface IEnableBuilder  //提供Build通路,目的是可以跳过配置Enable直接Build
{
    IBuildBuilder SetEnable(bool enable);
    Client Build();
}

public interface IBuildBuilder
{
    Client Build();
}

观察者模式

定义一种一对多的依赖关系,当一个对象(被观察者/主题)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

  • 一对多:一个被观察者可以对应多个观察者。
  • 依赖:观察者依赖于被观察者的状态变化。
  • 自动更新:被观察者状态变化时,观察者能自动执行相应行为。

核心机制:订阅-通知

  • 订阅:观察者向被观察者注册,表明“我对你的变化感兴趣”。
  • 通知:被观察者状态改变时,遍历所有订阅者并执行其预先定义好的更新方法。

一个场景:猫大叫一声,老鼠会逃跑,主人会醒来,宝宝会哭

/////////////////////////////////顶级语句/////////////////////
// 可运行的示例
// 1. 创建被观察者(主题):猫
Cat myCat = new();

// 2. 创建观察者,并让他们“订阅”猫的叫声
var mouse1 = new Mouse("米奇", myCat);
var mouse2 = new Mouse("杰瑞", myCat);
var master = new Master(myCat);
var baby = new Baby(myCat);

// 3. 猫叫一声,所有订阅者自动做出反应
myCat.Cry();

/////////////////////////////////////////////////////////////

// 被观察者:猫
class Cat
{
    // 定义一个事件,所有观察者订阅的就是这个事件
    public event Action? CryEvent;

    public void Cry()
    {
        Console.WriteLine("Cat Cry..");
        // 当猫叫时,触发事件,通知所有订阅者
        // 如果事件不为空,就调用所有注册的方法
        CryEvent?.Invoke();
    }
}

// 观察者1:老鼠
class Mouse
{
    private readonly string _name;

    public Mouse(string name, Cat cat)
    {
        _name = name;
        // 订阅猫的叫声事件:将自身的逃跑方法注册到猫的事件上
        cat.CryEvent += RunAway;
    }

    // 具体的响应行为
    private void RunAway()
    {
        Console.WriteLine($"{_name} 开始逃跑");
    }
}

// 观察者2:主人
class Master
{
    public Master(Cat cat)
    {
        // 订阅猫的叫声事件
        cat.CryEvent += WakeUp;
    }

    private void WakeUp()
    {
        Console.WriteLine("主人醒来");
    }
}

// 观察者3:宝宝
class Baby
{
    public Baby(Cat cat)
    {
        cat.CryEvent += WakeUp;
        cat.CryEvent += Cry;
    }

    private void WakeUp()
    {
        Console.WriteLine("宝宝醒来");
    }

    private void Cry()
    {
        Console.WriteLine("宝宝开始哭闹");
    }
}

策略模式

定义一系列算法,将每个算法封装起来,让它们可以互相替换。

策略模式让算法的变化独立于使用算法的客户。

会飞的鸭子

用继承,所有鸭子都会飞,结果橡皮鸭子也在天上飞,这显然不对。用策略模式来解决这个问题。

首先,我忍不住要献丑以下,用我的直觉写一下(模板方法模式,基类定义方法,子类提供实现,接口+继承+方法重写,静态类存放可复用方法实现)

IDuck duck1 = new MallardDuck("mallard duck");
IDuck duck2 = new RubberDuck("rubberDuck duck");
IDuck duck3 = new DecoyDuck("decoyDuck duck");

duck1.PrintName();
duck1.Fly();
duck1.Quack();

duck2.PrintName();
duck2.Fly();
duck2.Quack();

duck3.PrintName();
duck3.Fly();
duck3.Quack();

public interface IDuck
{
    void Quack();
    void Fly();
    string Name { get; }
    void PrintName();
}

public class Duck : IDuck
{
    public virtual void Quack(){}
    public virtual void Fly(){}
    public string Name { get; }
    public Duck(string name) => Name = name;
    public void PrintName() => Name.Dump();
}

public class MallardDuck : Duck
{
    public override void Quack() => FlyQuackType.QuackNormal();
    public override void Fly() => FlyQuackType.FlyWithWings(); 
    public MallardDuck(string name) : base(name){}
}

public class RubberDuck : Duck
{
    public override void Quack() => FlyQuackType.Squeak();
    public override void Fly() => FlyQuackType.FlyNoWay(); 
    public RubberDuck(string name) : base(name){}
}

public class DecoyDuck : Duck
{
    public override void Quack() => FlyQuackType.MuteQuack();
    public override void Fly() => FlyQuackType.FlyNoWay(); 
    public DecoyDuck(string name) : base(name){}
}

public static class FlyQuackType
{
    public static void FlyWithWings() => "用翅膀飞".Dump();
    public static void FlyNoWay() => "不会飞".Dump();
    public static void QuackNormal() => "嘎嘎".Dump();
    public static void Squeak() => "吱吱".Dump();
    public static void MuteQuack() => " ... ".Dump();
}

对于行为不会非常灵活的场景,这个写法简单直观。

但如果会有大量的子类、行为组合千变万化,且行为在运行时动态切换,就需要使用策略模式:

把所有行为包装成类和接口,原先的父类和接口添加“改变行为”的方法

对比之下的个人感受:

  1. 每一种行为都包装成了一个类,会产生很多的类
  2. 派生类里面不需要重写父类的虚方法,不需要在子类实现细节,而是直接使用行为类(这便是:组合优于继承 Composition over Inheritance),这样就能实现在外部通过新建行为类再传入,实现子类的行为修改
  3. 代码量激增,尤其是定义接口和类的部分。如果子类非常多,而行为并不算太多,且行为总是可能需要改动,那么就可以考虑使用策略模式
  4. 相比模板方法模式,实现了开闭原则,在外部可以修改子类的行为,只要将新的行为封成类然后传入修改即可。
IDuck duck1 = new MallardDuck("mallard duck");
IDuck duck2 = new RubberDuck("rubberDuck duck");
IDuck duck3 = new DecoyDuck("decoyDuck duck");

duck1.PrintName();
duck1.Fly();
duck1.Quack();
duck1.SetFly(new FlyNoWay());
duck1.SetQuack(new MuteQuack());
duck1.PrintName();
duck1.Fly();
duck1.Quack();

duck2.PrintName();
duck2.Fly();
duck2.Quack();

duck3.PrintName();
duck3.Fly();
duck3.Quack();
public interface IDuck
{
    void Quack();
    void Fly();
    void SetQuack(IQuack quack);
    void SetFly(IFly fly);
    string Name { get; }
    void PrintName();
}

public abstract class Duck : IDuck
{
    protected IQuack? QuackBehavior;
    protected IFly? FlyBehavior;
    
    protected Duck(string name, IFly fly, IQuack quack)
    {
        Name = name;
        QuackBehavior = quack;
        FlyBehavior = fly;
    }

    public void Quack() => QuackBehavior?.Quack();
    public void Fly() => FlyBehavior?.Fly();
    public string Name { get; }
    public void PrintName() => Name.Dump();
    public void SetQuack(IQuack quack) => QuackBehavior = quack;
    public void SetFly(IFly fly) => FlyBehavior = fly;
}

public class MallardDuck : Duck
{
    public MallardDuck(string name) : base(name, new FlyWithWings(), new QuackNormal()) { }
}

public class RubberDuck : Duck
{
    public RubberDuck(string name) : base(name, new FlyNoWay(), new Squeak()) { }
}

public class DecoyDuck : Duck
{
    public DecoyDuck(string name) : base(name, new FlyNoWay(), new MuteQuack()) { }
}

public interface IFly
{
    void Fly();
}

public interface IQuack
{
    void Quack();
}

public class FlyWithWings : IFly
{
    public void Fly() => "用翅膀飞".Dump();
}

public class FlyNoWay : IFly
{
    public void Fly() => "不会飞".Dump();
}

public class QuackNormal : IQuack
{
    public void Quack() => "嘎嘎".Dump();
}

public class Squeak : IQuack
{
    public void Quack() => "吱吱".Dump();
}

public class MuteQuack : IQuack
{
    public void Quack() => "。。。".Dump();
}

适配器模式

将一个类的接口转换成的另一个接口,使原本因接口不匹配而无法一起工作的类可以一起工作。

已有的类实现的很好,但方法名、参数、返回值等等需要转换,可以通过两种方式实现:

  • 类适配器 - 继承(c#不支持多继承,极少使用)
  • 对象适配器 - 组合(常用)
var logger = new MyLogger(new ThirdPartyLogger());
logger.Write("系统启动");

// 目标接口
public interface ILogger
{
    void Write(string message);
}

// 第三方库(无法修改)
public class ThirdPartyLogger
{
    public void WriteLog(string msg) => Console.WriteLine($"[ThirdParty] {msg}");
}

// 适配器
public class MyLogger(ThirdPartyLogger thirdPartyLogger) : ILogger
{
    public void Write(string message) => thirdPartyLogger.WriteLog(message);
}

组合模式

将对象组合成树形结构以表示‘部分-整体’的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

var company = new Department("moyuanfang");
var developerDepartment = new Department("开发部门");
var frontEndDepartment = new Department("前端部门");
var backEndDepartment = new Department("后端部门");
var productDepartment = new Department("产品部门");
var testDepartment = new Department("测试部门");

company.Add(developerDepartment);
company.Add(productDepartment);
company.Add(testDepartment);
developerDepartment.Add(frontEndDepartment);
developerDepartment.Add(backEndDepartment);

frontEndDepartment.Add(new Employee("lzl"));
backEndDepartment.Add(new Employee("lyo"));
backEndDepartment.Add(new Employee("hjx"));
backEndDepartment.Add(new Employee("shk"));
productDepartment.Add(new Employee("la"));
testDepartment.Add(new Employee("zmt"));

company.Display();

public class Employee : IOrganizationComponent
{
    public string Name{get;}
    public Employee(string name) => Name = name;
    public void Display()
    {
        Name.Dump();
    }
    public int GetEmployeeCount() => 1;
}

public class Department : IOrganizationComponent
{
    public string Name{get;}
    private List< IOrganizationComponent > _members = new();
    
    
    public Department(string name) => Name = name;
    
    public void Add(IOrganizationComponent component) => _members.Add(component);
    public void Remove(IOrganizationComponent component) => _members.Remove(component);
    
    public void Display()
    {
        Name.Dump();
        foreach(var member in _members)
            member.Display();
    }
    
    public int GetEmployeeCount()
    {
        return _members.Sum( m => m.GetEmployeeCount() );
    }
}

public interface IOrganizationComponent
{
    string Name { get; }
    void Display();
    int GetEmployeeCount();
}

优点:

  • 统一接口,简化客户端代码
  • 容易添加新类型组件
  • 符合开闭原则
  • 树形结构清晰

缺点:

  • 设计抽象,理解成本高
  • 不好限制组件
  • 叶子节点需要实现不适用的方法
    透明模式 vs 安全模式

透明模式

所有节点(叶子、容器)都实现同一个接口,接口中包含管理子节点的方法。

客户端无需区分叶子节点和容器节点,可以一视同仁地调用所有方法。

class File : IFileSystemItem 
{
    public void Add(IFileSystemItem item) =>
        throw new NotSupportedException();  // 叶子节点不支持的操作
}

符合“开闭原则”——新增节点类型不影响客户端。客户端代码极简方便。

但叶子节点被迫实现不需要的方法(接口污染)。

安全模式

公共接口只包含通用方法,管理子节点的方法只在容器类中定义。类型明确。

interface IFileSystemItem 
{
    void Display();  // 只有共同行为
}

class Folder : IFileSystemItem 
{
    public void Display() { /* ... */ }
    public void Add(IFileSystemItem item) { /* ... */ } 
}

能够避免误用方法导致异常,每个类职责明确。

但客户端需要向下类型转换才能调用方法。

扩展性差,新增节点类型后需要多处类型检查。

选择方式

如果叶节点和容器节点差异大,可以接受类型判断,则选用安全模式。

如果是内部团队约定清晰,可以用透明模式使代码简洁。

posted @ 2026-04-01 14:31  hélium  阅读(7)  评论(0)    收藏  举报