核心思想

高内聚,低耦合

模块

小到一个方法、一个接口、一个类。

大到一个业务、一个功能、一个系统。

  • 接口:指模块的输入和输出

  • 功能:指模块实现什么功能

  • 逻辑:指模块的内部如何实现要求的功能,以及所需要的数据

  • 状态:指模块调用与被调用的关系

高内聚

模块内部元素具有相同特点的相似程度高

高内聚提供了更好的程序可靠性和可读性

正面

Class:新闻管理

Method:新闻查看、新闻更新、新闻删除

则新闻管理类这个模块的内部元素(对新闻管理的方法)具有相同特点相似程度高(管理新闻),则称为新闻管理类为高内聚

反面

Class:新闻管理

Method:新闻查看、新闻更新、新闻删除、用户注册

则新闻管理类这个模块的内部元素(对新闻管理的方法)具有相同特点相似程度低的元素(用户注册),则称为新闻管理类为低内聚

低耦合

模块之间的依赖程度低

低耦合提供了更好的程序可扩展性和可复用性


UML类图

类与类

类与类之间的关系有

  • 泛化

  • 实现

  • 组合

  • 聚合

  • 关联

  • 依赖

泛化

Animal是Tiger的泛化

Tiger是Animal的特化

实现

类与接口的关系,表示类实现了接口

组合

是整体和部分的关系,组合是把部分作为整体类的对象,强拥有

个体或部分没有独立的生命周期,与整体生命周期保持一致

聚合

是整体和部分的关系,聚合是把个体对象的指针(引用)作为整体类的属性,弱拥有

个体或部分有独立的生命周期

关联

是一种拥有关系,它使一个类知道另一个类的属性和方法

依赖

是一种使用关系

我需要你才能活下去


类图表示法

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系 等。类图不显示暂时性的信息工类图是面向对象建模的主要组成部分。

  • 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解;

  • 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。

类的表示方式

在UML类图中,类使用包含类名属性(field)方法(method)且带有分割线的矩形来表示,比如下图表示一个Employee类, 它包含name,age和address这3个属性,以及work()方法。

属性/方法名称前加的加号减号表示了这个属性/方法的可见性, UMI类图中表示可见性的符号有三种:

  • +:表示public

  • -:表示private

  • #:表示protected

属性的完整表示方式是:可见性 名称 : 类型 [ = 缺省值] 方法的完整表示方式是:可见性 名称(参数列表) [ : 返回类型]

注:中括号中的内容表示可选的

示例

类与类之间关系的表示方式

关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我 关联又可以分为单向关联双向关联自关联

单向关联

在UML类图中单向关联用一个带箭头的实线表示。

双向关联

在UML类图中双向关联用一个不带箭头的直线表示。

自关联

在UML类图中自关联用一个带有箭头且指向自身的线表示。


聚合关系

聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。 聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立 存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。

在UML类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:


组合关系

组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。 在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。

在UML类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:


依赖关系

依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法 通过局部变量方法的参数或者对静态方法的调用访问另一个类(被依赖类)中的某些方法来完成一些职责。

在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:


继承关系

继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关 系。 在UML类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和Teacher类都是Person类的子类,其类图如下图所示:


实现关系

实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽 象操作。 在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交 通工具,其类图如下图所示:


设计原则

单一职责

Single Responsibility Principle

应该有且只有一个引起类变更的原因(一个类只干一件事)

优点

  • 提高代码可读性,提高系统可维护性

  • 降低类的复杂性,一个模块只负责一个职责,提高系统的可扩展性和可维护性

  • 降低变更引起的风险,变更是必然的,如果单一职责做得好,当修改一个功能的时候可以显著的降低对另一个功能的影响

反面

public class Telephone
{
    public void Dial(string phoneNumber)
    {
        Debug.Log("给" + phoneNumber + "打电话");
    }
​
    public void HangUp(string phoneNumber)
    {
        Debug.Log("挂断" + phoneNumber + "的电话");
    }
​
    public void SendMessage(string message)
    {
        Debug.Log("发送信息:" + message);
    }
​
    public void ReceiveMessage(string message)
    {
        Debug.Log("收到信息:" + message);
    }
}
  • 变化1:内部的变化,如果Telephone内部的方法,任意之一发生了改变,都会需要修改Telephone这个类,不符合单一职责原则

  • 变化2:外部的变化,如果Telephone要添加新的方法,也需要修改Telephone这个类

改进

我们希望有且只有一个(添加功能)能引起Telephone变化的原因

给每个方法都提炼为一个接口,抽象成一种能力

public interface IDial
{
    void DialNumber(string phoneNumber);
}
​
public interface IHangUp
{
    void HangUpNumber(string phoneNumber);
}
​
public interface ISendMessage
{
    void SendMessage(string message);
}
​
public interface IReceiveMessage
{
    void ReceiveMessage(string message);
}

然后分别写类,去实现接口

public class Dial : IDial
{
    public void DialNumber(string phoneNumber)
    {
        Debug.Log("给" + phoneNumber + "打电话");
    }
}
​
public class HangUp : IHangUp
{
    public void HangUpNumber(string phoneNumber)
    {
        Debug.Log("挂断" + phoneNumber + "的电话");
    }
}
​
public class Send : ISendMessage
{
    public void SendMessage(string message)
    {
        Debug.Log("发送信息:" + message);
    }
}
​
public class Receive : IReceiveMessage
{
    public void ReceiveMessage(string message)
    {
        Debug.Log("收到信息:" + message);
    }
}

在Telephone中只进行调用

public class Telephone
{
    private readonly IDial _dial;
    private readonly IHangUp _hangUp;
    private readonly ISendMessage _sendMessage;
    private readonly IReceiveMessage _receiveMessage;
​
    public Telephone(
        IDial dial, IHangUp hangUp, 
        ISendMessage sendMessage, IReceiveMessage receiveMessage)
    {
        _dial = dial;
        _hangUp = hangUp;
        _sendMessage = sendMessage;
        _receiveMessage = receiveMessage;
    }
​
    public void Dial(string phoneNumber)
    {
        _dial.DialNumber(phoneNumber);
    }
​
    public void HangUp(string phoneNumber)
    {
        _hangUp.HangUpNumber(phoneNumber);
    }
​
    public void SendMessage(string message)
    {
        _sendMessage.SendMessage(message);
    }
​
    public void ReceiveMessage(string message)
    {
        _receiveMessage.ReceiveMessage(message);
    }
}

开放封闭

Open Closed Principle

是面向对象所有原则的核心

  • 对扩展开放

  • 面向修改代码封闭

需求改变时,在不改变软件实体源代码(类、接口、方法等)的前提下,通过扩展功能,使其满足新的需求

面向抽象编程:使用抽象,封装变化

反面

对象1:用户,属性:记录不同类型的用户

public class BankClient
{
    public string BankType { get; set; }
}

对象2:银行柜员:帮助用户处理不同的请求

public class BankStuff
{
    private readonly BankProcess _bankProcess = new();
​
    public void HandleProcess(BankClient client)
    {
        // 调用银行业务系统,处理用户请求
        switch (client.BankType)
        {
            case "存款":
                _bankProcess.Deposit();
                break;
            case "取款":
                _bankProcess.DrawMoney();
                break;
            case "转账":
                _bankProcess.TransferMoney();
                break;
            default:
                Debug.Log("无法处理该业务类型");
                break;
        }
    }
}

对象3:银行业务系统:处理存钱,取钱,转账等需求的操作系统

  • 不符合单一职责原则

public class BankProcess
{
    public void Deposit()
    {
        Debug.Log("处理用户存款请求");
    }
​
    public void DrawMoney()
    {
        Debug.Log("处理用户取款请求");
    }
​
    public void TransferMoney()
    {
        Debug.Log("处理用户转账请求");
    }
}

改进

BankProcess

使用接口替换BankProcess

public interface IBankProcess
{
    void BankProcess();
}

不同的业务使用不同类实现接口

public class Deposit : IBankProcess
{
    public void BankProcess()
    {
        Debug.Log("存款");
    }
}
​
public class DrawMoney : IBankProcess
{
    public void BankProcess()
    {
        Debug.Log("取款");
    }
}
​
public class TransferMoney : IBankProcess
{
    public void BankProcess()
    {
        Debug.Log("转账");
    }
}
BankClient

使用接口替换BankClient

public interface IBankClient
{
    IBankProcess GetBankProcess();
}

根据用户要办理的不同业务类型来返回具体处理该业务的银行业务对象

public class DepositClient : IBankClient
{
    public IBankProcess GetBankProcess()
    {
        return new Deposit();
    }
}
​
public class DrawMoneyClient : IBankClient
{
    public IBankProcess GetBankProcess()
    {
        return new DrawMoney();
    }
}
​
public class TransferClient : IBankClient
{
    public IBankProcess GetBankProcess()
    {
        return new TransferMoney();
    }
}
BankStuff

BankStuff就不需要使用switch来区分不同的业务逻辑

public class BankStuff
{
    public void HandleProcess(IBankClient client)
    {
        // 调用银行业务系统,处理用户请求
        client.GetBankProcess()?.BankProcess();
    }
}
运行类

只需要替换IBankClient bankClient变量的值就可以做到处理不同的业务类型

public class MainClass : MonoBehaviour
{
    private void Start()
    {
        BankStuff bankStuff = new BankStuff();
​
        // 存款
        IBankClient bankClient = new DepositClient();
        bankStuff.HandleProcess(bankClient);
​
        // 取款
        bankClient = new DrawMoneyClient();
        bankStuff.HandleProcess(bankClient);
​
        // 转账
        bankClient = new TransferClient();
        bankStuff.HandleProcess(bankClient);
    }
}

依赖倒置

Dependence Inversion Principle

是实现开放封闭原则的基础

  • 高层模块(调用者)不应该依赖于低层模块(被调用者)

  • 低层模块(被调用者)不应该依赖于高层模块(调用者)

  • 两个都应该依赖于抽象

  • 抽象(抽象类、接口)不应该依赖细节,细节(实现)应该依赖于抽象

其本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,实现模块间的松耦合

反面

不同类型的歌曲对象

public class ChineseSong
{
    public string GetSongLyrics()
    {
        return "中国歌词";
    }
}
​
public class EnglishSong
{
    public string GetSongLyrics()
    {
        return "Hands up! Embrace who you wanna be";
    }
}

Singer做为高层模块,目前是严格依赖于低层模块的,不符合依赖倒置原则=>开闭原则=>单一职责原则

