设计模式
面向对象编程基本原则
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();
}
对于行为不会非常灵活的场景,这个写法简单直观。
但如果会有大量的子类、行为组合千变万化,且行为在运行时动态切换,就需要使用策略模式:
把所有行为包装成类和接口,原先的父类和接口添加“改变行为”的方法
对比之下的个人感受:
- 每一种行为都包装成了一个类,会产生很多的类
- 派生类里面不需要重写父类的虚方法,不需要在子类实现细节,而是直接使用行为类(这便是:组合优于继承 Composition over Inheritance),这样就能实现在外部通过新建行为类再传入,实现子类的行为修改
- 代码量激增,尤其是定义接口和类的部分。如果子类非常多,而行为并不算太多,且行为总是可能需要改动,那么就可以考虑使用策略模式
- 相比模板方法模式,实现了开闭原则,在外部可以修改子类的行为,只要将新的行为封成类然后传入修改即可。
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) { /* ... */ }
}
能够避免误用方法导致异常,每个类职责明确。
但客户端需要向下类型转换才能调用方法。
扩展性差,新增节点类型后需要多处类型检查。
选择方式
如果叶节点和容器节点差异大,可以接受类型判断,则选用安全模式。
如果是内部团队约定清晰,可以用透明模式使代码简洁。

浙公网安备 33010602011771号