public class Singer
{
    public void SingAChineseSong(ChineseSong song)
    {
        Debug.Log("歌手正在唱中文歌:" + song.GetSongLyrics());
    }
​
    public void SingAnEnglishSong(EnglishSong song)
    {
        Debug.Log("歌手正在唱英文歌:" + song.GetSongLyrics());
    }
}

改进

将可变的都抽象为接口

歌曲是可变的,抽象为接口

public interface ISongLyrics
{
    string GetLyrics();
}

不同类型的歌曲实现统一的接口

public class ChineseSong : ISongLyrics
{
    public string GetLyrics()
    {
        return "中国歌词";
    }
}
​
public class EnglishSong : ISongLyrics
{
    public string GetLyrics()
    {
        return "Hands up! Embrace who you wanna be";
    }
}

Singer只需要调用接口方法获取数据就可以

public class Singer
{
    public void SingASong(ISongLyrics song)
    {
        Debug.Log("歌手正在唱歌:" + song.GetLyrics());
    }
}

运行类

public class MainClass : MonoBehaviour
{
    private void Start()
    {
        Singer singer = new Singer();
        singer.SingASong(new ChineseSong());
        singer.SingASong(new EnglishSong());
    }
}

依赖注入

客户类不能在内部直接实例化服务类

在客户类中(如上述的Singer类)中(调用者/高层模块中),定义一个注入点(如上述Singer类SingASong方法的传入参数),用来注入我们的服务类(低层模块)

依赖关系传递(注入)方法

  • 通过接口传递(接口注入)

    public interface ISongLyrics
    {
        string GetLyrics();
    }
    ​
    public class Singer
    {
        // 接口注入ISongLyrics
        public void SingASong(ISongLyrics song)
        {
            Debug.Log("歌手正在唱歌:" + song.GetLyrics());
        }
    }
  • 通过构造方法传递

    public interface ISongLyrics
    {
        string GetLyrics();
    }
    ​
    public class Singer
    {
        ISongLyrics _song;
        
        // 构造方法注入ISongLyrics
        public Singer(ISongLyrics song)
        {
            _song = song;
        }
        
        public void SingASong()
        {
            Debug.Log("歌手正在唱歌:" + _song.GetLyrics());
        }
    }
  • 通过属性的set方法传递

    public interface ISongLyrics
    {
        string GetLyrics();
    }
    ​
    public class Singer
    {
        ISongLyrics _song;
        
        // set方法注入ISongLyrics
        public void SetSong(ISongLyrics song)
        {
            _song = song;
        }
        
        public void SingASong()
        {
            Debug.Log("歌手正在唱歌:" + _song.GetLyrics());
        }
    }

里氏替换

Liskov Substitution Principle

是实现依赖倒置原则的基础

  • 如果S是T的子类型,则T类型的对象可以替换为S类型的对象

  • 所有引用父类对象的地方,都可以使用其子类型代替

  • 子类可以替换父类


接口隔离

Interface Segregation Principle

  • 客户端不应该依赖它不需要的接口

  • 一个类对另一个类的依赖应该建立在最小接口上

  • 接口尽量细分,不要在一个接口中放很多方法

  • 拆分接口时,首先必须满足单一职责原则

单一职责原则的关系

单一职责:影响类变化的原因只有一个,高内聚

接口隔离:低耦合

总结

设计接口的指导原则


迪米特

Demeter Principle

  • 要求一个对象应该对其它对象有最少的了解(最少至少原则 The Least Knowledge Principle)

  • 降低类之间的耦合

  • 实际上就是一个类在创建方法和属性时要遵守的法则

  • 只和直接朋友通信

反面

电脑对象,包含电脑相关操作

public class Computer
{
    public void SaveCurrentTask()
    {
        Debug.Log("保存当前任务");
    }
​
    public void CloseScreen()
    {
        Debug.Log("关闭屏幕");
    }
​
    public void Shutdown()
    {
        Debug.Log("关闭电脑");
    }
}

人对象,包含关闭电脑操作

如果关闭电脑需要二十步,Computer提供30个方法,并且每个方法之间都有相应的绝对顺序,则需要充分了解Computer类才能正确的调用,显然这是不符合迪米原则的

public class Person
{
    public void CloseComputer(Computer computer)
    {
        // 不符合迪米特原则
        computer.SaveCurrentTask();
        computer.CloseScreen();
        computer.Shutdown();
    }
}

改进

Computer新增一个方法用来专门进行关闭电脑操作

public class Computer
{
    private void SaveCurrentTask()
    {
        Debug.Log("保存当前任务");
    }
​
    private void CloseScreen()
    {
        Debug.Log("关闭屏幕");
    }
​
    private void Shutdown()
    {
        Debug.Log("关闭电脑");
    }
​
    public void CloseComputer()
    {
        SaveCurrentTask();
        CloseScreen();
        Shutdown();
    }
}

这样人关闭电脑时就不需要知道类的全部方法,而只需要调用Computer类封装好的特定功能的集合方法就行

public class Person
{
    public void CloseComputer(Computer computer)
    {
        computer.CloseComputer();
    }
}

直接朋友

只和直接朋友通信

  • 成员对象

  • 方法参数

  • 方法返回值

出现在局部变量中的类,不是直接朋友

总结

设计类(包括类方法存放位置)的指导原则

当有方法有多处地方可以放置时,即多个类可以书写该方法,则通过迪米特原则存放方法


合成复用

Composite Reuse Principle

  • 又称组合/聚合复用原则

  • 尽量使用对象组合,而不是继承来达到复用

继承问题

有一父类

public class Father
{
    public void Method1()
    {
        Debug.Log("Father Method1");
    }
​
    public void Method2()
    {
        Debug.Log("Father Method2");
    }
​
    public void Method3()
    {
        Debug.Log("Father Method3");
    }
}

有3个子类

public class Son1 : Father
{
}
​
public class Son2 : Father
{
}
​
public class Son3 : Father
{
}

现在son123都继承了Father,当son1不需要Father的Method3,而其它2个子类son23需要Method3时,就会出现问题

我们需要给son1实现或重写Method3,但是son1并不需要Method3,这就导致系统耦合性变高

所以有如下问题

  • 破坏了系统的封装性,基类发生了改变,子类的实现也会发生改变

  • 会强行增加子类本不需要的方法,系统耦合性变高

  • 继承是静态的,不能在程序运行时发生改变

反面

改进

使用接口抽象颜色

public interface IColor
{
    string ShowColor();
}

不同颜色实现统一接口

public class Red : IColor
{
    public string ShowColor()
    {
        return "红色";
    }
}
​
public class Green : IColor
{
    public string ShowColor()
    {
        return "绿色";
    }
}
​
public class Blue : IColor
{
    public string ShowColor()
    {
        return "蓝色";
    }
}

汽车基类提供移动抽象方法,接口依赖注入颜色接口,即可组合颜色

public abstract class Car
{
    // 接口依赖
    public abstract void Move(IColor color);
}

不同类型汽车继承汽车基类

public class GasolineCar : Car
{
    public override void Move(IColor color)
    {
        Debug.Log(color.ShowColor() + "的汽油汽车正在奔跑");
    }
}
​
public class ElectricCar : Car
{
    public override void Move(IColor color)
    {
        Debug.Log(color.ShowColor() + "的电动汽车正在奔跑");
    }
}

运行类

public class MainClass : MonoBehaviour
{
    private void Start()
    {
        Car.Car car = new GasolineCar(); // 通过提供不同类型的汽车子类来组合不同种类的汽车
        car.Move(new Green()); // 通过提供不同颜色接口实现类来组合汽车的不同颜色
    }
}

总结

继承组合运用时机

  • 继承:我是你,则我要继承你

    • 学生是人,则继承人

  • 组合:我拥有你,则我要组合你

    • 学生有分数,则组合分数


设计模式

  • 在某些场景下,针对某些问题的某种通用的解决方案

  • 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结

  • 让代码更容易被人理解,保证代码可靠性、稳定性、易于扩展

分类

  • 创建型

    • 作用于对象的创建,将对象的创建与使用分离

  • 结构型

    • 将类或对象按照某种布局组成更大的结构

  • 行为型

    • 作用于类或对象之间互相协作完成单个对象无法单独完成的任务,以及怎样的分配职责

详细的分类


创建型

单例

非必要不使用单例设计模式

在程序运行中,保证某个类有且仅有一个实例

使用场景

在程序运行中,某个类有且仅有一个实例

饿汉式

程序开始运行时就自动创建对象实例

/// <summary>
/// 单例模式-饿汉式
/// </summary>
public class EarlySingleton
{
    // 1. 构造函数私有化
    private EarlySingleton() {}
​
    // 2. 创建唯一实例
    private static readonly EarlySingleton Instance = new();
​
    // 3. 提供静态公共访问接口获取实例
    public static EarlySingleton GetInstance()
    {
        return Instance;
    }
}
  • 不推荐使用,会造成资源浪费

懒汉式

在第一次需要对象实例时,才会进行创建

/// <summary>
/// 单例模式-懒汉式
/// </summary>
public class LazySingleton
{
    // 1. 构造函数私有化
    private LazySingleton() {}
​
    // 2. 声明静态字段,存储唯一对象实例
    private static LazySingleton _instance;
​
    // 3. 声明静态方法,获取唯一对象实例
    public static LazySingleton GetInstance()
    {
        // 4. 实现懒加载,只有第一次调用GetInstance方法时,才会创建实例
        return _instance ??= new LazySingleton();
    }
}
  • 多线程会有线程安全问题

线程安全懒汉式
/// <summary>
/// 单例模式-线程安全的懒汉式
/// </summary>
public class SafeLazySingleton
{
    // 1. 构造函数私有化
    private SafeLazySingleton() {}
​
    // 2. 使用 Lazy<T> 类型实现懒汉式单例模式,确保线程安全
    private static readonly Lazy<SafeLazySingleton> Instance = new(() => new SafeLazySingleton());
​
    // 3. 声明静态方法,获取唯一对象实例
    public static SafeLazySingleton GetInstance()
    {
        return Instance.Value;
    }
}
  • 反射会破坏单例模式

线程安全防反射懒汉式
/// <summary>
/// 单例模式-线程安全的懒汉式
/// </summary>
public class SafeLazySingleton
{
    // 标志位记录是否通过特定方法创建了实例
    private static bool _createdByLazy;
​
    // 1. 构造函数私有化
    private SafeLazySingleton()
    {
        // 防止反射破坏单例模式
        lock (typeof(SafeLazySingleton))
        {
            if (Instance.IsValueCreated || !_createdByLazy)
                throw new InvalidOperationException("Singleton instance already created.");
        }
    }
​
    // 2. 使用 Lazy<T> 类型实现懒汉式单例模式,确保线程安全
    private static readonly Lazy<SafeLazySingleton> Instance = new(() =>
    {
        _createdByLazy = true;
        return new SafeLazySingleton();
    });
​
    // 3. 声明静态方法,获取唯一对象实例
    public static SafeLazySingleton GetInstance()
    {
        return Instance.Value;
    }
}
内部静态
/// <summary>
/// 单例模式-静态内部类
/// </summary>
public class InnerSingleton
{
    public static InnerSingleton GetInstance()
    {
        return Inner.INSTANCE;
    }
​
    private static class Inner
    {
        public static readonly InnerSingleton INSTANCE = new();
    }
}

简单工厂

又称静态工厂方法(Static Factory Method),它不属于23种设计模式之一

是由工厂决定创建出哪一种产品类的实例,是工厂模式系列中最简单的模式

实际就是新增简单工厂类,将对象创建过程封装到该类的静态方法中

使用场景

通过工厂类决定创建出某一种产品类的实例

缺点
  • 系统扩展困难,一旦加入新功能,就必须要修改工厂逻辑

  • 简单工厂集合了所有创建对象的逻辑,一旦不能正常工作,就会导致整个系统出问题

示例

以实现加减乘除计算器为例子

抽象计算为接口

/// <summary>
/// 计算类接口
/// </summary>
public interface ICalculator
{
    /// <summary>
    /// 获取最终计算结果
    /// </summary>
    double GetResult(double num1, double num2);
}

实现不同操作类

public class Add : ICalculator
{
    public double GetResult(double num1, double num2)
    {
        return num1 + num2;
    }
}
​
public class Sub : ICalculator
{
    public double GetResult(double num1, double num2)
    {
        return num1 - num2;
    }
}
​
public class Mul : ICalculator
{
    public double GetResult(double num1, double num2)
    {
        return num1 * num2;
    }
}
​
public class Div : ICalculator
{
    public double GetResult(double num1, double num2)
    {
        return num1 / num2;
    }
}

新增简单工厂,将对象创建过程封装到静态方法中

public class CalculateFactory
{
    public static ICalculator CreateCalculator(string type)
    {
        return type switch
        {
                "+" => new Add(),
                "-" => new Sub(),
                "*" => new Mul(),
                "/" => new Div(),
                _ => null
        };
    }
}

运行类

public class MainClass : MonoBehaviour
{
    private void Start()
    {
        double num1 = 10.5;
        double num2 = 5.2;
        string op = "-";
​
        ICalculator calc = CalculateFactory.CreateCalculator(op);
        double result = calc.GetResult(num1, num2);
        Debug.Log($"The result of calculating {num1} {op} {num2} is {result}");
    }
}

工厂方法

定义一个用于创建对象的接口,让子类决定实例化哪一个类

工厂方法使一个类实例化,延迟到子类

使用场景

低耦合的通过工厂类决定创建出某一种产品类的实例

示例

还是简单工厂的示例

抽象简单工厂的创建工厂方法

public interface ICalculateFactory
{
    ICalculator CreateCalculate();
}

不同工厂创建不同对象

public class AddFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Add();
    }
}
    
public class SubFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Sub();
    }
}
​
public class MulFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Mul();
    }
}
​
public class DivFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Div();
    }
}

运行类

private void Start()
{
    double num1 = 10.5;
    double num2 = 5.2;
    string op = "-";
​
    // 创建不同的工厂类
    ICalculateFactory factory = null;
    switch (op)
    {
        case "+":
            factory = new AddFactory();
            break;
        case "-":
            factory = new SubFactory();
            break;
        case "*":
            factory = new MulFactory();
            break;
        case "/":
            factory = new DivFactory();
            break;
    }
​
    // 不同工厂类创建不同对象进行不同的计算
    double result = factory.CreateCalculate().GetResult(num1, num2);
    Debug.Log($"The result of calculating {num1} {op} {num2} is {result}");
}
反射

使用反射去除客户端创建工厂对象时的switch分支

switch就是表达运算符与对应工厂类的映射关系,使用Attribute特性利用反射解决映射问题

使用特性表示该工厂对象对应的运算符

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class OperatorAttribute : Attribute
{
    public string mSymbol { get; }
​
    public OperatorAttribute(string symbol)
    {
        mSymbol = symbol;
    }
}

为了程序稳定性,操作类添加空操作类型

public class Empty : ICalculator
{
    public double GetResult(double num1, double num2)
    {
        return 0;
    }
}

给工厂类应用特性

[Operator("")]
public class EmptyFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Empty();
    }
}
​
[Operator("+")]
public class AddFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Add();
    }
}
​
[Operator("-")]
public class SubFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Sub();
    }
}
​
[Operator("*")]
public class MulFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Mul();
    }
}
​
[Operator("/")]
public class DivFactory : ICalculateFactory
{
    public ICalculator CreateCalculate()
    {
        return new Div();
    }
}

新建反射工厂,建立特性与工厂类的映射关系(生成映射表格)

public class ReflectionFactory
{
    private readonly Dictionary<string, ICalculateFactory> _mOperator2FactoryMap = new();
​
    public ReflectionFactory()
    {
        // 1. 得到当前运行的程序集
        Assembly assembly = Assembly.GetExecutingAssembly();
        
        // 2. 遍历程序集中的所有类型
        foreach (var type in assembly.GetTypes())
        {
            // 3. 判断类型是否实现了ICalculateFactory接口(并排除接口)
            if (!typeof(ICalculateFactory).IsAssignableFrom(type) && !type.IsInterface) continue;
            
            // 4. 获得类的特性
            var attribute = type.GetCustomAttribute<OperatorAttribute>();
            if (attribute == null) continue;
            
            // 5. 注册到字典中
            _mOperator2FactoryMap.Add(attribute.mSymbol, (ICalculateFactory) Activator.CreateInstance(type));
        }
    }
​
    public ICalculateFactory GetFactory(string symbol)
    {
        _mOperator2FactoryMap.TryGetValue(symbol, out var factory);
        return factory ?? new EmptyFactory();
    }
}

通过该工厂获得工厂对象,工厂对象获得操作对象进行计算

private void Start()
{
    double num1 = 10.5;
    double num2 = 5.2;
    string op = "*";
​
    // 反射工厂获得计算工厂对象
    var reflectionFactory = new ReflectionFactory();
    ICalculateFactory factory = reflectionFactory.GetFactory(op);
​
    // 计算工厂获得操作对象进行具体计算
    double result = factory.CreateCalculate().GetResult(num1, num2);
    Debug.Log($"The result of calculating {num1} {op} {num2} is {result}");
}

抽象工厂

为了缩减创建子类工厂的数量,不必给每一个产品分配一个工厂类

可将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建

避免工厂子类无限衍生

使用场景

通过经过分组抽象的工厂类决定创建出某一种产品类的实例

示例

生成不同品牌的键盘、鼠标和主机

通过对产品进行分组,可以分出每个品牌一个工厂(品牌分组),每个品牌的工厂只生成自己品牌的对象

抽象键盘和鼠标接口

public interface IKeyboard
{
    string ShowKeyboardBrand();
}
​
public interface IMouse
{
    string ShowMouseBrand();
}

不同类型键盘和鼠标

public class DellKeyboard : IKeyboard
{
    public string ShowKeyboardBrand()
    {
        return "我是戴尔键盘";
    }
}
​
public class HpKeyboard : IKeyboard
{
    public string ShowKeyboardBrand()
    {
        return "我是惠普键盘";
    }
}
​
public class DellMouse : IMouse
{
    public string ShowMouseBrand()
    {
        return "我是戴尔鼠标";
    }
}
​
public class HpMouse : IMouse
{
    public string ShowMouseBrand()
    {
        return "我是惠普鼠标";
    }
}

按照产品分组抽象工厂接口

public interface IDeviceFactory
{
    IKeyboard CreateKeyboard();
    IMouse CreateMouse();
}

不同品牌的工厂类

public class DellFactory : IDeviceFactory
{
    public IKeyboard CreateKeyboard()
    {
        return new DellKeyboard();
    }
​
    public IMouse CreateMouse()
    {
        return new DellMouse();
    }
}
​
public class HpFactory : IDeviceFactory
{
    public IKeyboard CreateKeyboard()
    {
        return new HpKeyboard();
    }
​
    public IMouse CreateMouse()
    {
        return new HpMouse();
    }
}

运行类

private void Start()
{
    // 可以像工厂方法一样用反射解决switch选择具体品牌工厂的问题
    IDeviceFactory deviceFactory = new HpFactory();
    IKeyboard keyboard = deviceFactory.CreateKeyboard();
    IMouse mouse = deviceFactory.CreateMouse();
    Debug.Log($"Created [{keyboard.ShowKeyboardBrand()}] and [{mouse.ShowMouseBrand()}]");
}

原型

用于方便创建重复的对象,同时又能保证性能

  • 浅拷贝

    • 值类型:搞个新的

    • 引用类型:搞一个引用

  • 深拷贝

    • 值类型:搞个新的

    • 引用类型:搞个新的

普通值类型可直接使用ICloneable接口实现Clone方法并调用来达到浅拷贝的目的

对于引用类型的深拷贝,可以参考表达式树来进行拷贝

使用场景

高效的复制对象


建造者

对一个“复杂对象”的创建进行组合式的方法,通常各个部分的子对象用一定的算法构成

使用场景

对复杂对象的创建进行组合化,使得复杂对象的构建过程更灵活,可通过不同的步骤创建不同的对象

复杂对象
  • 子部件较多,没有恰当赋值之前,对象不能当做一个完整的对象或产品使用

    • 例如,邮件:发件人、收件人、抄送人、主题、内容等

  • 子部件需要按照一定顺序赋值才有意义,在某个子部件没有赋值之前,另一个子部件就无法赋值

    • 例如,装修:改水电 => 瓷砖 => 家电 => 电视墙

示例

以电脑为复杂对象,进行电脑组装

  • AbstractBuilder(抽象建造者)

    • 为创建一个产品对象的各个部件指定抽象接口,在该接口中一般提供2种方法

    • 各个组件的创建方法

    • 对象返回方法(最终构建),用于将构建完成的对象返回

  • ConcreteBuilder(具体建造者)

    具体建造者实现或继承抽象建造者,实现各个组件的创建方法和对象方法的方法

  • Product

    被构建的复杂对象,包含多个组件

  • Director(指挥者)

    指挥者负责安排复杂对象的建造顺序

电脑类

public class Computer
{
    // 电脑零部件集合
    private readonly List<string> _mPartList = new();
​
    public void AddPart(string part)
    {
        _mPartList.Add(part);
    }
​
    public void ShowComputer()
    {
        // 打印电脑零部件列表
        foreach (var part in _mPartList)
        {
            Debug.Log($"正在组装电脑零部件:{part}");
        }
    }
}

电脑构建者接口

public interface IComputerBuilder
{
    void BuildCpu();
    void BuildDisk();
    void BuildMemory();
    void BuildScreen();
    void BuildOperatingSystem();
    Computer Build();
}

构建不同品质电脑的构建者

public class GoodComputerBuilder : IComputerBuilder
{
    private readonly Computer _mComputer = new();
​
    public void BuildCpu()
    {
        _mComputer.AddPart("i38的CPU");
    }
​
    public void BuildDisk()
    {
        _mComputer.AddPart("500Z的硬盘");
    }
​
    public void BuildMemory()
    {
        _mComputer.AddPart("16T的内存");
    }
​
    public void BuildScreen()
    {
        _mComputer.AddPart("128寸的显示器");
    }
​
    public void BuildOperatingSystem()
    {
        _mComputer.AddPart("Windows101的操作系统");
    }
​
    public Computer Build()
    {
        return _mComputer;
    }
}
​
public class BadComputerBuilder : IComputerBuilder
{
    private readonly Computer _mComputer = new();
​
    public void BuildCpu()
    {
        _mComputer.AddPart("i5的CPU");
    }
​
    public void BuildDisk()
    {
        _mComputer.AddPart("114B的硬盘");
    }
​
    public void BuildMemory()
    {
        _mComputer.AddPart("16K的内存");
    }
​
    public void BuildScreen()
    {
        _mComputer.AddPart("3寸的显示器");
    }
​
    public void BuildOperatingSystem()
    {
        _mComputer.AddPart("Unix1.0的操作系统");
    }
​
    public Computer Build()
    {
        return _mComputer;
    }
}

指挥者确保构建顺序

public class BuildDirector
{
    public Computer BuildComputer(IComputerBuilder builder)
    {
        builder.BuildCpu();
        builder.BuildDisk();
        builder.BuildMemory();
        builder.BuildScreen();
        builder.BuildOperatingSystem();
        return builder.Build();
    }
}

运行类

private void Start()
{
    IComputerBuilder good = new GoodComputerBuilder();
    IComputerBuilder bad = new BadComputerBuilder();
    BuildDirector director = new BuildDirector();
​
    director.BuildComputer(good);
    director.BuildComputer(bad);
​
    Computer goodComputer = good.Build();
    Computer badComputer = bad.Build();
​
    goodComputer.ShowComputer();
    badComputer.ShowComputer();
}

结构型

适配器

将现有对象放入新环境中进行使用,但是新环境要求的接口与现有对象不匹配,则此时需要适配器对现有对象的接口进行适配以达到匹配新环境的目的

类比电子设备,如,Lighting接口转Type-C接口头 就是一个适配器

  • Adaptor:初始角色,实现了我们想要的功能,但是接口不匹配(适配器源,Lighting接口)

  • Target:目标角色,定义了用户希望的接口(适配器要转换的目的地,Type-C接口)

  • Adapter:适配器角色,实现了目标接口(Lighting接口转Type-C接口的转接头)

    • 通过内部包含Adaptor对象,调用Adaptor对象原有方法实现功能

使用场景

接口与接口不兼容时使用

示例

初始角色,一根IPhone充电线

public class IphoneChargeAdaptor
{
    public void IphoneCharge()
    {
        Debug.Log("IPhone充电线充电");
    }
}

目标角色,一个安卓Type-C接口的充电口

public interface IAndroidCharge
{
    void PhoneCharge();
}

适配器,将IPhone充电线的lighting接口转换为安卓的Type-C接口

public class PhoneChargeAdapter : IAndroidCharge
{
    private readonly IphoneChargeAdaptor _mChargeAdaptor;
​
    public PhoneChargeAdapter(IphoneChargeAdaptor chargeAdaptor)
    {
        _mChargeAdaptor = chargeAdaptor;
    }
​
    public void PhoneCharge()
    {
        _mChargeAdaptor.IphoneCharge();
    }
}

插入充电口进行充电

private void Start()
{
    // 有一根IPhone充电线
    var iphoneChargeAdaptor = new IphoneChargeAdaptor();
​
    // IPhone充电线适配到安卓的Type-C接口
    IAndroidCharge phoneCharge = new PhoneChargeAdapter(iphoneChargeAdaptor);
​
    // 利用适配器转接为Type-C接口进行充电
    phoneCharge.PhoneCharge();
}

装饰器

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

  • Component:顶级抽象父类或接口,后续是给这类子类对象添加额外职责

  • ConcreteComponent:具体的类,后续是给这类对象添加额外职责

  • Decorator:装饰器,包含后续添加职责类的抽象父类(Component)

  • ConcreteDecorator:具体装饰器,要添加的额外职责

使用场景

通过在对象外部包装新的功能,增强对象的功能而不修改其定义

示例

以饮料添加各种配料为例

抽象饮料

public abstract class Drink
{
    public abstract double Cost();
}

不同饮料

public class MilkTea : Drink
{
    public override double Cost()
    {
        Debug.Log("奶茶¥10一杯");
        return 10;
    }
}
​
public class FruitTea : Drink
{
    public override double Cost()
    {
        Debug.Log("水果茶¥15一杯");
        return 15;
    }
}
​
public class Soda : Drink
{
    public override double Cost()
    {
        Debug.Log("苏打¥5一瓶");
        return 5;
    }
}

抽象装饰器(配料)

public abstract class DosageDecorator : Drink
{
    private Drink _mDrink;
​
    /// <summary>
    /// 添加配料到目标中
    /// </summary>
    /// <param name="drink">要添加到哪个目标中</param>
    public void SetComponent(Drink drink)
    {
        _mDrink = drink;
    }
​
    public override double Cost()
    {
        return _mDrink.Cost();
    }
}

不同配料

public class Coral : DosageDecorator
{
    private const double Money = 3;
​
    public override double Cost()
    {
        Debug.Log("珍珠¥3");
        return base.Cost() + Money;
    }
}
​
public class MedicinalHerb : DosageDecorator
{
    private const double Money = 6.8;
​
    public override double Cost()
    {
        Debug.Log("仙草¥6.8");
        return base.Cost() + Money;
    }
}
​
public class Pudding : DosageDecorator
{
    private const double Money = 1.5;
​
    public override double Cost()
    {
        Debug.Log("布丁¥1.5");
        return base.Cost() + Money;
    }
}

环状调用添加配料并计算总价格

private void Start()
{
    // 一杯奶茶
    MilkTea milkTea = new MilkTea();
    // 2份布丁
    Pudding pudding1 = new Pudding();
    Pudding pudding2 = new Pudding();
    // 1份珍珠
    Coral coral = new Coral();
    // 1份仙草
    MedicinalHerb medicinalHerb = new MedicinalHerb();
​
    // 2份布丁添加到奶茶中
    pudding1.SetComponent(milkTea);
    pudding2.SetComponent(pudding1);
    // 1份珍珠添加到奶茶中
    coral.SetComponent(pudding2);
    // 1份仙草添加到奶茶中
    medicinalHerb.SetComponent(coral);
​
    // 计算奶茶的价格
    double price = medicinalHerb.Cost();
    Debug.Log("奶茶的价格为:" + price);
}

调用最后添加配料的价格计算函数就可以递归的计算出总价格了

观察计算顺序也可以知道最后添加的配料价格最先被计算


代理

为其他对象提供一种代理,以控制这个对象的访问

客户端只能通过代理对目标进行访问,此时,我们可以在代理中对该访问进行拦截、控制等操作

  • Subject:定义了真实实体抽象出的动作方法,便于后续客户端通过代理调用相应方法

  • RealSubject:真实实体,实现了供客户端通过接口调用的动作方法

  • Proxy:代理,包含了真实实体,客户端通过代理调用真实实体的方法

使用场景

控制对象的访问,增加额外的功能,如延迟加载、访问控制等

示例

抽象真实实体可供客户端通过代理调用的方法

public interface ISubject
{
    void GiveMoney();
    void GiveJk();
    void GiveFood();
}

创建真实实体与其数据

public class ClassFlower
{
    public string mName { get; set; }
}
public class RealSubject : ISubject
{
    private readonly ClassFlower _mClassFlower;
​
    public RealSubject(ClassFlower classFlower)
    {
        _mClassFlower = classFlower;
    }
    
    public void GiveMoney()
    {
        Debug.Log($"给{_mClassFlower.mName}送钱");
    }
​
    public void GiveJk()
    {
        Debug.Log($"给{_mClassFlower.mName}送JK");
    }
​
    public void GiveFood()
    {
        Debug.Log($"给{_mClassFlower.mName}送食物");
    }
}

创建代理类

public class Proxy : ISubject
{
    private readonly RealSubject _mRealSubject;
​
    public Proxy(RealSubject realSubject)
    {
        _mRealSubject = realSubject;
    }
    
    public void GiveMoney()
    {
        _mRealSubject.GiveMoney();
    }
​
    public void GiveJk()
    {
        _mRealSubject.GiveJk();
    }
​
    public void GiveFood()
    {
        _mRealSubject.GiveFood();
    }
}

通过代理调用方法

private void Start()
{
    // 创建真实实体
    RealSubject realSubject = new RealSubject(new ClassFlower { mName = "青衣" });
​
    // 创建代理对象
    ISubject proxy = new Subject.Proxy(realSubject);
    
    // 通过代理调用方法
    proxy.GiveMoney();
    proxy.GiveFood();
    proxy.GiveJk();
}

外观

隐藏了系统的复杂性,并向客户端提供一个可以访问系统的统一接口,这个接口组合了子系统的多个接口,使子系统更容易被访问和使用

使用场景

为一个复杂系统提供一个统一接口供外部调用

缺点

不符合开闭原则,如果客户端需要使用更多功能时,不仅仅需要修改子系统,也必须修改外观层

示例

有多处办事地点

public class PoliceOffice
{
    public void GetHouseholdRegistration()
    {
        Debug.Log("警察局开具户籍证明");
    }
}
​
public class StreetOffice
{
    public void GetPopulationCertificate()
    {
        Debug.Log("街道办事处开具户口");
    }
}
​
public class HospitalOffice
{
    public void GetBornCertificate()
    {
        Debug.Log("医院开具出生证明");
    }
}

市政大厅集合所有办事地点为一起(外观),提供统一方法

public class OfficeFacade
{
    private readonly PoliceOffice _mPoliceOffice = new();
    private readonly StreetOffice _mStreetOffice = new();
    private readonly HospitalOffice _mHospitalOffice = new();
​
    public void IssueCertificate()
    {
        _mPoliceOffice.GetHouseholdRegistration();
        _mStreetOffice.GetPopulationCertificate();
        _mHospitalOffice.GetBornCertificate();
    }
}

调用外观提供的统一方法,开具证明

private void Start()
{
    OfficeFacade officeFacade = new OfficeFacade();
    officeFacade.IssueCertificate();
}

桥接

将抽象部分(Abstraction)和抽象或接口(Implementer)分离,使其可以独立变化

合成复用原则的具体实现

  • Abstraction:一般为抽象类

  • Implementer:某一变化维度的抽象

使用场景

使得抽象部分和实现部分可以独立变化,增加系统的灵活性和可扩展性

缺点
  • 由于在组合/聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程

  • 要求正确识别出系统中两个独立变化的维度,引起对开发者的编程思想有较高的要求

示例

以不同驱动方式的汽车和颜色为例,可以分出两个变化的维度

这里将颜色这个维度抽象成接口

public interface IColor
{
    string ShowColor();
}

不同颜色

public class Red : IColor
{
    public string ShowColor()
    {
        return "红色";
    }
}
​
public class Green : IColor
{
    public string ShowColor()
    {
        return "绿色";
    }
}
​
public class Blue : IColor
{
    public string ShowColor()
    {
        return "蓝色";
    }
}

将汽车抽象为抽象类

public abstract class Car
{
    protected readonly IColor _mColor;
​
    protected Car(IColor color)
    {
        _mColor = color;
    }
​
    public abstract void Move();
}

不同驱动类型的汽车

public class GasolineCar : Car
{
    public GasolineCar(IColor color) : base(color)
    {
    }
​
    public override void Move()
    {
        Debug.Log($"{_mColor.ShowColor()}的汽油汽车正在行驶");
    }
}
​
public class ElectricCar : Car
{
    public ElectricCar(IColor color) : base(color)
    {
    }
​
    public override void Move()
    {
        Debug.Log($"{_mColor.ShowColor()}的电动汽车正在行驶");
    }
}

运行类

private void Start()
{
    IColor color = new Red();
    Car car = new GasolineCar(color);
    car.Move();
}

组合

可以使用组合设计模式将对象组合成树状结构,并且能像使用独立对象一样使用它们

  • Component:根节点,可以是接口、抽象类(常用)、普通类

    • 该类定义了子类中所有共性内容

    • 并且还定义了用于访问和管理子类的方法

  • Leaf:叶子节点,也就是末端节点,该节点下不会再有子节点

  • Composite:树枝(非叶子节点)

    • 它的作用是存储子部件(叶子节点)

    • 并且还实现了对子部件的相关操作

使用场景

树状结构,使得客户端可以以一致的方式处理单个对象和对象组合

示例

以公司(根节点)管理部门(树枝)和员工(叶子)为例

创建公司根节点

public abstract class CompanyComponent
{
    public string mName { get; set; }
​
    protected CompanyComponent(string name)
    {
        mName = name;
    }
    
    public abstract void Add(CompanyComponent companyComponent);
    public abstract void Remove(CompanyComponent companyComponent);
    public abstract void Display(int depth);
}

创建部门树枝

public class DepartmentComposite : CompanyComponent
{
    /// <summary>
    /// 存储部门和员工集合
    /// </summary>
    private readonly List<CompanyComponent> _mComponentList = new();
    
    public DepartmentComposite(string name) : base(name)
    { }
​
    public override void Add(CompanyComponent companyComponent)
    {
        _mComponentList.Add(companyComponent);
    }
​
    public override void Remove(CompanyComponent companyComponent)
    {
        _mComponentList.Remove(companyComponent);
    }
​
    public override void Display(int depth)
    {
        var name = new string('-', depth) + mName;
        Debug.Log(name);
        // +3 是为了输出子集时多出3个“-”符号
        _mComponentList.ForEach(component => component.Display(depth + 3));
    }
}

创建员工叶子

public class EmployeeLeaf : CompanyComponent
{
    public EmployeeLeaf(string name) : base(name)
    { }
​
    public override void Add(CompanyComponent companyComponent)
    { }
​
    public override void Remove(CompanyComponent companyComponent)
    { }
​
    public override void Display(int depth)
    {
        var name = new string('-', depth) + mName;
        Debug.Log(name);
    }
}

运行类,调用根节点打印树全部结构信息

private void Start()
{
    // 创建公司
    CompanyComponent company = new DepartmentComposite("Company ,Ltd.");
    // 创建部门
    CompanyComponent dep1 = new DepartmentComposite("总裁办");
    CompanyComponent dep2 = new DepartmentComposite("技术部");
    // 创建员工
    CompanyComponent boss = new EmployeeLeaf("青衣");
    CompanyComponent tech1 = new EmployeeLeaf("伊埃斯");
    CompanyComponent tech2 = new EmployeeLeaf("艾莲·乔");
​
    // 部门添加到公司
    company.Add(dep1);
    company.Add(dep2);
    // 员工添加到部门
    dep1.Add(boss);
    dep2.Add(tech1);
    dep2.Add(tech2);
​
    // 打印公司信息
    company.Display(3); // 3为打印“-”符号的基础数量
}


享元

共享元素(对象)设计模式

当系统中大量使用某些相同或相似的对象,这些对象会消耗大量的资源,并且这些对象剔除外部状态后可以通过同一个对象来替代,这时就可以使用享元设计模式解决

  • 内部状态:对象内部不受环境改变的部分作为内部状态

    • 例如,视频内容本身数据

  • 外部状态:随着环境的变化而变化的部分

    • 例如,上传人,上传时间

运用共享技术有效的支持大量细粒度的对象(池技术,如,数据库连接池、线程池、对象缓存池),避免创建大量重复对象

  • Flyweight:享元对象的超类或接口,可接受并用作外部状态

  • ConcreteFlyweight:具体的享元类

  • FlyweightFactory:享元工厂,创建并管理享元对象

使用场景

通过共享相似对象来减少内存消耗

示例

以共享自行车为例子

自行车抽象超类

public abstract class FlyweightBike
{
    // 内部状态:BikeID、State 0为锁定,1为骑行
    // 外部状态:用户
    
    public string mBikeID { get; set; }
    public int mState { get; set; }
    
    public abstract void Ride(string userName);
    public abstract void Back(string userName);
}

具体自行车子类

public class YellowBike : FlyweightBike
{
    public YellowBike(string id)
    {
        mBikeID = id;
    }
​
    public override void Ride(string userName)
    {
        mState = 1;
        Debug.Log($"用户{userName}骑着ID为{mBikeID}的小黄车");
    }
​
    public override void Back(string userName)
    {
        mState = 0;
        Debug.Log($"用户{userName}归还ID为{mBikeID}的小黄车");
    }
}

创建享元工厂

public class FlyweightFactory
{
    private readonly List<FlyweightBike> _mBikePool = new();
​
    public FlyweightFactory()
    {
        for (var i = 0; i < 10; i++)
        {
            _mBikePool.Add(new YellowBike(i.ToString()));
        }
    }
​
    public FlyweightBike GetBike()
    {
        return _mBikePool.FirstOrDefault(bike => bike.mState == 0);
    }
}

运行类

private void Start()
{
    FlyweightFactory factory = new FlyweightFactory();
​
    FlyweightBike bike = factory.GetBike();
    bike.Ride("可琳");
    FlyweightBike bike2 = factory.GetBike();
    bike2.Ride("流莹");
    bike.Back("可琳");
    FlyweightBike bike3 = factory.GetBike();
    bike3.Ride("猫宫又奈");
​
    bike2.Back("流莹");
    bike3.Back("猫宫又奈");
}


行为型

模板方法

对一个算法已确定其执行顺序,但某个/些具体步骤实现与具体的环境相关,则将该步骤延迟到子类实现,使得子类可以重新定义算法的某些步骤,而不改变算法的结构

例如,去银行办理业务有4个步骤:取号、排队、办理具体业务、评分,其中取号、排队、评分都是一样的,可在父类实现,而办理具体业务因人而异,需要延迟到子类进行具体实现

  • Abstract:抽象类,负责给出一个算法的轮廓和骨架,由一个模板方法和若干个基本方法构成

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法

    • 基本方法:实现算法各个步骤的方法,是模板方法的组成部分

      • 抽象方法:由抽象类声明,其子类实现

      • 具体方法:由抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承

      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法(一般钩子方法是用于判断的逻辑方法,该类方法名一般为IsXxx,返回值为bool类型)

  • Concrete:具体子类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤

使用场景
  • 算法整体步骤很固定,但其中个别部分易变时,将易变部分抽象出来,供子类实现

  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制

示例

以炒菜为例

定义抽象类,包含一系列方法

public abstract class Cook
{
    // 基本方法
    protected virtual void PourOil()
    {
        Debug.Log("倒入油");
    }
​
    protected virtual void HeatOil()
    {
        Debug.Log("进行热油");
    }
​
    protected virtual void Fry()
    {
        Debug.Log("进行翻炒");
    }
    
    // 抽象方法
    protected abstract void PourVegetables();
    protected abstract void AddSauce();
    
    // 模板方法
    public void CookProcess()
    {
        PourOil();
        HeatOil();
        PourVegetables();
        AddSauce();
        Fry();
    }
}

各种烹饪方法子类

public class StirFry : Cook
{
    protected override void PourOil()
    {
        Debug.Log("倒入金龙油");
    }
​
    protected override void PourVegetables()
    {
        Debug.Log("下锅蔬菜为辣椒和包菜");
    }
​
    protected override void AddSauce()
    {
        Debug.Log("加入辣椒酱");
    }
}
​
public class Steamed : Cook
{
    protected override void PourVegetables()
    {
        Debug.Log("下锅蔬菜为包菜");
    }
​
    protected override void AddSauce()
    {
        Debug.Log("加入蒜蓉酱");
    }
}

运行类

private void Start()
{
    Cook cook = new StirFry();
    cook.CookProcess();
}

策略

该模式定义了一系列的算法,并将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响使用算法的客户端

通过对算法的封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理,使得算法可以独立于使用它的客户变化,通过定义一系列可互换的算法实现

例如,出行到达某一目的地可以使用不同的交通工具,则这些不同的交通工具就为不同的策略

  • Strategy:抽象策略,通常为一个接口或抽象类,其给出所有的具体策略所需的接口

  • Concrete Strategy:具体策略,提供具体的算法实现或行为

  • Context:环境上下文,持有一个策略类的引用,最终给客户端调用

使用场景
  • 一个系统需要动态的在几种算法之中选择一种时,可将每个算法封装到策略类中

  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句(如switch)的形式出现,可将每个条件分支移入它们各自的策略类中以替换这些语句

  • 系统中各算法彼此完全独立,且要求对客户端隐藏实现细节时

  • 系统要求使用算法的客户端不应该知道其操作的数据时,可使用该模式来隐藏与算法相关的数据结构

  • 多个类只区别在表现行为的不同,可使用该模式,在运行时动态选择具体要执行的行为

示例

以促销活动为例,针对不同节日推出不同的促销活动

抽象策略接口

public interface ISaleStrategy
{
    void ShowActivity();
}

定义不同的促销策略

public class SpringStrategy : ISaleStrategy
{
    public void ShowActivity()
    {
        Debug.Log("满100减50");
    }
}
​
public class MidAutumnStrategy : ISaleStrategy
{
    public void ShowActivity()
    {
        Debug.Log("满200减100");
    }
}
​
public class SixOneEightStrategy : ISaleStrategy
{
    public void ShowActivity()
    {
        Debug.Log("满300减150");
    }
}

销售员为环境上下文,负责展示促销内容

public class Salesman
{
    private readonly ISaleStrategy _mStrategy;
​
    public Salesman(ISaleStrategy strategy)
    {
        _mStrategy = strategy;
    }
​
    public void Promote()
    {
        _mStrategy.ShowActivity();
    }
}

运行类

private void Start()
{
    ISaleStrategy strategy = new MidAutumnStrategy();
    Salesman salesman = new Salesman(strategy);
    salesman.Promote();
}

命令

将一个请求封装为对象,使得发出请求的责任和执行请求的责任分割开,方便请求可参数化、排队执行、记录日志及撤销重做

  • Command:抽象命令,定义命令的接口,声明执行的方法

  • Concrete Command:具体命令,通常会持有接收者,并调用接收者的功能来完成命令的执行

  • Receiver:接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能

  • Sender:请求者,通常持有具体命令对象,可持有多个命令对象,相当于是使用命令对象的人口

使用场景
  • 系统需要将请求发送者(调用者)和请求接收者解耦,使得发送者(调用者)和接收者不直接交互

  • 系统需要在不同的时间指定请求,将请求排队和执行请求

  • 系统需要支持命令的撤销(Undo)和重做(Redo)操作

示例

以顾客点单为例,服务员为请求者,大厨为接收者,命令为订单

抽象命令接口

public interface ICommand
{
    void Execute();
}

请求封装为对象

public class Order
{
    /// <summary>
    /// 餐桌编号
    /// </summary>
    public int mDiningTable { get; set; }
​
    /// <summary>
    /// 所点的菜品和份数
    /// </summary>
    public readonly Dictionary<string, int> mFoodDict = new();
​
    // 点餐
    public void OrderFood(string foodName, int count)
    {
        mFoodDict.Add(foodName, count);
    }
}

定义接收者

public class Chef
{
    public void Cook(string name, int count)
    {
        Debug.Log($"开始烹饪{count}份{name}");
    }
}

定义发送者,用来供客户端添加和执行命令

public class Waiter
{
    private readonly List<ICommand> _mCommandList = new();
​
    public void AddCommand(ICommand command)
    {
        _mCommandList.Add(command);
    }
​
    public void OrderUp()
    {
        Debug.Log("服务员通知大厨制作订单");
        _mCommandList.ForEach(c => c.Execute());
    }
}

具体的命令对象,包含接收者和封装的请求对象,并在执行方法中调用接收者相应的功能方法

public class OrderCommand : ICommand
{
    private readonly Chef _mReceiver;
    private readonly Order _mOrder;
​
    public OrderCommand(Chef receiver, Order order)
    {
        _mReceiver = receiver;
        _mOrder = order;
    }
​
    public void Execute()
    {
        Debug.Log($"制作{_mOrder.mDiningTable}桌的订单:");
        foreach (var foodName in _mOrder.mFoodDict.Keys)
        {
            _mReceiver.Cook(foodName, _mOrder.mFoodDict[foodName]);
        }
        Debug.Log($"{_mOrder.mDiningTable}桌的菜烹饪完成");
    }
}

运行类

private void Start()
{
    // 新增订单
    Order order = new Order
    {
        mDiningTable = 1001
    };
    order.OrderFood("汉堡包", 1);
    order.OrderFood("鸡翅", 3);
    order.OrderFood("牛排", 2);
    Order order2 = new Order
    {
        mDiningTable = 1002
    };
    order2.OrderFood("可乐", 1);
    order2.OrderFood("鸡腿堡", 1);
    order2.OrderFood("牛排", 2);
​
    // 创建命令
    Chef chef = new Chef();
    ICommand orderCommand = new OrderCommand(chef, order);
    ICommand orderCommand2 = new OrderCommand(chef, order2);
    
    // 添加命令
    Waiter waiter = new Waiter();
    waiter.AddCommand(orderCommand);
    waiter.AddCommand(orderCommand2);
    
    // 执行命令
    waiter.OrderUp();
}

职责链

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一对象的引用而连成一条链,当有请求发生时,通过将请求沿链传递,直到有对象处理它,避免请求发送者和接收者的耦合

例如,拦截器进行放行则可传递请求至下一个拦截器,否则不再传递

  • Handler:抽象处理者,定义一个处理请求的接口,包含处理方法和一个后续链接

  • Concrete Handler:具体处理者,实现抽象处理者的处理方法,判断能否处理本次请求,可以则处理,否则请求转发到下一个处理器

  • Chain:处理器链,将一系列处理器串联起来

使用场景

将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求,并以链式进行处理

示例

以请求流程控制系统为例

封装请求参数对象

public class LeaveRequest
{
    public string mName { get; set; }
    public int mDays { get; set; }
    public string mReason { get; set; }
​
    public LeaveRequest(string name, int days, string reason)
    {
        mName = name;
        mDays = days;
        mReason = reason;
    }
}

处理器超类

public abstract class Handler
{
    protected const int NUM_ONE = 1;
    protected const int NUM_THREE = 3;
    protected const int NUM_SEVEN = 7;
​
    // 该领导处理器能处理的请假天数区间
    private int _mDaysStart;
    private int _mDaysEnd;
    
    // 指向下一个处理器
    protected Handler _mNextHandler;
​
    protected Handler(int daysStart)
    {
        _mDaysStart = daysStart;
    }
​
    protected Handler(int daysStart, int daysEnd)
    {
        _mDaysStart = daysStart;
        _mDaysEnd = daysEnd;
    }
​
    public void SetNext(Handler handler)
    {
        _mNextHandler = handler;
    }
    
    /// <summary>
    /// 处理请假请求
    /// </summary>
    /// <param name="request">请假条</param>
    protected abstract void HandleRequest(LeaveRequest request);
​
    /// <summary>
    /// 向处理器提交请假请求
    /// </summary>
    /// <param name="request">请假条</param>
    public void Submit(LeaveRequest request)
    {
        HandleRequest(request);
        if (_mNextHandler != null && request.mDays > _mDaysEnd)
        {
            _mNextHandler.HandleRequest(request);
        }
        else
        {
            Debug.Log("请假请求已处理");
        }
    }
}

不同的具体处理器

public class GroupHandler : Handler
{
    public GroupHandler() : base(0, NUM_ONE)
    { }
​
    protected override void HandleRequest(LeaveRequest request)
    {
        Debug.Log($"小组长审批{request.mName}的{request.mDays}天请假条,请求理由:{request.mReason}");
    }
}
​
public class ManagerHandler : Handler
{
    public ManagerHandler() : base(NUM_ONE, NUM_THREE)
    { }
​
    protected override void HandleRequest(LeaveRequest request)
    {
        Debug.Log($"经理审批{request.mName}的{request.mDays}天请假条,请求理由:{request.mReason}");
    }
}
​
public class CeoHandler : Handler
{
    public CeoHandler() : base(NUM_THREE, NUM_SEVEN)
    { }
​
    protected override void HandleRequest(LeaveRequest request)
    {
        Debug.Log($"CEO审批{request.mName}的{request.mDays}天请假条,请求理由:{request.mReason}");
    }
}

运行类

private void Start()
{
    // 构建职责链
    Handler groupHandler = new GroupHandler();
    Handler managerHandler = new ManagerHandler();
    Handler ceoHandler = new CeoHandler();
    groupHandler.SetNext(managerHandler);
    managerHandler.SetNext(ceoHandler);
    
    // 创建请假条
    LeaveRequest request = new LeaveRequest
    (
        "青雀",
        4,
        "游玩帝垣琼玉"
    );
    
    // 进行请假处理
    groupHandler.Submit(request);
}

状态

对有状态的对象,将状态的逻辑封装在独立的状态类中(把复杂逻辑的“判断逻辑”提取到不同的状态对象中),使得状态的变化更清晰和易于管理,允许状态对象在其内部状态发生改变时改变其行为

例如,通过按钮控制一个电梯的状态,一个电梯状态有:开门、关门、停止和运行。每一种状态改变都有可能要根据其他状态来更新处理。电梯处于运行状态时,就不能进行开门操作等等

  • Context:环境上下文,定义了客户端调用操作目标对象需要的接口,并维护一个当前状态,将与状态相关的操作委托给当前状态对象来处理

  • State:抽象状态,用以封装环境对象中的特定状态所对应的行为

  • Concrete State:具体状态,不同状态对应的不同行为

使用场景
  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用

  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时

示例

以电梯按钮操作为例

抽象电梯状态

public abstract class LiftState
{
    protected Context _mContext;
​
    public void SetContext(Context context)
    {
        _mContext = context;
    }
​
    public abstract void Open();
    public abstract void Close();
    public abstract void Run();
    public abstract void Stop();
}

实现各种电梯状态

public class OpeningState : LiftState
{
    public override void Open()
    {
        Debug.Log("电梯正在开启");
    }
​
    public override void Close()
    {
        _mContext.SetState(Context.Closing);
        _mContext.Close();
    }
​
    public override void Run()
    {
    }
​
    public override void Stop()
    {
    }
}
​
public class ClosingState : LiftState
{
    public override void Open()
    {
        _mContext.SetState(Context.Opening);
        _mContext.Open();
    }
​
    public override void Close()
    {
        Debug.Log("电梯正在关闭");
    }
​
    public override void Run()
    {
        _mContext.SetState(Context.Running);
        _mContext.Run();
    }
​
    public override void Stop()
    {
        _mContext.SetState(Context.Stopping);
        _mContext.Stop();
    }
}
​
public class RunningState : LiftState
{
    public override void Open()
    {
    }
​
    public override void Close()
    {
    }
​
    public override void Run()
    {
        Debug.Log("电梯正在运行");
    }
​
    public override void Stop()
    {
        _mContext.SetState(Context.Stopping);
        _mContext.Stop();
    }
}
​
public class StoppingState : LiftState
{
    public override void Open()
    {
        _mContext.SetState(Context.Opening);
        _mContext.Open();
    }
​
    public override void Close()
    {
        _mContext.SetState(Context.Closing);
        _mContext.Close();
    }
​
    public override void Run()
    {
        _mContext.SetState(Context.Running);
        _mContext.Run();
    }
​
    public override void Stop()
    {
        Debug.Log("电梯正在停止");
    }
}

定义环境上下文

public class Context
{
    public static readonly OpeningState Opening = new();
    public static readonly ClosingState Closing = new();
    public static readonly RunningState Running = new();
    public static readonly StoppingState Stopping = new();
    
    private LiftState _mState;
​
    public void SetState(LiftState state)
    {
        _mState = state;
        _mState.SetContext(this);
    }
    
    public void Open()
    {
        _mState.Open();
    }
    
    public void Close()
    {
        _mState.Close();
    }
    
    public void Run()
    {
        _mState.Run();
    }
​
    public void Stop()
    {
        _mState.Stop();
    }
}

运行类

private void Start()
{
    // 创建环境上下文
    Context context = new Context();
    // 设置初始状态
    context.SetState(Context.Running);
​
    // 操作电梯
    context.Open();
    context.Close();
    context.Run();
    context.Stop();
}

观察者

又称发布-订阅模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当这个对象状态改变时,通知所有依赖的对象(观察者)进行相应更新

  • Subject:抽象主题,抽象被观察者,抽象主题把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可增加和删除观察者对象

  • Concrete Subject:具体主题,具体被观察者,将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知

  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己

  • Concrete Observer:具体观察者,实现抽象观察者定义的接口,以便在得到主题更改通知时更新自身的状态

使用场景
  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时

示例

以微信公众号推送给关注了的用户消息为例

抽象主题

public interface ISubject
{
    /// <summary>
    /// 添加订阅者
    /// </summary>
    /// <param name="observer">订阅者(观察者对象)</param>
    void Attach(IObserver observer);
    
    /// <summary>
    /// 移除订阅者
    /// </summary>
    /// <param name="observer">订阅者(观察者对象)</param>
    void Detach(IObserver observer);
    
    /// <summary>
    /// 通知所有订阅者
    /// </summary>
    /// <param name="message">通知的消息</param>
    void Notify(string message);
}

不同主题

public class SubscriptionSubject : ISubject
{
    /// <summary>
    /// 存储多个观察者对象
    /// </summary>
    private readonly List<IObserver> _mObserverList = new();
    
    public void Attach(IObserver observer)
    {
        _mObserverList.Add(observer);
    }
​
    public void Detach(IObserver observer)
    {
        _mObserverList.Remove(observer);
    }
​
    public void Notify(string message)
    {
        _mObserverList.ForEach(observer => observer.Update(message));
    }
}

抽象观察者

public interface IObserver
{
    /// <summary>
    /// 主题推送
    /// </summary>
    /// <param name="message">推送内容</param>
    void Update(string message);
}

不同观察者

public class WeChatUser : IObserver
{
    private readonly string _mName;
​
    public WeChatUser(string name)
    {
        _mName = name;
    }
​
    public void Update(string message)
    {
        Debug.Log($"微信用户{_mName}接受公众号推送:{message}");
    }
}

多个观察者订阅一个主题

private void Start()
{
    IObserver user1 = new WeChatUser("青衣");
    IObserver user2 = new WeChatUser("安比");
    IObserver user3 = new WeChatUser("可琳");
​
    ISubject subject = new SubscriptionSubject();
    subject.Attach(user1);
    subject.Attach(user3);
​
    subject.Notify("狡兔屋财政再次引发赤字危机...");
}

中介者

又称调停模式

定义一个中介角色来封装一系列对象之间的交互,通过中介者简化对象之间的交互,使原有对象直接的耦合松散,且可以独立地改变它们之间的交互


Mediator:抽象中介者,是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法

  • Concrete Mediator:具体中介者,实现中介者接口,定义一个列表来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖与同事角色

  • Colleague:抽象同事类,定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能

  • Concrete Colleague:具体同事类,当需要与其他同事交互时,由中介者对象负责后续的交互

使用场景
  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解

  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时

示例

以租房为例

抽象中介者接口

public interface IMediator
{
    void Contact(string message, Person sender);
}

房屋中介

public class HouseMediator : IMediator
{
    public HouseOwner mOwner { get; set; }
    public Tenant mTenant { get; set; }
    
    public void Contact(string message, Person sender)
    {
        if (sender is HouseOwner)
        {
            mTenant.GetMessage(message);
        }
        else if (sender is Tenant)
        {
            mOwner.GetMessage(message);
        }
    }
}

抽象同事类

public abstract class Person
{
    protected string _mName { get; set; }
    protected IMediator _mMediator;
​
    protected Person(IMediator mediator, string name)
    {
        _mMediator = mediator;
        _mName = name;
    }
    
    // 和中介沟通
    public abstract void Contact(string message);
​
    public abstract void GetMessage(string message);
}

定义不同同事类,房主和租客

public class HouseOwner : Person
{
    public HouseOwner(IMediator mediator, string name) : base(mediator, name)
    {
    }
    
    public override void Contact(string message)
    {
        _mMediator.Contact(message, this);
    }
​
    public override void GetMessage(string message)
    {
        Debug.Log($"房主{_mName}收到消息:{message}");
    }
}
​
public class Tenant : Person
{
    public Tenant(IMediator mediator, string name) : base(mediator, name)
    {
    }
​
    public override void Contact(string message)
    {
        _mMediator.Contact(message, this);
    }
​
    public override void GetMessage(string message)
    {
        Debug.Log($"租房者{_mName}收到消息:{message}");
    }
}

房租和租客进行无感沟通

private void Start()
{
    HouseMediator mediator = new HouseMediator();
    Tenant tenant = new Tenant(mediator, "符玄");
    HouseOwner owner = new HouseOwner(mediator, "青雀");
    
    // 中介知道房东和租户
    mediator.mTenant = tenant;
    mediator.mOwner = owner;
    
    // 房东和租户进行联系
    tenant.Contact("青雀!我要租你的房子!");
    owner.Contact("可以,不过我要带薪休假。");
}

迭代器

提供一个对象来顺序访问聚合对象中的一系列数据,使得客户端可以遍历聚合对象,而无需关心其内部实现

  • Aggregate:抽象聚合,定义存储、添加、删除聚会元素以及创建迭代器对象的接口

  • Concrete Aggregate:具体聚合,返回一个具体迭代器实例

  • Iterator:抽象迭代器,定义访问和遍历聚合元素的接口,通常包含HasNext()Next()等方法

  • Concrete Iterator:具体迭代器,完成对聚合对象的遍历,记录遍历的当前位置

使用场景
  • 需要为多种聚合对象提供不同的遍历方式

  • 需要为遍历不同聚合结构提供统一接口时

  • 访问一个聚合对象的内容而无需暴露其内部访问细节时

示例

以存储学生对象为例

学生类

public class Student
{
    public string mName { get; set; }
    public int mAge { get; set; }
    public string mId { get; set; }
​
    public override string ToString()
    {
        return $"学生名称: {mName}, 年龄: {mAge}, Id: {mId}";
    }
}

学生迭代器类,实现IEnumeratorIEnumerable接口

public class StudentIterator : IEnumerator<Student>
{
    private readonly List<Student> _mStudentList;
​
    public StudentIterator(List<Student> studentList)
    {
        _mStudentList = studentList;
        Reset();
    }
​
    public bool MoveNext()
    {
        var currentIndex = _mStudentList.IndexOf(Current);
        if (currentIndex + 1 >= _mStudentList.Count) return false;
        Current = _mStudentList[currentIndex + 1];
        return true;
    }
​
    public void Reset()
    {
        Current = _mStudentList[0];
    }
​
    public Student Current { get; private set; }
​
    object IEnumerator.Current => Current;
​
    public void Dispose()
    {
    }
}

进行遍历操作

private void Start()
{
    List<Student> studentList = new List<Student>
    {
        new() { mName = "青雀", mAge = 200, mId = Guid.NewGuid().ToString() },
        new() { mName = "符玄", mAge = 213, mId = Guid.NewGuid().ToString() },
        new() { mName = "艾莲·乔", mAge = 17, mId = Guid.NewGuid().ToString() },
    };
    StudentIterator it = new StudentIterator(studentList);
    
    do
    {
        if (it.Current == null) continue;
        Debug.Log(it.Current.ToString());
    } while (it.MoveNext());
}

访问者

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作

使得增加新的操作变得更容易,而不改变数据结构

  • Visitor:抽象访问者,定义了对每一个元素访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数是一样的,访问者模式要求元素类的个数不能改变

  • Concrete Visitor:具体访问者,给出每个元素类访问时所产生的具体行为

  • Element:抽象元素,定义了一个接收访问方法的具体实现,而这个具体实现通常情况下是使用访问者提供的访问元素的方法

  • Object Structure:对象结构,定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可理解为一个具有容器性质或复合对象特性的类,它会含有一组元素并且可以迭代这些元素供访问者访问

使用场景
  • 对象结构相对稳定,但其操作算法经常改变的程序

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构

示例

以给宠物喂食为例,处理主人可以喂食,其他人也可以喂食

抽象元素接口

public interface IAnimal
{
    void Accept(IPerson person);
}

不同元素

public class Cat : IAnimal
{
    public void Accept(IPerson person)
    {
        person.Feed(this);
        Debug.Log("宠物猫被喂食");
    }
}
​
public class Dog : IAnimal
{
    public void Accept(IPerson person)
    {
        person.Feed(this);
        Debug.Log("宠物狗被喂食");
    }
}

抽象访问者接口

public interface IPerson
{
    void Feed(Cat cat);
    void Feed(Dog dog);
}

不同访问者

public class Master : IPerson
{
    public void Feed(Cat cat)
    {
        Debug.Log("主人正在喂猫");
    }
​
    public void Feed(Dog dog)
    {
        Debug.Log("主人正在喂狗");
    }
}
​
public class Guest : IPerson
{
    public void Feed(Cat cat)
    {
        Debug.Log("客人正在喂猫");
    }
​
    public void Feed(Dog dog)
    {
        Debug.Log("客人正在喂狗");
    }
}

对象结构

public class Home
{
    // 存储元素对象
    private readonly List<IAnimal> _mNodeList = new();
    
    public void Add(IAnimal animal)
    {
        _mNodeList.Add(animal);
    }
​
    public void Action(IPerson person)
    {
        _mNodeList.ForEach(node => node.Accept(person));
    }
}

运行类,具体访问者动作元素

private void Start()
{
    // 创建宠物(元素)
    Cat cat = new Cat();
    Dog dog = new Dog();
​
    // 创建人(访问者)
    Master master = new Master();
    Guest guest = new Guest();
​
    // 创建结构
    Home home = new Home();
​
    // 添加元素
    home.Add(cat);
    home.Add(dog);
​
    // 具体访问者访问元素
    home.Action(master);
    home.Action(guest);
}
分派

访问者模式运用到了双分派技术

  • 静态分派:发生在编译期间,分派根据静态类型信息发生

  • 动态分派:发生在运行时期,动态分派动态的置换掉某个方法


备忘录

又称快照模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态

  • Originator:发起人,记录当前时刻的内部状态信息,提供备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息

  • Memento:备忘录,负责存储发起人的内部状态,在需要时提供这些内部状态给发起人

  • Caretaker:管理者,对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改

使用场景

允许对象恢复到之前的状态,提供撤销功能

等效接口

备忘录有2个等效接口

  • 窄接口:管理者对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口,这个窄接口只允许他把备忘录对象传给其他对象

  • 宽接口:与管理者对象看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态

示例

以游戏挑战boss为例,若玩家感觉与boss决斗效果不理想,可让游戏恢复至决斗之前的状态

有2种方式

  • 白箱

  • 黑箱

白箱

备忘录对任何对象都提供一个接口,即宽接口,备忘录的内部所存储的状态就对所有对象公开

管理者可以对备忘录进行操作(增删改查等),客户端也可以对备忘录进行操作

要保存的对象类,发起人

public class GameRole
{
    public int mHealth { get; set; }
    public int mAttack { get; set; }
    public int mDefense { get; set; }
​
    public void InitState()
    {
        mHealth = 100;
        mAttack = 20;
        mDefense = 135;
    }
​
    public void Fight()
    {
        mHealth = 0;
        mAttack = 0;
        mDefense = 0;
    }
    
    // 保存角色状态
    public RoleStateMemento SaveState()
    {
        return new RoleStateMemento(mHealth, mAttack, mDefense);
    }
    
    // 恢复角色状态
    public void RecoverState(RoleStateMemento memento)
    {
        mHealth = memento.mHealth;
        mAttack = memento.mAttack;
        mDefense = memento.mDefense;
    }
    
    // 展示状态
    public void ShowState()
    {
        Debug.Log($"角色生命值: {mHealth}, 攻击力: {mAttack}, 防御力: {mDefense}");
    }
}

备忘录

public class RoleStateMemento
{
    public int mHealth { get; private set; }
    public int mAttack { get; private set; }
    public int mDefense { get; private set; }
​
    public RoleStateMemento()
    {
    }
​
    public RoleStateMemento(int health, int attack, int defense)
    {
        mHealth = health;
        mAttack = attack;
        mDefense = defense;
    }
}

备忘录管理者

public class RoleStateCaretaker
{
    public RoleStateMemento mMemento { get; set; }
    
    // 管理者可以对备忘录进行操作
}

备份大战并恢复

private void Start()
{
    Debug.Log("===========大战Boss前===========");
    GameRole role = new GameRole();
    role.InitState();
    role.ShowState();
    
    // 保存状态
    RoleStateCaretaker caretaker = new RoleStateCaretaker();
    RoleStateMemento memento = role.SaveState();
    // 客户端也可以对备忘录进行操作
    caretaker.mMemento = memento;
    
    role.Fight();
    Debug.Log("===========大战Boss后===========");
    role.ShowState();
    
    Debug.Log("===========恢复之前的状态===========");
    role.RecoverState(caretaker.mMemento);
    role.ShowState();
}
黑箱

备忘录对发起人对象提供一个宽接口,而对其他对象提供一个窄接口

备忘录设计为发起人类的内部类

窄接口,是个标识接口,故没有任何方法

public interface IMemento
{
}

修改备忘录管理者

public class RoleStateCaretaker
{
    public IMemento mMemento { get; set; }
}

修改发起者

...
// 保存角色状态
public IMemento SaveState()
{
    return new RoleStateMemento(mHealth, mAttack, mDefense);
}
​
// 恢复角色状态
public void RecoverState(IMemento memento)
{
    if (memento is not RoleStateMemento mem) return;
    mHealth = mem.mHealth;
    mAttack = mem.mAttack;
    mDefense = mem.mDefense;
}
​
// 备忘录内部类
private class RoleStateMemento : IMemento
{
    public int mHealth { get; private set; }
    public int mAttack { get; private set; }
    public int mDefense { get; private set; }
​
    public RoleStateMemento()
    {
    }
​
    public RoleStateMemento(int health, int attack, int defense)
    {
        mHealth = health;
        mAttack = attack;
        mDefense = defense;
    }
}

运行类只需使用窄接口获取备忘录

...
// 保存状态
RoleStateCaretaker caretaker = new RoleStateCaretaker();
IMemento memento = role.SaveState();
caretaker.mMemento = memento;

解释器

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

解释语言中的表达式,根据定义的文法解释特定的语言

  • Abstract Expression:抽象表达式,定义解释器接口,约定解释器的解释操作,主要包含解释方法Interpret()

  • Terminal Expression:终结符表达式,是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体的终结表达式与之相对应

  • Nonterminal Expression:非终结符表达式,是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应与一个非终结符表达式

  • Context:环境,通常包含各个解释器需要的数据或是公共功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值

  • Client:客户端,主要是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法

使用场景
  • 当语言的文法较为简单,且执行效率不是关键问题时

  • 当问题重复出现。且可以用一种简单的语言来进行表达时

  • 当一个语言需要解释执行,并且语言中的句子表示为一个抽象语法树的时候

文法规则

文法(语法)规则用于描述语言的语法结构的形式规则

expression ::= value | plus | minus
plus ::= expression '+' expression
minus ::= expression '-' expression
value ::= integer
  • ::=表定义为

  • |表或,左右的其中一个

  • ‘’引号内表字符本身,引号外表语法

抽象语法树

抽象语法树(Abstract Syntax Tree)或简称语法树是源代码语法结构的一种抽象表示

它以树状的形式表现编程语言的语法结构,树上每个节点都表示源代码的一种结构

用树形来表示符合文法规则的句子

示例

以实现加减计算器为例

抽象表达式

public interface IAbstractExpression
{
    int Interpret(Context context);
}

定义不同表达式,变量也是一种表达式

public class Minus : IAbstractExpression
{
    private readonly IAbstractExpression _mLeftExpression;
    private readonly IAbstractExpression _mRightExpression;
​
    public Minus(IAbstractExpression leftExpression, IAbstractExpression rightExpression)
    {
        _mLeftExpression = leftExpression;
        _mRightExpression = rightExpression;
    }
​
    public int Interpret(Context context)
    {
        return _mLeftExpression.Interpret(context) - _mRightExpression.Interpret(context);
    }
​
    public override string ToString()
    {
        return $"({_mLeftExpression} - {_mRightExpression})";
    }
}
​
public class Plus : IAbstractExpression
{
    private readonly IAbstractExpression _mLeftExpression;
    private readonly IAbstractExpression _mRightExpression;
​
    public Plus(IAbstractExpression leftExpression, IAbstractExpression rightExpression)
    {
        _mLeftExpression = leftExpression;
        _mRightExpression = rightExpression;
    }
​
    public int Interpret(Context context)
    {
        return _mLeftExpression.Interpret(context) + _mRightExpression.Interpret(context);
    }
​
    public override string ToString()
    {
        return $"({_mLeftExpression} + {_mRightExpression})";
    }
}
​
public class Variable : IAbstractExpression
{
    private readonly string _mName;
​
    public Variable(string name)
    {
        _mName = name;
    }
​
    public int Interpret(Context context)
    {
        return context.Lookup(this);
    }
​
    public override string ToString()
    {
        return _mName;
    }
}

定义环境类

public class Context
{
    // 存储变量和对应的值
    private readonly Dictionary<Variable, int> _mVariableMap = new();
​
    public void Assign(Variable variable, int value)
    {
        _mVariableMap[variable] = value;
    }
​
    public int Lookup(Variable variable)
    {
        return _mVariableMap[variable];
    }
}

运行类,进行计算

private void Start()
{
    // 创建环境
    Context context = new Context();
    
    // 创建多个变量
    Variable a = new Variable("a");
    Variable b = new Variable("b");
    Variable c = new Variable("c");
    Variable d = new Variable("d");
    
    // 存储遍历
    context.Assign(a, 56);
    context.Assign(b, 1);
    context.Assign(c, 23);
    context.Assign(d, 87);
    
    // 获取抽象语法树(a + b - c + d)
    // a + b
    IAbstractExpression ab = new Plus(a, b);
    // ab - c
    IAbstractExpression abc = new Minus(ab, c);
    // abc + d
    IAbstractExpression abcd = new Plus(abc, d);
​
    // 计算最终结果
    int result = abcd.Interpret(context);
    Debug.Log($"{abcd} = {result}");
}

参考

https://www.bilibili.com/video/BV1Xv4y1T7byhttps://www.bilibili.com/video/BV1Np4y1z7BU

规则,就是用来打破的( ̄へ ̄)!