23种计划模式详解
计划模式(Design Patterns)是软件工程中的一种办理方案,它提供了一套颠末验证的代码计划和架构方案,用于办理软件计划中反复出现的题目。计划模式不特定于某种编程语言,而是普遍适用于各种面向对象的编程语言。计划模式主要分为三大类:创建型模式、结构型模式和行为型模式。
1 计划模式概述
1.1 创建型模式(Creational Patterns)
创建型模式关注对象的创建过程,旨在使对象的创建与使用分离,避免体系与具体类之间的紧密耦合。
- 工厂方法模式(Factory Method):定义一个创建对象的接口,但由子类决定实例化哪个类。
- 抽象工厂模式(Abstract Factory):提供一个创建一系列相关或依赖对象的接口,而无需指定它们的具体类。
- 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
- 建造者模式(Builder):将复杂对象的创建与其表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式(Prototype):使用对象的原型实例创建新对象,即通过克隆来创建对象。
2.2 结构型模式(Structural Patterns)
结构型模式关注类和对象的组合,旨在通过组合接口和实现来实现新的功能。
- 适配器模式(Adapter):将一个类的接口转换成客户盼望的另一个接口,使原本不兼容的类可以一起工作。
- 桥接模式(Bridge):将抽象部分与实现部分分离,使它们可以独立变化。
- 组合模式(Composite):将对象组合成树形结构以表示“部分-团体”的层次结构,使客户可以一致地处理单个对象和组合对象。
- 装饰者模式(Decorator):动态地给一个对象添加额外的职责,扩展对象的功能。
- 外观模式(Facade):为子体系中的一组接口提供一个一致的界面,使子体系更轻易使用。
- 享元模式(Flyweight):通过共享尽大概多的细节以支持大量细粒度对象的高效共享。
- 代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
3.3 行为型模式(Behavioral Patterns)
行为型模式关注对象之间的通信和职责分配,旨在进步对象之间的通信服从和灵活性。
- 责任链模式(Chain of Responsibility):为请求创建一个接收者对象的链,沿着这条链通报请求,直到有一个对象处理它为止。
- 下令模式(Command):将请求封装成对象,以便使用不同的请求、队列或日记参数化其他对象。
- 解释器模式(Interpreter):为语言创建解释器,定义一种文法表示,并建立一个解释器来处理该文法。
- 迭代器模式(Iterator):提供一种方法顺序访问聚合对象中的各个元素,而不暴露其内部表示。
- 中介者模式(Mediator):用一个中介对象来封装一系列对象交互,使对象之间不需要显式地相互引用,从而使它们可以松散耦合。
- 备忘录模式(Memento):在不粉碎封装的条件下,捕获对象的内部状态,并在以后恢复该状态。
- 观察者模式(Observer):定义对象间的一对多依赖关系,当一个对象改变状态时,所有依赖对象都会被关照并主动更新。
- 状态模式(State):允许对象在内部状态改变时改变其行为,看起来对象好像修改了它的类。
- 战略模式(Strategy):定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。
- 模板方法模式(Template Method):定义一个操作中的算法骨架,而将一些步骤耽误到子类中,使得子类可以在不改变算法结构的环境下重新定义算法的某些步骤。
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的条件下定义作用于这些元素的新操作。
2 计划模式详解
2.1 简单工厂模式(Simple Factory Pattern)
2.1.1 定义
简单工厂模式,也称为静态工厂方法模式,并不属于GoF(Gang of Four)计划模式之一。它提供了一个创建对象的简单接口,通过通报不同的参数来决定创建哪种类的实例。
2.1.2 结构
简单工厂模式包罗以下角色:
- 工厂类(Factory): 提供一个静态方法,根据传入的参数来创建不同范例的对象。
- 产物(Product): 定义创建对象的接口或抽象类。
- 具体产物(ConcreteProduct): 实现产物接口的具体类。
UML 类图
- +-------------------+
- | Factory |
- +-------------------+
- | + CreateProduct() |
- +-------------------+
- |
- v
- +-------------------+
- | Product |
- +-------------------+
- / \
- / \
- +--------+ +---------+
- |ProductA| |ProductB |
- +--------+ +---------+
复制代码 2.1.3 示例代码
- // 产品接口
- public interface IProduct
- {
- void Display();
- }
- // 具体产品A
- public class ProductA : IProduct
- {
- public void Display()
- {
- Console.WriteLine("Product A");
- }
- }
- // 具体产品B
- public class ProductB : IProduct
- {
- public void Display()
- {
- Console.WriteLine("Product B");
- }
- }
- // 简单工厂
- public class SimpleFactory
- {
- public static IProduct CreateProduct(string type)
- {
- if (type == "A")
- {
- return new ProductA();
- }
- else if (type == "B")
- {
- return new ProductB();
- }
- else
- {
- throw new ArgumentException("Invalid type");
- }
- }
- }
- // 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- IProduct product = SimpleFactory.CreateProduct("A");
- product.Display();
- }
- }
复制代码 2.1.4 特点
- 长处:
- 简单易懂,得当创建少量对象的环境。
- 统一了对象的创建过程,便于管理和修改。
- 缺点:
- 不符合开放-关闭原则:每增加一种新范例的产物,都需要修改工厂类。
- 工厂类过于会合,扩展性较差,随着产物种类的增加,工厂类会变得痴肥。
- 只适用于创建单一产物族的环境。
2.1.5 适用场景
- 当一个体系中需要创建的对象较少,并且不需要对创建逻辑举行高度的扩展或修改时。
- 当客户端只需指定创建对象的范例,而不关心具体的创建过程时。
2.1.6 工厂方法模式(Factory Method Pattern)
2.1.6.1 定义
工厂方法模式定义了一个用于创建对象的接口,但由子类决定实例化哪一个类。通过这种方式,工厂方法将对象的创建推迟到子类。
2.1.6.2 结构
工厂方法模式包罗以下角色:
- 抽象产物(Product): 定义工厂方法所创建对象的接口或抽象类。
- 具体产物(ConcreteProduct): 实现 Product 接口或继承 Product 抽象类的具体类。
- 抽象创建者(Creator): 声明工厂方法,该方法返回一个 Product 范例的对象。也可以包罗一些默认实现。
- 具体创建者(ConcreteCreator): 实现工厂方法,返回具体产物的实例。
UML 类图
- +---------------------+
- | Creator |
- +---------------------+
- | + FactoryMethod() |
- +---------------------+
- |
- |
- v
- +---------------------+
- | ConcreteCreator |
- +---------------------+
- | + FactoryMethod() |
- +---------------------+
- |
- |
- v
- +---------------------+
- | ConcreteProduct |
- +---------------------+
- | Implements Product |
- +---------------------+
复制代码 2.1.6.3 示例代码
假设我们要创建不同范例的日记记录器(如控制台日记记录器和文件日记记录器),可以使用工厂方法模式。
- // 产品接口
- public interface ILogger
- {
- void Log(string message);
- }
- // 具体产品1:控制台日志记录器
- public class ConsoleLogger : ILogger
- {
- public void Log(string message)
- {
- Console.WriteLine("Console Logger: " + message);
- }
- }
- // 具体产品2:文件日志记录器
- public class FileLogger : ILogger
- {
- public void Log(string message)
- {
- Console.WriteLine("File Logger: " + message);
- }
- }
- // 抽象创建者
- public abstract class LoggerFactory
- {
- public abstract ILogger CreateLogger();
- public void WriteLog(string message)
- {
- var logger = CreateLogger();
- logger.Log(message);
- }
- }
- // 具体创建者1
- public class ConsoleLoggerFactory : LoggerFactory
- {
- public override ILogger CreateLogger()
- {
- return new ConsoleLogger();
- }
- }
- // 具体创建者2
- public class FileLoggerFactory : LoggerFactory
- {
- public override ILogger CreateLogger()
- {
- return new FileLogger();
- }
- }
- // 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- LoggerFactory loggerFactory = new ConsoleLoggerFactory();
- loggerFactory.WriteLog("This is a console log.");
- loggerFactory = new FileLoggerFactory();
- loggerFactory.WriteLog("This is a file log.");
- }
- }
复制代码 2.1.6.4 特点
- 长处:
- 遵照单一职责原则:将对象的创建和使用分离。
- 遵照开放-关闭原则:通过子类来扩展新的产物范例,而无需修改现有代码。
- 缺点:
- 随着产物种类的增加,会引入多个具体创建者,导致代码量增加。
- 每新增一种产物,需要新增一个具体的创建者类,增加了体系的复杂性。
2.1.6.5 适用场景
- 当体系需要将创建对象的过程与其使用过程分开时。
- 当一个类不应决定它所创建的对象的具体范例时,可以使用工厂方法模式。
2.2 抽象工厂模式(Abstract Factory Pattern)
2.2.1 定义
抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。它适用于需要创建多个产物族的环境。
2.2.2 结构
抽象工厂模式包罗以下角色:
- 抽象工厂(AbstractFactory): 声明创建一系列相关对象的接口。
- 具体工厂(ConcreteFactory): 实现抽象工厂中的创建方法,天生具体产物。
- 抽象产物(AbstractProduct): 为每种产物声明接口。
- 具体产物(ConcreteProduct): 定义由具体工厂创建的产物对象,必须实现抽象产物接口。
UML 类图
- +-------------------------+
- | AbstractFactory |
- +-------------------------+
- | + CreateProductA() |
- | + CreateProductB() |
- +-------------------------+
- /|\
- |
- +-------------------------+
- | ConcreteFactory1 |
- +-------------------------+
- | + CreateProductA() |
- | + CreateProductB() |
- +-------------------------+
- |
- +-------------------------+
- | ConcreteFactory2 |
- +-------------------------+
- | + CreateProductA() |
- | + CreateProductB() |
- +-------------------------+
- /|\
- |
- +----------------------------+ +----------------------------+
- | AbstractProductA | | AbstractProductB |
- +----------------------------+ +----------------------------+
- | + OperationA() | | + OperationB() |
- +----------------------------+ +----------------------------+
- /|\ /|\
- | |
- +----------------------------+ +----------------------------+
- | ProductA1 | | ProductB1 |
- +----------------------------+ +----------------------------+
- | + OperationA() | | + OperationB() |
- +----------------------------+ +----------------------------+
- |
- +----------------------------+
- | ProductA2 |
- +----------------------------+
- | + OperationA() |
- +----------------------------+
复制代码 2.2.3 示例代码
假设我们要创建两种风格的家具:现代风格和维多利亚风格,每种风格包括椅子和沙发。我们可以使用抽象工厂模式来实现。
2.2.4 特点
- 长处:
- 遵照开放-关闭原则:可以通过添加新的具体工厂来扩展产物家属,而不修改现有代码。
- 保证产物族的一致性:所有由同一具体工厂创建的产物都是相互兼容的。
- 缺点:
- 复杂度较高:随着产物族和具体产物的增加,类的数量会显著增加。
- 扩展困难:增加新的产物族时,必须修改抽象工厂及其子类,这违背了开放-关闭原则。
2.2.5 适用场景
- 当体系要创建多个产物族的对象时。
- 当你需要确保一个产物族中的所有对象是兼容的时。
- 当产物族的创建逻辑较为复杂,需要封装起来时。
2.2.6 比较总结
- 工厂方法模式:
- 适用于创建单一产物的场景。
- 通过子类化具体创建者来扩展产物范例。
- 工厂方法模式更关注的是"怎样创建"一个对象。
- 抽象工厂模式:
- 适用于创建多个相关产物的场景。
- 通过增加新的具体工厂来扩展产物族。
- 抽象工厂模式更关注的是"创建什么"(一系列相关的对象)。
在实际应用中,工厂方法模式较得当较简单的对象创建场景,而抽象工厂模式则得当更复杂的、多产物族的对象创建需求。
2.3 单例模式(Singleton Pattern)
2.3.1 定义
单例模式确保一个类只有一个实例,并提供一个访问该实例的全局访问点。它可以防止类被多次实例化,并且在某些环境下可以节省内存、确保一致性或控制资源的访问。
2.3.2 结构
单例模式的主要角色包括:
- 单例类(Singleton): 包罗一个私有的静态变量来生存单例实例,并提供一个公共的静态方法来返回这个实例。
UML 类图
- +----------------------+
- | Singleton |
- +----------------------+
- | - instance: Singleton|
- +----------------------+
- | + getInstance(): |
- | Singleton |
- +----------------------+
复制代码 2.3.3 示例代码
单例模式的实现有多种方式,以下是最常见的几种。
懒汉式(Lazy Initialization)
懒汉式实现中,实例在第一次调用 getInstance() 方法时才被创建。这种方式可以耽误实例的创建,节省资源,但在多线程环境下需要举行同步以保证线程安全。
- public class Singleton
- {
- private static Singleton _instance;
- // 构造函数设置为私有,防止通过new创建实例
- private Singleton() { }
- public static Singleton GetInstance()
- {
- if (_instance == null)
- {
- _instance = new Singleton();
- }
- return _instance;
- }
- }
复制代码 线程安全的懒汉式
为了保证线程安全,可以在 getInstance 方法上添加 lock 关键字,但这样大概会低落性能。
- public class Singleton
- {
- private static Singleton _instance;
- private static readonly object _lock = new object();
- private Singleton() { }
- public static Singleton GetInstance()
- {
- lock (_lock)
- {
- if (_instance == null)
- {
- _instance = new Singleton();
- }
- }
- return _instance;
- }
- }
复制代码 双重查抄锁定(Double-Check Locking)
这种方法在查抄实例是否已经存在时只加一次锁,进步了性能。这是线程安全且高效的实现方式。
- public class Singleton
- {
- private static Singleton _instance;
- private static readonly object _lock = new object();
- private Singleton() { }
- public static Singleton GetInstance()
- {
- if (_instance == null)
- {
- lock (_lock)
- {
- if (_instance == null)
- {
- _instance = new Singleton();
- }
- }
- }
- return _instance;
- }
- }
复制代码 饿汉式(Eager Initialization)
饿汉式在类加载时就创建实例,因此不存在线程安全题目,但假如实例比较大且未使用时,会浪费资源。
- public class Singleton
- {
- private static readonly Singleton _instance = new Singleton();
- // 构造函数设置为私有,防止通过new创建实例
- private Singleton() { }
- public static Singleton GetInstance()
- {
- return _instance;
- }
- }
复制代码 静态内部类(Static Inner Class)
使用静态内部类的方式可以实现耽误加载和线程安全。静态内部类的实例只会在第一次被引用时初始化,因此可以实现懒加载效果。
- public class Singleton
- {
- private Singleton() { }
- private static class SingletonHolder
- {
- internal static readonly Singleton _instance = new Singleton();
- }
- public static Singleton GetInstance()
- {
- return SingletonHolder._instance;
- }
- }
复制代码 枚举(Enum)
使用枚举来实现单例是最简单和安全的方式,因为枚举实例化是线程安全的,并且只会被实例化一次。这种方式不仅实现了单例,而且还能防止反序列化和反射攻击。
- public enum Singleton
- {
- Instance;
- public void SomeMethod()
- {
- Console.WriteLine("Singleton method called.");
- }
- }
复制代码 2.3.4 特点
- 长处:
- 控制实例数量: 确保体系中只有一个实例存在,减少内存开销。
- 全局访问点: 提供了一个全局访问点,便于共享实例。
- 避免资源冲突: 多个线程或进程访问同一资源时,单例模式可以有效地避免冲突。
- 缺点:
- 不易扩展: 由于单例类不能被继承,或者不应该被继承,导致其难以扩展。
- 隐藏依赖: 单例模式通过全局访问点共享状态,大概导致隐藏依赖,使得代码难以理解和测试。
- 多线程题目: 在多线程环境下实现单例模式需要警惕处理,否则大概导致线程安全题目。
2.3.5 适用场景
- 需要全局唯一实例的场景: 如线程池、数据库毗连池、设置文件管理器等。
- 需要控制资源的场景: 如打印机管理类,控制对同一资源的并发访问。
- 需要共享状态的场景: 如应用程序的日记类,保证所有日记信息都记录到同一对象中。
单例模式在实际开发中非常常见,但在使用时要注意其潜伏的缺陷,特别是在多线程和高并发的环境下,需要选择合适的实现方式以确保线程安全。
2.4 建造者模式(Builder Pattern)
2.4.1 定义
建造者模式将一个复杂对象的构造过程分离出来,使得相同的构造过程可以创建不同的表示。它使用多个简单的对象一步一步构建复杂对象,通过不同的建造者实现不同的构建方式。
2.4.2 结构
建造者模式主要包罗以下角色:
- 产物(Product): 最终要创建的复杂对象。
- 抽象建造者(Builder): 定义构造产物的抽象步骤接口。
- 具体建造者(ConcreteBuilder): 实现 Builder 接口,构建和装配各个部分。
- 指挥者(Director): 负责管理 Builder 接口,按步骤构建产物。
UML 类图
- +-------------------+ +-------------------+
- | Director | | Builder |
- +-------------------+ +-------------------+
- | - Construct() |<------------->| + BuildPart() |
- +-------------------+ +-------------------+
- /|\ |
- | |
- | |
- | +-------------------+
- | | ConcreteBuilder |
- | +-------------------+
- | | + BuildPart() |
- | | + GetResult() |
- | +-------------------+
- |
- |
- +------------------+
- | Product |
- +------------------+
- | + AddPart() |
- +------------------+
复制代码 2.4.3 示例代码
下面是一个用建造者模式构建复杂对象的示例。在这个例子中,我们通过建造者模式来创建一种复杂的 House 对象,包罗多个部分如地基、墙壁和屋顶。
产物类
- // 产品:House
- public class House
- {
- private List<string> parts = new List<string>();
- public void AddPart(string part)
- {
- parts.Add(part);
- }
- public void ShowParts()
- {
- Console.WriteLine("House parts:");
- foreach (var part in parts)
- {
- Console.WriteLine(part);
- }
- }
- }
复制代码 抽象建造者
- // 抽象建造者
- public abstract class HouseBuilder
- {
- protected House house = new House();
- public abstract void BuildFoundation();
- public abstract void BuildWalls();
- public abstract void BuildRoof();
- public House GetResult()
- {
- return house;
- }
- }
复制代码 具体建造者
- // 具体建造者1:建造木质房屋
- public class WoodenHouseBuilder : HouseBuilder
- {
- public override void BuildFoundation()
- {
- house.AddPart("Wooden Foundation");
- }
- public override void BuildWalls()
- {
- house.AddPart("Wooden Walls");
- }
- public override void BuildRoof()
- {
- house.AddPart("Wooden Roof");
- }
- }
- // 具体建造者2:建造石质房屋
- public class StoneHouseBuilder : HouseBuilder
- {
- public override void BuildFoundation()
- {
- house.AddPart("Stone Foundation");
- }
- public override void BuildWalls()
- {
- house.AddPart("Stone Walls");
- }
- public override void BuildRoof()
- {
- house.AddPart("Stone Roof");
- }
- }
复制代码 指挥者
- // 指挥者
- public class ConstructionDirector
- {
- private HouseBuilder _houseBuilder;
- public void SetBuilder(HouseBuilder builder)
- {
- _houseBuilder = builder;
- }
- public void ConstructHouse()
- {
- _houseBuilder.BuildFoundation();
- _houseBuilder.BuildWalls();
- _houseBuilder.BuildRoof();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- var director = new ConstructionDirector();
- // 建造木质房屋
- var woodenHouseBuilder = new WoodenHouseBuilder();
- director.SetBuilder(woodenHouseBuilder);
- director.ConstructHouse();
- House woodenHouse = woodenHouseBuilder.GetResult();
- woodenHouse.ShowParts();
- Console.WriteLine();
- // 建造石质房屋
- var stoneHouseBuilder = new StoneHouseBuilder();
- director.SetBuilder(stoneHouseBuilder);
- director.ConstructHouse();
- House stoneHouse = stoneHouseBuilder.GetResult();
- stoneHouse.ShowParts();
- }
- }
复制代码 2.4.4 特点
- 长处:
- 解耦建造过程和产物表示: 客户端不需要知道构造细节,只需要通过指挥者控制构建过程。
- 代码清晰: 将复杂对象的创建过程一步一步实现,使得代码易于维护和理解。
- 更好的控制: 允许渐渐创建产物,使得每个部分的构建步骤可以独立变化。
- 缺点:
- 产物范例过多: 假如有许多不同的产物范例,大概会导致建造者类的数量过多,增加体系复杂性。
- 难以支持变化: 假如产物的构建步骤发生变化,所有具体建造者都需要修改,难以顺应变化。
2.4.5 适用场景
- 构建复杂对象: 当一个对象包罗多个部分且构建过程复杂时,使用建造者模式。
- 相同的构建过程,创建不同的表示: 当构建过程相同,但不同的具体建造者可以构建不同表示的对象时。
- 分步骤创建对象: 当需要通过多个步骤来创建一个对象,并且这些步骤大概会有所变化时。
2.4.6 与工厂模式对比
- 与工厂模式的区别: 工厂模式关注的是创建单个产物对象,而建造者模式关注的是通过多个步骤构建复杂对象。
- 与抽象工厂模式的区别: 抽象工厂模式提供了一系列相关对象的创建,而建造者模式则更关注构建过程的控制。
建造者模式非常得当在创建复杂对象时使用,尤其是在构建步骤明确且需要控制构建过程的环境下。
2.5 原型模式(Prototype Pattern)
2.5.1 定义
原型模式允许一个对象通过复制自身来创建新的对象。这种模式提供了一种创建对象的快捷方式,尤其适用于创建代价较高的对象。
2.5.2 结构
原型模式的结构包罗以下角色:
- 原型(Prototype): 定义一个可以克隆自身的接口。
- 具体原型(ConcretePrototype): 实现 Prototype 接口并能够克隆自身。
- 客户端(Client): 通过请求 Prototype 对象复制自身来创建新对象。
UML 类图
- +----------------------+ +-------------------------+
- | Prototype |<---------------| ConcretePrototype |
- +----------------------+ +-------------------------+
- | + Clone(): Prototype | | + Clone(): Prototype |
- +----------------------+ +-------------------------+
- /|\ |
- | |
- | |
- +---------------------+ +---------------------------------+
- | Client | | AnotherConcretePrototype |
- +---------------------+ +---------------------------------+
- | + Operation() | | + Clone(): Prototype |
- +---------------------+ +---------------------------------+
复制代码 2.5.3 示例代码
原型模式的焦点在于实现对象的深拷贝或浅拷贝。以下是一个实现原型模式的简单示例,其中我们克隆一个对象来创建新的对象。
原型接口
- // 原型接口
- public abstract class Prototype
- {
- public abstract Prototype Clone();
- }
复制代码 具体原型类
- // 具体原型类
- public class ConcretePrototype : Prototype
- {
- public string Name { get; set; }
- public ConcretePrototype(string name)
- {
- Name = name;
- }
- // 实现克隆方法
- public override Prototype Clone()
- {
- return (Prototype)this.MemberwiseClone(); // 浅拷贝
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建一个原型对象
- ConcretePrototype prototype1 = new ConcretePrototype("Prototype 1");
- // 克隆原型对象
- ConcretePrototype clonedPrototype = (ConcretePrototype)prototype1.Clone();
- // 显示克隆对象的属性
- Console.WriteLine("Original Prototype Name: " + prototype1.Name);
- Console.WriteLine("Cloned Prototype Name: " + clonedPrototype.Name);
- // 修改克隆对象的属性
- clonedPrototype.Name = "Cloned Prototype 1";
- Console.WriteLine("\nAfter modification:");
- Console.WriteLine("Original Prototype Name: " + prototype1.Name);
- Console.WriteLine("Cloned Prototype Name: " + clonedPrototype.Name);
- }
- }
复制代码 在这个例子中,ConcretePrototype 是具体的原型类,实现了 Clone 方法。Clone 方法使用 MemberwiseClone 来执行浅拷贝,这意味着对象的成员变量会被复制,但对象的引用范例成员变量依然指向同一个内存地址。
浅拷贝与深拷贝:
- 浅拷贝(Shallow Copy): 复制对象时,只复制对象的根本数据范例成员变量,对于引用范例成员变量,只复制引用,不复制引用指向的对象。
- 深拷贝(Deep Copy): 复制对象时,不仅复制对象的根本数据范例成员变量,也复制引用范例成员变量所引用的对象,生玉成新的副本。
深拷贝的实现
要实现深拷贝,可以在 Clone 方法中手动复制引用范例的成员变量,或者通过序列化和反序列化来实现。
- // 实现深拷贝的具体原型类
- [Serializable]
- public class DeepConcretePrototype : Prototype
- {
- public string Name { get; set; }
- public List<string> Features { get; set; }
- public DeepConcretePrototype(string name, List<string> features)
- {
- Name = name;
- Features = features;
- }
- public override Prototype Clone()
- {
- // 深拷贝:通过序列化和反序列化
- using (MemoryStream stream = new MemoryStream())
- {
- IFormatter formatter = new BinaryFormatter();
- formatter.Serialize(stream, this);
- stream.Seek(0, SeekOrigin.Begin);
- return (Prototype)formatter.Deserialize(stream);
- }
- }
- }
复制代码 2.5.4 特点
- 长处:
- 减少对象创建的成本: 尤其是当对象的创建代价很高时,通过克隆一个现有的对象可以减少资源消耗。
- 避免复杂对象的重复创建: 当创建复杂对象需要设置大量参数或步骤时,原型模式可以避免重复工作。
- 易于扩展: 可以通过继承现有原型类来扩展新的克隆行为,而无需修改现有代码。
- 缺点:
- 深拷贝实现复杂: 假如对象包罗复杂的引用关系,深拷贝的实现大概会很复杂。
- 轻易引起肴杂: 假如在体系中有多个原型对象,大概会导致代码的可读性低落,增加理解难度。
2.5.5 适用场景
- 对象创建代价高: 当一个对象的创建成本高昂(如初始化设置、数据库毗连等)时,使用原型模式可以减少创建成本。
- 对象结构复杂: 对于具有复杂结构的对象,使用原型模式可以避免重复的创建工作。
- 需要多个雷同实例: 当需要天生一系列相似对象时,使用原型模式可以通过克隆现有对象来快速创建新对象。
2.5.6 与其他模式对比
- 与工厂模式的区别: 工厂模式是通过实例化来创建对象,而原型模式是通过克隆已有对象来创建新对象。
- 与建造者模式的区别: 建造者模式关注的是一步步构建复杂对象,而原型模式是通过复制现有对象来创建新对象。
原型模式非常得当在对象创建复杂或成本较高的场景下使用,它通过克隆来简化对象的创建过程并进步性能。
2.6 适配器模式(Adapter Pattern)
2.6.1 定义
适配器模式将一个类的接口转换成客户盼望的另一个接口,适配器使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
2.6.2 结构
适配器模式主要包罗以下角色:
- 目标接口(Target): 定义客户所等待的接口。
- 适配者(Adaptee): 需要适配的类,接口不兼容的已有组件。
- 适配器(Adapter): 实现 Target 接口并包装一个 Adaptee 对象,从而将 Adaptee 的接口转换为 Target 的接口。
- 客户端(Client): 通过 Target 接口与适配器交互。
UML 类图
- +-------------------+ +-------------------+
- | Client | | Target |
- +-------------------+ +-------------------+
- | - Request() |<-------| + Request() |
- +-------------------+ +-------------------+
- ^
- |
- +-----------------------+
- | Adapter |
- +-----------------------+
- | + Request() |
- | - adaptee: Adaptee |
- +-----------------------+
- |
- v
- +-----------------------+
- | Adaptee |
- +-----------------------+
- | + SpecificRequest() |
- +-----------------------+
复制代码 2.6.3 示例代码
以下是一个实现适配器模式的简单示例。在这个例子中,Adaptee 类有一个接口 SpecificRequest,它与 Target 接口 Request 不兼容。通过适配器模式,我们可以创建一个适配器类,将 Adaptee 的接口转换为 Target 的接口,使得客户端可以通过 Target 接口与 Adaptee 交互。
目标接口
- // 目标接口
- public interface ITarget
- {
- void Request();
- }
复制代码 适配者类
- // 需要适配的类
- public class Adaptee
- {
- public void SpecificRequest()
- {
- Console.WriteLine("Called SpecificRequest()");
- }
- }
复制代码 适配器类
- // 适配器类
- public class Adapter : ITarget
- {
- private readonly Adaptee _adaptee;
- public Adapter(Adaptee adaptee)
- {
- _adaptee = adaptee;
- }
- public void Request()
- {
- // 调用适配者的接口,将其转换为目标接口
- _adaptee.SpecificRequest();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 客户端需要通过 ITarget 接口调用方法
- ITarget target = new Adapter(new Adaptee());
-
- // 客户端通过目标接口与适配器交互
- target.Request();
- }
- }
复制代码 2.6.4 特点
- 长处:
- 进步了类的复用性: 通过适配器模式,原本无法复用的类如今可以在新的环境中使用。
- 进步了类的灵活性: 通过使用适配器,可以轻松引入新接口,改变现有类的行为,而不需要修改现有代码。
- 符合开闭原则: 新增适配器类不会影响现有代码的功能,符合开闭原则。
- 缺点:
- 复杂性增加: 引入适配器模式大概会增加体系的复杂性,特别是在适配多个接口时,需要创建多个适配器类。
- 性能开销: 假如适配器做了大量的转换工作,大概会带来一定的性能开销。
2.6.5 适用场景
- 现有类与新接口不兼容: 当你想使用一个已经存在的类,但它的接口不符合你的要求时,可以使用适配器模式。
- 引入遗留体系: 在引入遗留体系的类库时,使用适配器模式可以避免修改现有的代码。
- 替代接口重构: 当接口发生变化时,可以通过适配器模式渐渐过渡到新的接口,而不需要一次性修改所有依赖代码。
2.6.6 与其他模式的关系
- 与装饰器模式的区别: 装饰器模式关注的是增强对象的功能,而适配器模式关注的是将一个接口转换为另一个接口。
- 与桥接模式的区别: 桥接模式是为了将抽象部分与实现部分分离,从而可以独立地变化,而适配器模式是为了让现有的接口适配新的接口。
适配器模式非常得当在项目中需要集成现有体系或类库,而它们的接口又与当前需求不兼容时使用。通过适配器模式,可以在不修改现有代码的条件下实现接口的兼容和扩展。
2.7 桥接模式(Bridge Pattern)
2.7 .1 定义
桥接模式允许在运行时将抽象类与实在现类解耦。它通过引入一个接口来使得抽象类与具体的实现类分开,从而支持两者的独立变化。这样可以避免复杂的多层继承结构。
2.7 .2 结构
桥接模式的结构包罗以下角色:
- 抽象类(Abstraction): 定义抽象接口,并生存一个指向实现类的引用。
- 扩展抽象类(Refined Abstraction): 继承抽象类,并实现其接口。
- 实现类接口(Implementor): 定义实现类的接口。
- 具体实现类(Concrete Implementor): 实现实现类接口的具体类。
UML 类图
- +-------------------+ +-------------------+
- | Abstraction | | Implementor |
- +-------------------+ +-------------------+
- | - implementor | | + OperationImpl() |
- | + Operation() | +-------------------+
- +-------------------+ ^
- | |
- | |
- +-------------------+ +---------------------+
- | RefinedAbstraction| | ConcreteImplementor |
- +-------------------+ +---------------------+
- | + Operation() | | + OperationImpl() |
- +-------------------+ +---------------------+
复制代码 2.7 .3 示例代码
以下是一个实现桥接模式的简单示例。假设我们要创建一个图形绘制程序,其中有不同范例的图形和不同的绘制方式。
实现类接口
- // 实现者接口
- public interface IColor
- {
- void ApplyColor();
- }
复制代码 具体实现类
- // 具体实现者1:红色
- public class RedColor : IColor
- {
- public void ApplyColor()
- {
- Console.WriteLine("Applying red color.");
- }
- }
- // 具体实现者2:绿色
- public class GreenColor : IColor
- {
- public void ApplyColor()
- {
- Console.WriteLine("Applying green color.");
- }
- }
复制代码 抽象类
- // 抽象类:形状
- public abstract class Shape
- {
- protected IColor color;
- protected Shape(IColor color)
- {
- this.color = color;
- }
- public abstract void Draw();
- }
复制代码 扩展抽象类
- // 细化抽象类1:圆形
- public class Circle : Shape
- {
- public Circle(IColor color) : base(color)
- {
- }
- public override void Draw()
- {
- Console.Write("Circle is being drawn. ");
- color.ApplyColor();
- }
- }
- // 细化抽象类2:矩形
- public class Rectangle : Shape
- {
- public Rectangle(IColor color) : base(color)
- {
- }
- public override void Draw()
- {
- Console.Write("Rectangle is being drawn. ");
- color.ApplyColor();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建红色和绿色的实现者
- IColor red = new RedColor();
- IColor green = new GreenColor();
- // 创建带有不同颜色的形状
- Shape redCircle = new Circle(red);
- Shape greenRectangle = new Rectangle(green);
- // 画形状
- redCircle.Draw(); // 输出: Circle is being drawn. Applying red color.
- greenRectangle.Draw(); // 输出: Rectangle is being drawn. Applying green color.
- }
- }
复制代码 在这个例子中:
- IColor 是实现者接口,定义了 ApplyColor() 方法。
- RedColor 和 GreenColor 是具体实现者,实现了 IColor 接口。
- Shape 是抽象类,它维护了一个 IColor 范例的引用,并定义了 Draw() 方法。
- Circle 和 Rectangle 是 Shape 的细化抽象类,分别实现了 Draw() 方法。
2.7 .4 特点
- 长处:
- 解耦抽象与实现: 通过桥接模式,抽象与实现可以独立变化,低落了体系的耦合度。
- 灵活性: 可以很方便地扩展新的抽象和实现类,而不需要修改已有的代码。
- 符合开闭原则: 新的实现可以在不修改原有代码的环境下添加。
- 缺点:
- 增加了体系的复杂性: 由于引入了多个类和接口,大概会使得体系结构变得复杂。
- 维护难度增加: 当扩展的类和接口数量增多时,维护这些类和接口的难度大概会增加。
2.7 .5 适用场景
- 多变的实现类: 当你有多个实现类和多个抽象类,并且你盼望在运行时动态组合这些实现时,可以使用桥接模式。
- 避免类爆炸: 当抽象类和实现类的数量不断增加,导致类的数量爆炸时,可以考虑使用桥接模式来减少复杂性。
2.7 .6 与其他模式的关系
- 适配器模式: 适配器模式用于办理接口不兼容的题目,而桥接模式用于将抽象与实现解耦。
- 组合模式: 桥接模式与组合模式都使用了组合的方式来构建对象,但桥接模式偏重于解耦,而组合模式更关注于树形结构的创建。
桥接模式通过将抽象与实现分离,使得体系的扩展更加灵活,适用于需要同时支持多种变化的场景。
2.8 组合模式(Composite Pattern)
2.8.1 定义
组合模式通过定义树形结构来构成对象,使得客户端对单个对象和对象聚集的处理方式一致。这个模式让你可以使用相同的接口来操作单一对象和对象聚集,简化了代码和操作逻辑。
2.8.2 结构
组合模式的结构包罗以下角色:
- 组件(Component): 定义树形结构中所有对象的共同接口,包括叶子节点和复合节点(容器节点)。
- 叶子节点(Leaf): 叶子节点是树的根本元素,它实现了 Component 接口,但没有子节点。
- 复合节点(Composite): 复合节点可以包罗子节点,包括叶子节点和其他复合节点,实现了 Component 接口并能够管理其子节点。
UML 类图
- +---------------------+
- | Component |
- +---------------------+
- | + Operation() |
- +---------------------+
- | + Add(Component) |
- | + Remove(Component) |
- | + GetChild(int) |
- +---------------------+
- ^
- |
- +---------------------+ +---------------------+
- | Leaf | | Composite |
- +---------------------+ +---------------------+
- | + Operation() | | + Operation() |
- +---------------------+ +---------------------+
- | + Add(Component) |
- | + Remove(Component) |
- | + GetChild(int) |
- +---------------------+
复制代码 2.8.3 示例代码
以下是一个实现组合模式的简单示例。在这个示例中,我们有一个文件体系,其中 File 是叶子节点,Directory 是复合节点(目次),它可以包罗多个文件或目次。
组件接口
- // 组件接口
- public abstract class FileSystemComponent
- {
- public abstract void Display(int depth);
- // 组合节点的方法
- public virtual void Add(FileSystemComponent component) { }
- public virtual void Remove(FileSystemComponent component) { }
- public virtual FileSystemComponent GetChild(int index) { return null; }
- }
复制代码 叶子节点类
- // 叶子节点类:文件
- public class File : FileSystemComponent
- {
- private string _name;
- public File(string name)
- {
- _name = name;
- }
- public override void Display(int depth)
- {
- Console.WriteLine(new string('-', depth) + _name);
- }
- }
复制代码 复合节点类
- // 复合节点类:目录
- public class Directory : FileSystemComponent
- {
- private List<FileSystemComponent> _children = new List<FileSystemComponent>();
- private string _name;
- public Directory(string name)
- {
- _name = name;
- }
- public override void Add(FileSystemComponent component)
- {
- _children.Add(component);
- }
- public override void Remove(FileSystemComponent component)
- {
- _children.Remove(component);
- }
- public override FileSystemComponent GetChild(int index)
- {
- return _children[index];
- }
- public override void Display(int depth)
- {
- Console.WriteLine(new string('-', depth) + _name);
- foreach (var child in _children)
- {
- child.Display(depth + 2);
- }
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建文件和目录
- FileSystemComponent file1 = new File("File 1");
- FileSystemComponent file2 = new File("File 2");
- FileSystemComponent file3 = new File("File 3");
- Directory directory1 = new Directory("Directory 1");
- Directory directory2 = new Directory("Directory 2");
- // 构建目录树
- directory1.Add(file1);
- directory1.Add(file2);
- directory2.Add(file3);
- directory2.Add(directory1);
- // 显示目录树
- directory2.Display(1);
- }
- }
复制代码 在这个示例中:
- FileSystemComponent 是组件接口,定义了叶子节点和复合节点的共同接口。
- File 是叶子节点,表示文件,没有子节点。
- Directory 是复合节点,表示目次,可以包罗多个子节点(文件或其他目次)。
2.8.4 特点
- 长处:
- 简化客户端代码: 客户端代码可以统一处理叶子节点和复合节点,减少了代码复杂度。
- 增加灵活性: 通过将叶子节点和复合节点统一成一个接口,可以灵活地构建和管理复杂的树形结构。
- 符合开闭原则: 可以通过添加新的叶子节点或复合节点来扩展功能,而无需修改现有代码。
- 缺点:
- 计划复杂: 组合模式大概会增加体系的复杂性,因为你需要计划和管理树形结构。
- 性能题目: 假如树形结构非常巨大,操作树形结构大概会影响性能。
2.8.5 适用场景
- 部分-团体层次结构: 当你需要表示部分-团体的层次结构时,比方文件体系、构造结构等。
- 统一处理树形结构: 当你需要统一处理树形结构中的对象,无论它们是叶子节点照旧复合节点。
- 动态构建立形结构: 当你需要动态构建和操作复杂的树形结构时,比方图形界面中的组件树。
2.8.6 与其他模式对比
- 与桥接模式的区别: 组合模式用于表示部分-团体层次结构,而桥接模式用于将抽象与实现分离。
- 与装饰器模式的区别: 组合模式主要用于处理树形结构,而装饰器模式用于动态地增加对象的功能。
组合模式非常得当用来构建复杂的树形结构,通过将对象和对象聚集统一成一个接口,它能够简化对复杂结构的操作,并进步体系的灵活性和可扩展性。
2.9 装饰器模式(Decorator Pattern)
2.9.1 定义
装饰器模式通过创建一个装饰器类,它实现了与被装饰对象相同的接口,从而可以在运行时动态地添加新功能。这种模式提供了一种灵活的替代方案来扩展对象的功能,避免了使用子类来扩展功能的需要。
2.9.2 结构
装饰器模式的结构包罗以下角色:
- 组件(Component): 定义一个接口,描述可以动态添加的功能。
- 具体组件(Concrete Component): 实现 Component 接口的具体对象,表示要装饰的对象。
- 装饰器(Decorator): 维护一个对 Component 对象的引用,定义与 Component 接口相同的接口,并可以在运行时扩展功能。
- 具体装饰器(Concrete Decorator): 扩展 Decorator,实现具体的附加功能。
UML 类图
- +-------------------+
- | Component |
- +-------------------+
- | + Operation() |
- +-------------------+
- ^
- |
- +-------------------+ +---------------------------+
- | ConcreteComponent | <------- | Decorator |
- +-------------------+ +---------------------------+
- | + Operation() | | - component: Component |
- +-------------------+ | + Operation() |
- +---------------------------+
- ^
- |
- +------------------------+
- | ConcreteDecorator |
- +------------------------+
- | + Operation() |
- | + AdditionalBehavior() |
- +------------------------+
复制代码 2.9.3 示例代码
以下是一个实现装饰器模式的简单示例。在这个示例中,我们有一个饮料的体系,可以为饮料添加不同的配料(装饰)。
组件接口
- // 组件接口
- public abstract class Beverage
- {
- public abstract string GetDescription();
- public abstract double Cost();
- }
复制代码 具体组件类
- // 具体组件:咖啡
- public class Coffee : Beverage
- {
- public override string GetDescription()
- {
- return "Coffee";
- }
- public override double Cost()
- {
- return 2.00; // 基本咖啡的价格
- }
- }
复制代码 装饰器类
- // 装饰器类
- public abstract class CondimentDecorator : Beverage
- {
- protected Beverage _beverage;
- public CondimentDecorator(Beverage beverage)
- {
- _beverage = beverage;
- }
- }
复制代码 具体装饰器类
- // 具体装饰器:牛奶
- public class MilkDecorator : CondimentDecorator
- {
- public MilkDecorator(Beverage beverage) : base(beverage)
- {
- }
- public override string GetDescription()
- {
- return _beverage.GetDescription() + ", Milk";
- }
- public override double Cost()
- {
- return _beverage.Cost() + 0.50; // 添加牛奶的价格
- }
- }
- // 具体装饰器:巧克力
- public class ChocolateDecorator : CondimentDecorator
- {
- public ChocolateDecorator(Beverage beverage) : base(beverage)
- {
- }
- public override string GetDescription()
- {
- return _beverage.GetDescription() + ", Chocolate";
- }
- public override double Cost()
- {
- return _beverage.Cost() + 0.70; // 添加巧克力的价格
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建基本的咖啡
- Beverage beverage = new Coffee();
- // 添加配料
- beverage = new MilkDecorator(beverage);
- beverage = new ChocolateDecorator(beverage);
- // 显示描述和总费用
- Console.WriteLine($"{beverage.GetDescription()} costs {beverage.Cost():C}");
- }
- }
复制代码 在这个示例中:
- Beverage 是组件接口,定义了饮料的根本功能。
- Coffee 是具体组件,表示基础饮料(咖啡)。
- CondimentDecorator 是装饰器类,它包罗了对 Beverage 对象的引用,并实现了 Beverage 接口。
- MilkDecorator 和 ChocolateDecorator 是具体装饰器,分别添加牛奶和巧克力的功能。
2.9.4 特点
- 长处:
- 灵活性: 可以动态添加和扩展对象的功能,而不需要创建大量的子类。
- 符合开闭原则: 可以通过添加新的装饰器类来扩展功能,而无需修改已有的类。
- 避免类的膨胀: 通过组合多个装饰器,可以避免创建过多的子类,从而减少类的数量。
- 缺点:
- 复杂性: 装饰器模式大概会增加体系的复杂性,尤其是当装饰器层次过多时。
- 调试困难: 由于装饰器的嵌套,调试代码时大概会变得复杂。
2.9.5 适用场景
- 需要动态扩展功能: 当你需要在运行时动态地给对象添加功能时,装饰器模式是一个抱负的选择。
- 避免类的膨胀: 当类的扩展导致创建大量子类时,装饰器模式可以帮助你减少类的数量。
- 需要组合多个功能: 当你需要组合多个功能或行为时,装饰器模式能够轻松实现。
2.9.6 与其他模式对比
- 与组合模式的区别: 装饰器模式用于在不修改对象结构的环境下动态添加功能,而组合模式用于表示部分-团体的层次结构。
- 与代理模式的区别: 代理模式用于控制对对象的访问,而装饰器模式用于在不改变对象的接口的环境下动态添加功能。
装饰器模式提供了一种灵活的方式来扩展对象的功能,使得开发者可以在不影响已有代码的环境下,轻松地添加新的行为或属性。
2.10 外观模式(Facade Pattern)
2.10.1 定义
外观模式通过为复杂的子体系提供一个简化的接口,使得客户端可以通过这个接口访问子体系的功能,而不需要直接与子体系的多个组件举行交互。外观模式可以隐藏子体系的复杂性,并且使得子体系与客户端的耦合度低落。
2.10.2 结构
外观模式的结构包罗以下角色:
- 外观(Facade): 提供一个简化的接口,封装子体系的复杂性,供客户端调用。
- 子体系(Subsystem Classes): 子体系中的多个类,它们实现了子体系的实际功能。子体系可以有多个类,客户端可以直接访问它们,但通过外观类访问会更简单。
UML 类图
- +-------------------+
- | Client |
- +-------------------+
- ^
- |
- +-------------------+ +-------------------+ +-------------------+
- | SubsystemA | ----> | Facade | <---- | SubsystemC |
- +-------------------+ +-------------------+ +-------------------+
- ^ ^
- | |
- +-------------------+ +-------------------+
- | SubsystemB | | SubsystemD |
- +-------------------+ +-------------------+
复制代码 2.10.3 示例代码
以下是一个实现外观模式的简单示例。在这个示例中,我们构建了一个家庭影院体系,客户端通过外观类 HomeTheaterFacade 来控制各个子体系,如电视、音响、灯光等。
子体系类
- // 子系统类:电视
- public class Television
- {
- public void On()
- {
- Console.WriteLine("Television is on.");
- }
- public void Off()
- {
- Console.WriteLine("Television is off.");
- }
- }
- // 子系统类:音响
- public class SoundSystem
- {
- public void On()
- {
- Console.WriteLine("Sound system is on.");
- }
- public void Off()
- {
- Console.WriteLine("Sound system is off.");
- }
- public void SetVolume(int volume)
- {
- Console.WriteLine($"Sound system volume set to {volume}.");
- }
- }
- // 子系统类:灯光
- public class Lights
- {
- public void Dim(int level)
- {
- Console.WriteLine($"Lights dimmed to {level}%.");
- }
- public void On()
- {
- Console.WriteLine("Lights are on.");
- }
- }
复制代码 外观类
- // 外观类:家庭影院
- public class HomeTheaterFacade
- {
- private readonly Television _television;
- private readonly SoundSystem _soundSystem;
- private readonly Lights _lights;
- public HomeTheaterFacade(Television television, SoundSystem soundSystem, Lights lights)
- {
- _television = television;
- _soundSystem = soundSystem;
- _lights = lights;
- }
- public void WatchMovie()
- {
- Console.WriteLine("Get ready to watch a movie...");
- _lights.Dim(30);
- _television.On();
- _soundSystem.On();
- _soundSystem.SetVolume(5);
- }
- public void EndMovie()
- {
- Console.WriteLine("Shutting movie theater down...");
- _television.Off();
- _soundSystem.Off();
- _lights.On();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建子系统对象
- Television television = new Television();
- SoundSystem soundSystem = new SoundSystem();
- Lights lights = new Lights();
- // 创建外观对象
- HomeTheaterFacade homeTheater = new HomeTheaterFacade(television, soundSystem, lights);
- // 使用外观模式控制子系统
- homeTheater.WatchMovie();
- Console.WriteLine("\nMovie is running...\n");
- homeTheater.EndMovie();
- }
- }
复制代码 在这个例子中:
- Television、SoundSystem、Lights 是子体系类,提供了各自的功能。
- HomeTheaterFacade 是外观类,它将各个子体系的操作封装成了 WatchMovie() 和 EndMovie() 两个简单的方法,客户端只需调用这些方法即可控制整个家庭影院体系。
- 客户端 Program 通过 HomeTheaterFacade 类来控制整个家庭影院的各个设备,而不需要直接与每个设备交互。
2.10.4 特点
- 长处:
- 简化接口: 提供一个简化的接口来访问复杂的子体系,减少客户端与子体系之间的耦合。
- 隐藏子体系的复杂性: 客户端不需要相识子体系的内部结构,只需要与外观类交互即可。
- 减少依赖: 客户端与子体系之间的依赖关系减少,假如子体系发生变化,只需修改外观类,而不需要修改客户端代码。
- 缺点:
- 不符合开闭原则: 假如要扩展外观类的功能,大概需要修改外观类的代码,从而违背开闭原则。
- 潜伏性能题目: 外观模式大概会因为封装了大量子体系的调用,而引入一定的性能开销。
2.10.5 适用场景
- 简化复杂体系的使用: 当体系内部结构复杂,客户端盼望能够通过简单的接口使用体系时,使用外观模式非常合适。
- 解耦客户端与子体系: 当你盼望减少客户端与多个子体系之间的依赖关系时,可以使用外观模式。
- 构建库或框架: 当你构建一个复杂库或框架,并盼望提供一个简洁的接口供外部使用时,外观模式可以帮助你计划这个接口。
2.10.6 与其他模式的关系
- 与适配器模式的区别: 适配器模式是为了将一个接口转换为客户端期望的接口,而外观模式是为了提供一个简化的接口来使用复杂体系。适配器模式专注于接口兼容性,而外观模式专注于简化体系的使用。
- 与桥接模式的区别: 桥接模式用于将抽象与实现分离,使得它们可以独立变化,而外观模式关注于简化接口。
- 与单例模式的结合: 在某些环境下,外观类可以计划成单例,以确保客户端使用的是同一个外观对象。
外观模式通过提供一个统一和简化的接口来隐藏体系的复杂性,使得客户端能够更轻松地使用体系,同时保持体系内部结构的灵活性和可扩展性。
2.11 享元模式(Flyweight Pattern)
2.11.1 定义
享元模式的焦点头脑是将对象的状态分为内部状态(可以共享的部分)和外部状态(不能共享的部分)。通过共享相同的内部状态,减少内存的重复占用,从而实现体系的资源优化。
2.11.2 结构
享元模式的结构包罗以下角色:
- 享元(Flyweight): 定义享元对象的接口,通过外部状态完成享元对象的操作。
- 具体享元(Concrete Flyweight): 实现享元接口,并存储可以共享的内部状态。
- 非共享具体享元(Unshared Concrete Flyweight): 不可共享的享元类,通常是享元对象的组合。
- 享元工厂(Flyweight Factory): 创建并管理享元对象,确保合理地共享对象。
- 客户端(Client): 维护对所有享元对象的引用,并且需要将享元对象的外部状态通报给享元对象。
UML 类图
- +-------------------+
- | IFlyweight |
- +-------------------+
- | + Operation() |
- +-------------------+
- ^
- |
- +-----------------------+
- | ConcreteFlyweight |
- +-----------------------+
- | - property1 | // 共享的内部状态
- | - property2 | // 共享的内部状态
- | + Operation() |
- +-----------------------+
- +-------------------+
- | FlyweightFactory |
- +-------------------+
- | - flyweights |
- | + Operation() |
- +-------------------+
- +---------------------------+
- | UnsharedConcreteFlyweight |
- +---------------------------+
- | - property1 | // 不共享的外部状态
- | - property2 | // 不共享的外部状态
- | - flyweight | // 组合享元
- | + Operation() |
- +---------------------------+
复制代码 2.11.3 示例代码
2.11.3.1 文字处理器
以下是一个实现享元模式的简单示例。在这个示例中,我们模仿了一个文字处理器,其中的字符对象可以被共享,以减少内存的占用。
享元接口
- // 享元接口
- public interface ICharacter
- {
- void Display(int fontSize);
- }
复制代码 具体享元类
- // 具体享元类:字符
- public class Character : ICharacter
- {
- private readonly char _symbol; // 内部状态(共享部分)
- public Character(char symbol)
- {
- _symbol = symbol;
- }
- public void Display(int fontSize)
- {
- Console.WriteLine($"Character: {_symbol}, Font size: {fontSize}");
- }
- }
复制代码 享元工厂类
- // 享元工厂类
- public class CharacterFactory
- {
- private readonly Dictionary<char, ICharacter> _characters = new Dictionary<char, ICharacter>();
- public ICharacter GetCharacter(char symbol)
- {
- if (!_characters.ContainsKey(symbol))
- {
- _characters[symbol] = new Character(symbol); // 创建新的享元对象
- }
- return _characters[symbol]; // 返回共享的享元对象
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- CharacterFactory factory = new CharacterFactory();
- // 获取并显示字符对象
- ICharacter a = factory.GetCharacter('A');
- a.Display(12);
- ICharacter b = factory.GetCharacter('B');
- b.Display(14);
- ICharacter a2 = factory.GetCharacter('A');
- a2.Display(18);
- // 检查两个 'A' 字符是否为同一个实例
- Console.WriteLine($"Is 'A' and 'A2' the same instance? {ReferenceEquals(a, a2)}");
- }
- }
复制代码 在这个例子中:
- ICharacter 是享元接口,定义了 Display() 方法,用于体现字符及其巨细。
- Character 是具体享元类,存储了共享的字符符号 _symbol,并在 Display() 方法中使用外部状态(字体巨细)。
- CharacterFactory 是享元工厂类,负责创建和管理 Character 对象,并确保相同的字符符号只创建一个实例。
- 客户端代码通过工厂获取字符对象,并在不同的字体巨细下体现它们,同时查抄同一字符是否被共享。
2.11.3.2 棋子与棋盘的实现示例
享元接口
- // 享元接口:棋子
- public interface IChessPiece
- {
- void Display(int x, int y);
- }
复制代码 具体享元类
- // 具体享元类:具体的棋子,如"黑车"或"白马"
- public class ChessPiece : IChessPiece
- {
- private readonly string _color; // 内部状态(共享部分)
- private readonly string _type; // 内部状态(共享部分)
- public ChessPiece(string color, string type)
- {
- _color = color;
- _type = type;
- }
- public void Display(int x, int y)
- {
- Console.WriteLine($"Chess Piece: {_color} {_type}, Position: ({x}, {y})");
- }
- }
复制代码 享元工厂类
- // 享元工厂类:负责管理棋子对象
- public class ChessPieceFactory
- {
- private readonly Dictionary<string, IChessPiece> _pieces = new Dictionary<string, IChessPiece>();
- public IChessPiece GetChessPiece(string color, string type)
- {
- string key = color + "_" + type;
- if (!_pieces.ContainsKey(key))
- {
- _pieces[key] = new ChessPiece(color, type);
- }
- return _pieces[key];
- }
- }
复制代码 非共享具体享元类
- // 非共享具体享元类:棋盘上的棋子
- public class ChessBoardPosition
- {
- private readonly int _x; // 外部状态
- private readonly int _y; // 外部状态
- private readonly IChessPiece _chessPiece; // 共享享元
- public ChessBoardPosition(int x, int y, IChessPiece chessPiece)
- {
- _x = x;
- _y = y;
- _chessPiece = chessPiece;
- }
- public void Display()
- {
- _chessPiece.Display(_x, _y);
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- ChessPieceFactory factory = new ChessPieceFactory();
- // 获取棋子,并在棋盘上设置位置
- ChessBoardPosition position1 = new ChessBoardPosition(1, 1, factory.GetChessPiece("Black", "Rook"));
- ChessBoardPosition position2 = new ChessBoardPosition(1, 2, factory.GetChessPiece("Black", "Knight"));
- ChessBoardPosition position3 = new ChessBoardPosition(1, 3, factory.GetChessPiece("Black", "Bishop"));
- ChessBoardPosition position4 = new ChessBoardPosition(1, 1, factory.GetChessPiece("White", "Pawn"));
- // 显示棋盘上的棋子
- position1.Display();
- position2.Display();
- position3.Display();
- position4.Display();
- }
- }
复制代码 在这个例子中:
- 享元接口 IChessPiece: 定义了体现棋子的方法 Display(),要求提供棋子的位置信息。
- 具体享元类 ChessPiece: 实现了 IChessPiece 接口,包罗了棋子的颜色和范例,这些是共享的内部状态。
- 享元工厂类 ChessPieceFactory: 负责创建和管理享元对象(棋子),确保同种颜色和范例的棋子只创建一个实例。
- 非共享具体享元类 ChessBoardPosition: 代表棋盘上的每一个棋子位置,包罗棋子在棋盘上的位置坐标 _x 和 _y,这些黑白共享的外部状态。每个位置持有一个共享的棋子对象。
2.11.4 特点
- 长处:
- 减少内存占用: 通过共享对象,显著减少了体系中的内存消耗,特别适用于大量相似对象的场景。
- 进步性能: 减少了创建对象的开销,特别是在对象创建成本高昂的环境下。
- 缺点:
- 增加复杂性: 引入共享机制后,代码的复杂性增加,需要管理外部状态和内部状态。
- 非线程安全: 享元对象在多线程环境下大概会引发线程安全题目,需要审慎处理。
2.11.5 适用场景
- 需要大量细粒度对象: 当体系中需要创建大量相似对象时,可以考虑使用享元模式来减少内存消耗。
- 外部状态可分离: 当对象的状态可以分为内部状态和外部状态,且内部状态可以共享时,享元模式非常得当。
2.11.6 与其他模式的关系
- 与单例模式的区别: 单例模式确保一个类只有一个实例,而享元模式允许多个实例共享内部状态。
- 与工厂方法模式的区别: 工厂方法模式负责创建对象,而享元模式则关注共享对象的管理。
享元模式通过共享对象的内部状态,有效地减少了内存的占用,是优化体系性能的一种有效手段,特别是在需要大量相似对象的环境下。
2.12 代理模式(Proxy Pattern)
2.12.1 定义
代理模式通过创建一个代理对象来控制对另一个对象的访问。代理对象具有与原对象相同的接口,客户端可以通过代理对象访问实际的服务对象。代理对象可以在不影响客户端的环境下,对请求举行预处理或后处理。
2.12.2 结构
代理模式的结构包罗以下角色:
- Subject(抽象主题): 定义了代理类和真实类的公共接口。
- RealSubject(真实主题): 实现了抽象主题的接口,是代理所代表的真实对象。
- Proxy(代理): 实现了抽象主题接口,并持有一个真实主题对象的引用。代理对象可以在调用真实对象的操作前后执行一些操作。
UML 类图
- +-----------------+
- | Subject | <--------- 抽象主题
- +-----------------+ |
- | + Request() | |
- +-----------------+ |
- ^ |
- | |
- +-----------------+ +-----------------+
- | Proxy | | RealSubject |
- +-----------------+ +-----------------+
- | - realSubject: | ------- | + Request() |
- | RealSubject | +-----------------+
- | + Request() |
- +-----------------+
复制代码 2.12.3 示例代码
假设我们有一个需要访问的图像文件对象。图像文件大概很大,所以我们盼望在图像真正需要体现时才加载它。在这种环境下,我们可以使用代理模式。
抽象主题接口
- // 抽象主题
- public interface IImage
- {
- void Display();
- }
复制代码 真实主题类
- // 真实主题类:真实的图像文件
- public class RealImage : IImage
- {
- private readonly string _filename;
- public RealImage(string filename)
- {
- _filename = filename;
- LoadImageFromDisk(); // 模拟加载图像文件
- }
- private void LoadImageFromDisk()
- {
- Console.WriteLine($"Loading image from disk: {_filename}");
- }
- public void Display()
- {
- Console.WriteLine($"Displaying image: {_filename}");
- }
- }
复制代码 代理类
- // 代理类:代理图像
- public class ProxyImage : IImage
- {
- private RealImage _realImage;
- private readonly string _filename;
- public ProxyImage(string filename)
- {
- _filename = filename;
- }
- public void Display()
- {
- if (_realImage == null)
- {
- _realImage = new RealImage(_filename); // 延迟加载
- }
- _realImage.Display();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- IImage image = new ProxyImage("test_image.jpg");
- // 图像第一次显示时,代理对象会加载真实图像
- image.Display();
- // 图像再次显示时,不需要再次加载
- image.Display();
- }
- }
复制代码 在这个例子中:
- 抽象主题 IImage: 定义了 Display() 方法,所有图像对象都必须实现这个方法。
- 真实主题类 RealImage: 实现了 IImage 接口,并在构造函数中模仿加载图像文件。Display() 方法体现图像。
- 代理类 ProxyImage: 同样实现了 IImage 接口,持有一个 RealImage 对象的引用。只有在需要时,才会加载真实的图像文件,执行耽误加载。
运行效果
运行上述代码,你会看到以下输出:
- Loading image from disk: test_image.jpg
- Displaying image: test_image.jpg
- Displaying image: test_image.jpg
复制代码 在这个例子中,图像在第一次调用 Display() 时才会加载,之后的调用直接体现图像,无需再次加载。这就是代理模式的耽误加载(Lazy Loading)应用。
2.12.4 特点
- 长处:
- 控制对象访问: 可以在不影响客户端的环境下控制对目标对象的访问。
- 耽误加载: 通过代理可以实现对象的耽误初始化,节省资源。
- 权限控制: 可以通过代理实现对敏感对象的访问控制。
- 缺点:
- 增加复杂性: 增加了类的数量和体系的复杂性。
- 大概引入性能开销: 代理模式大概会引入额外的处理逻辑,导致性能开销。
2.12.5 适用场景
- 需要耽误加载的场景: 当对象的创建成本较高且不经常使用时。
- 远程对象访问: 通过网络访问远程对象时,可以使用代理来隐藏复杂性。
- 控制访问权限: 需要对对象的访问举行权限控制时。
- 智能引用代理: 在访问对象时附加一些额外操作,如引用计数、日记记录等。
2.12.6 与其他模式的关系
- 装饰器模式: 代理和装饰器都通过代理控制对象,但装饰器用于添加行为,而代理用于控制访问。
- 适配器模式: 适配器模式改变接口以适配另一体系,而代理模式不改变接口,只是控制访问。
代理模式在计划复杂体系时提供了非常灵活的对象管理方式,通过合理使用代理,可以有效地提升体系的性能和安全性。
2.13 责任链模式(Chain of Responsibility Pattern)
2.13.1 定义
责任链模式的焦点头脑是将请求沿着处理者链通报,直到其中一个处理者处理这个请求。这种模式的一个重要特性是:请求的发送者并不知道哪个对象会最终处理请求,体系中的处理者对象也无需知道其他处理者的结构,处理者之间的解耦进步了体系的灵活性。
2.13.2 结构
责任链模式包罗以下角色:
- 抽象处理者(Handler): 定义一个处理请求的接口,并且实现链中下一个处理者的引用。
- 具体处理者(ConcreteHandler): 具体实现抽象处理者,处理请求或者将请求通报给链中的下一个处理者。
- 客户端(Client): 负责将请求发送到链中的第一个处理者,通常不关心请求的最终处理者是谁。
UML 类图
- +-----------------------------------+
- | Handler | <----- 抽象处理者
- +-----------------------------------+
- | - next: Handler | <----- 链中下一个处理者的引用
- | + SetNext(handler: Handler) |
- | + HandleRequest(request: Request) |
- +-----------------------------------+
- ^
- |
- +-----------------------------------+ +-----------------------------------+
- | ConcreteHandler1 | | ConcreteHandler2 |
- +-----------------------------------+ +-----------------------------------+
- | + HandleRequest(request: Request) | | + HandleRequest(request: Request) |
- +-----------------------------------+ +-----------------------------------+
复制代码 2.13.3 示例代码
假设我们有一个支持多级审批的体系,每个级别的审批员处理不同级别的请求。请求可以从一个审批员通报到下一个审批员,直到某个审批员处理了请求或者请求被拒绝。
抽象处理者
- // 抽象处理者
- public abstract class Approver
- {
- protected Approver _nextApprover;
- public void SetNext(Approver nextApprover)
- {
- _nextApprover = nextApprover;
- }
- public abstract void ProcessRequest(PurchaseRequest request);
- }
复制代码 具体处理者
- // 具体处理者1:经理
- public class Manager : Approver
- {
- public override void ProcessRequest(PurchaseRequest request)
- {
- if (request.Amount < 10000)
- {
- Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
- }
- else if (_nextApprover != null)
- {
- _nextApprover.ProcessRequest(request);
- }
- }
- }
- // 具体处理者2:总监
- public class Director : Approver
- {
- public override void ProcessRequest(PurchaseRequest request)
- {
- if (request.Amount < 25000)
- {
- Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
- }
- else if (_nextApprover != null)
- {
- _nextApprover.ProcessRequest(request);
- }
- }
- }
- // 具体处理者3:副总裁
- public class VicePresident : Approver
- {
- public override void ProcessRequest(PurchaseRequest request)
- {
- if (request.Amount < 50000)
- {
- Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
- }
- else if (_nextApprover != null)
- {
- _nextApprover.ProcessRequest(request);
- }
- }
- }
- // 具体处理者4:总裁
- public class President : Approver
- {
- public override void ProcessRequest(PurchaseRequest request)
- {
- if (request.Amount < 100000)
- {
- Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
- }
- else
- {
- Console.WriteLine($"Request# {request.Number} requires an executive meeting!");
- }
- }
- }
复制代码 请求类
- // 请求类:采购请求
- public class PurchaseRequest
- {
- public int Number { get; }
- public double Amount { get; }
- public string Purpose { get; }
- public PurchaseRequest(int number, double amount, string purpose)
- {
- Number = number;
- Amount = amount;
- Purpose = purpose;
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建处理者对象
- Approver manager = new Manager();
- Approver director = new Director();
- Approver vp = new VicePresident();
- Approver president = new President();
- // 设置责任链
- manager.SetNext(director);
- director.SetNext(vp);
- vp.SetNext(president);
- // 创建多个采购请求
- PurchaseRequest request1 = new PurchaseRequest(1, 5000, "Buy supplies");
- PurchaseRequest request2 = new PurchaseRequest(2, 20000, "Buy projectors");
- PurchaseRequest request3 = new PurchaseRequest(3, 35000, "Buy laptops");
- PurchaseRequest request4 = new PurchaseRequest(4, 90000, "Buy servers");
- // 处理请求
- manager.ProcessRequest(request1);
- manager.ProcessRequest(request2);
- manager.ProcessRequest(request3);
- manager.ProcessRequest(request4);
- }
- }
复制代码 运行效果
- Manager approved request# 1
- Director approved request# 2
- VicePresident approved request# 3
- President approved request# 4
复制代码 在这个例子中,Manager、Director、VicePresident 和 President 这四个处理者构成了一个责任链。每个处理者都查抄请求的金额,并决定是否可以处理请求,假如不能处理则将请求通报给下一个处理者。
2.13.5 特点
- 长处:
- 低落耦合度: 客户端不需要知道哪个处理者会最终处理请求,这使得体系更灵活、可扩展。
- 增加灵活性: 可以动态地添加或移除处理者,乃至可以动态调整处理者链的顺序。
- 职责分离: 每个处理者只关注自己能够处理的那部分职责,符合单一职责原则。
- 缺点:
- 不保证请求被处理: 假如链上的处理者都不能处理请求,那么请求大概会被扬弃。
- 大概影响性能: 假如责任链过长,或者某些处理者链路中的节点过多,大概导致请求处理的耽误。
2.13.6 适用场景
- 多个对象可以处理同一请求: 好比事件处理体系、请求处理链。
- 请求的处理需要动态指定: 好比审批流程中的不同审批级别。
- 需要在不明确接收者的环境下发送请求: 客户端不需要知道具体是哪个对象处理了请求。
2.13.7 总结
责任链模式通过将处理者对象串联成一条链,使请求能够沿着链通报,直到被某个处理者处理。这种模式的灵活性使其适用于多种场景,尤其是那些需要动态指定请求处理者或处理者职责大概发生变化的场合。
2.14 下令模式(Command Pattern)
2.14.1 定义
下令模式的焦点头脑是将“请求”封装为对象,并将请求的执行和请求的具体操作细节解耦。这样可以在不同的时间点、不同的环境下执行请求,还可以通过下令对象的统一接口来记录日记、撤销操作等。
2.14.2 结构
下令模式包罗以下角色:
- 下令接口(Command): 声明执行下令的接口。
- 具体下令类(ConcreteCommand): 实现下令接口,定义请求的具体操作。
- 接收者(Receiver): 知道怎样执行与请求相关的操作。
- 调用者(Invoker): 负责调用下令对象执行请求。它并不直接操作接收者。
- 客户端(Client): 创建具体的下令对象,并设置它们的接收者。
UML 类图
- +-------------------+
- | Command | <----- 命令接口
- +-------------------+
- | + Execute() |
- +-------------------+
- ^
- |
- +-----------------------+ +----------------------+
- | ConcreteCommand1 | | ConcreteCommand2 |
- +-----------------------+ +----------------------+
- | - receiver: Receiver | | - receiver: Receiver |
- | + Execute() | | + Execute() |
- +-----------------------+ +----------------------+
- +-------------------+
- | Receiver | <----- 接收者
- +-------------------+
- | + Action() |
- +-------------------+
- +-----------------------------+
- | Invoker | <----- 调用者
- +-----------------------------+
- | - command: Command |
- | + SetCommand(cmd: Command) |
- | + ExecuteCommand() |
- +-----------------------------+
复制代码 2.14.3 示例代码
假设我们要实现一个简单的家电控制体系,可以用下令模式来计划将家电的操作(如开灯、关灯等)封装为下令对象。
下令接口
- // 命令接口
- public interface ICommand
- {
- void Execute();
- void Undo();
- }
复制代码 接收者
- // 接收者:灯
- public class Light
- {
- public void TurnOn()
- {
- Console.WriteLine("The light is on");
- }
- public void TurnOff()
- {
- Console.WriteLine("The light is off");
- }
- }
复制代码 具体下令类
- // 具体命令类:打开灯
- public class LightOnCommand : ICommand
- {
- private readonly Light _light;
- public LightOnCommand(Light light)
- {
- _light = light;
- }
- public void Execute()
- {
- _light.TurnOn();
- }
- public void Undo()
- {
- _light.TurnOff(); // 撤销打开灯,实际上是关闭灯
- }
- }
- // 具体命令类:关闭灯
- public class LightOffCommand : ICommand
- {
- private readonly Light _light;
- public LightOffCommand(Light light)
- {
- _light = light;
- }
- public void Execute()
- {
- _light.TurnOff();
- }
- public void Undo()
- {
- _light.TurnOn(); // 撤销关闭灯,实际上是打开灯
- }
- }
复制代码 调用者
- public class RemoteControl
- {
- private ICommand _command;
- private readonly Stack<ICommand> _history = new Stack<ICommand>();
- private readonly Stack<ICommand> _redoStack = new Stack<ICommand>();
- public void SetCommand(ICommand command)
- {
- _command = command;
- }
- public void PressButton()
- {
- _command.Execute();
- _history.Push(_command); // 保存执行的命令
- _redoStack.Clear(); // 清空恢复栈,因为有新操作了
- }
- public void PressUndo()
- {
- if (_history.Count > 0)
- {
- ICommand lastCommand = _history.Pop();
- lastCommand.Undo();
- _redoStack.Push(lastCommand); // 保存到恢复栈
- }
- }
- public void PressRedo()
- {
- if (_redoStack.Count > 0)
- {
- ICommand lastUndoneCommand = _redoStack.Pop();
- lastUndoneCommand.Execute();
- _history.Push(lastUndoneCommand); // 重新保存到历史栈
- }
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建接收者
- Light livingRoomLight = new Light();
- // 创建命令对象
- ICommand lightOn = new LightOnCommand(livingRoomLight);
- ICommand lightOff = new LightOffCommand(livingRoomLight);
- // 创建调用者并设置命令
- RemoteControl remote = new RemoteControl();
- // 打开灯
- remote.SetCommand(lightOn);
- remote.PressButton();
- // 关闭灯
- remote.SetCommand(lightOff);
- remote.PressButton();
- // 撤销关闭灯操作(即再次打开灯)
- remote.PressUndo();
- // 撤销打开灯操作(即再次关闭灯)
- remote.PressUndo();
- // 恢复关闭灯操作(即再次打开灯)
- remote.PressRedo();
- }
- }
复制代码 运行效果
- The light is on
- The light is off
复制代码 在这个例子中,LightOnCommand 和 LightOffCommand 这两个具体下令类封装了开灯和关灯的操作。RemoteControl 是调用者,通过调用 SetCommand 方法将具体下令对象通报给它,并通过 PressButton 方法执行下令。
2.14.4 特点
- 长处:
- 解耦请求发送者和接收者: 发送者只需要知道下令接口,而不需要相识具体实现。
- 支持撤销操作: 可以实现下令的撤销和恢复功能。
- 支持宏下令: 可以将多个下令组合成一个宏下令,顺序执行。
- 支持请求排队和日记记录: 通过生存下令对象,可以将请求生存到队列中或日记中,便于后续操作。
- 缺点:
- 类数量增加: 每个具体下令都需要定义一个类,大概导致类的数量增加。
- 体系复杂性增加: 封装请求为对象虽然增加了灵活性,但也增加了体系的复杂性。
2.14.5 适用场景
- 需要参数化对象的操作: 客户端可以在运行时选择并设置要执行的下令。
- 需要支持撤销和恢复操作: 体系需要支持撤销功能时,可以使用下令模式生存操作的历史记录。
- 需要在不同时间点执行操作: 可以将下令放入队列中,耽误执行或按顺序执行。
- 需要支持宏下令: 需要将多个操作组合成一个下令时,可以使用下令模式。
2.14.6 总结
下令模式通过将操作封装为独立的下令对象,实现了请求发送者与接收者的解耦。它为体系增加了灵活性,尤其是在支持撤销、恢复、宏下令和请求排队等功能时非常有效。然而,下令模式的使用也大概导致类的数量增加,体系的复杂性增加,因此在计划时需要衡量使用。
2.15 解释器模式(Interpreter Pattern)
2.15.1 定义
解释器模式通过定义一种语言的语法规则,使用这些规则剖析和执行语言中的语句。这种模式通常用于那些具有简单语法和小型下令集的范畴特定语言(DSL)中。
2.15.2 结构
解释器模式包罗以下角色:
- 抽象表达式(AbstractExpression): 声明解释操作,接口或抽象类。
- 闭幕符表达式(TerminalExpression): 实现与语法规则相关的操作,处理语法中的闭幕符。
- 非闭幕符表达式(NonTerminalExpression): 表示语法规则中的非闭幕符,通常包罗一个或多个表达式。
- 上下文(Context): 包罗解释器需要的全局信息。
- 客户端(Client): 构建或解释语法规则,并使用解释器来解释语句。
UML 类图
- +---------------------------------------+
- | AbstractExpression | <----- 抽象表达式
- +---------------------------------------+
- | + Interpret(context: Context): void |
- +---------------------------------------+
- ^
- |
- +---------------------------------------+ +---------------------------------------+
- | TerminalExpression | | NonTerminalExpression |
- +---------------------------------------+ +---------------------------------------+
- | + Interpret(context: Context): void | | + Interpret(context: Context): void |
- +---------------------------------------+ +---------------------------------------+
- +-------------------+
- | Context | <----- 上下文
- +-------------------+
- | + GetInfo(): ... |
- +-------------------+
复制代码 2.15.3 示例代码
假设我们要实现一个简单的数学表达式解释器,可以剖析和盘算简单的加法和减法运算表达式。
抽象表达式
- // 抽象表达式
- public interface IExpression
- {
- int Interpret();
- }
复制代码 闭幕符表达式
- // 终结符表达式:数字
- public class NumberExpression : IExpression
- {
- private readonly int _number;
- public NumberExpression(int number)
- {
- _number = number;
- }
- public int Interpret()
- {
- return _number;
- }
- }
复制代码 非闭幕符表达式
- // 非终结符表达式:加法
- public class AddExpression : IExpression
- {
- private readonly IExpression _leftExpression;
- private readonly IExpression _rightExpression;
- public AddExpression(IExpression leftExpression, IExpression rightExpression)
- {
- _leftExpression = leftExpression;
- _rightExpression = rightExpression;
- }
- public int Interpret()
- {
- return _leftExpression.Interpret() + _rightExpression.Interpret();
- }
- }
- // 非终结符表达式:减法
- public class SubtractExpression : IExpression
- {
- private readonly IExpression _leftExpression;
- private readonly IExpression _rightExpression;
- public SubtractExpression(IExpression leftExpression, IExpression rightExpression)
- {
- _leftExpression = leftExpression;
- _rightExpression = rightExpression;
- }
- public int Interpret()
- {
- return _leftExpression.Interpret() - _rightExpression.Interpret();
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 构造表达式:10 + 5 - 2
- IExpression expression = new SubtractExpression(
- new AddExpression(new NumberExpression(10), new NumberExpression(5)),
- new NumberExpression(2)
- );
- // 解释并计算结果
- int result = expression.Interpret();
- Console.WriteLine($"Result: {result}");
- }
- }
复制代码 运行效果
在这个例子中,表达式 10 + 5 - 2 被构造为一个解释器树,并通过调用 Interpret 方法递归地盘算出效果。NumberExpression 是闭幕符表达式,用于表示具体的数字值,AddExpression 和 SubtractExpression 黑白闭幕符表达式,用于表示加法和减法操作。
2.15.4 特点
- 长处:
- 灵活性高: 解释器模式使得计划自定义语言变得更加轻易,通过组合不同的表达式类可以实现复杂的语法剖析。
- 可扩展性好: 新的语法规则可以通过添加新的表达式类来实现,而不需要修改现有的体系。
- 缺点:
- 性能题目: 解释器模式适用于语法规则相对简单的场景。对于复杂的语法剖析,由于要递归剖析表达式树,大概会导致性能题目。
- 类的数量增加: 每个语法规则都需要一个类来实现,大概导致类的数量急剧增加,增加了体系的复杂性。
2.15.5 适用场景
- 简单的语言解释器: 如脚本语言解释器、设置文件剖析器、规则引擎等。
- 编译器计划: 在编译器中使用解释器模式来剖析和解释源代码。
- 复杂的数据剖析: 需要解释和执行复杂的下令或数据时,使用解释器模式来剖析并执行。
2.15.6 总结
解释器模式通过定义语言的语法规则,并使用这些规则剖析和执行语句。它适用于简单的语法规则和小型语言剖析任务,但不适用于复杂的语法剖析和大规模体系。解释器模式的灵活性和扩展性使其在某些范畴特定语言的实现中非常有效。
2.16 迭代器模式(Iterator Pattern)
2.16.1 定义
迭代器模式的焦点头脑是提供一种统一的接口来遍历聚合对象中的元素,而不需要相识聚合对象的内部结构。通过这种方式,聚集和遍历算法之间解耦,遍历的方式可以更轻易地改变或扩展。
2.16.2 结构
迭代器模式包罗以下角色:
- 迭代器接口(Iterator): 定义访问和遍历元素的接口,通常包括 Next、HasNext、Current 等方法。
- 具体迭代器(ConcreteIterator): 实现迭代器接口,负责具体元素的遍历。
- 聚合接口(Aggregate): 定义创建迭代器对象的接口。
- 具体聚合类(ConcreteAggregate): 实现聚合接口,返回具体的迭代器实例。
UML 类图
- +-------------------------------+ +-------------------+
- | Aggregate | | Iterator |
- +-------------------------------+ +-------------------+
- | + CreateIterator(): Iterator | | + HasNext(): bool |
- +-------------------------------+ | + Next(): T |
- ^ | + Current(): T |
- | +-------------------+
- +-------------------------------+
- |ConcreteAggregate | +-------------------+
- +-------------------------------+ | ConcreteIterator |
- | + CreateIterator(): Iterator | | + HasNext(): bool |
- +-------------------------------+ | + Next(): T |
- | + Current(): T |
- +-------------------+
复制代码 2.16.3 示例代码
假设我们要实现一个自定义的 List 聚集,并为它提供一个迭代器来遍历其中的元素。
迭代器接口
- // 迭代器接口
- public interface IIterator<T>
- {
- bool HasNext();
- T Next();
- T Current { get; }
- }
复制代码 具体迭代器
- // 具体迭代器
- public class ListIterator<T> : IIterator<T>
- {
- private readonly List<T> _list;
- private int _position = 0;
- public ListIterator(List<T> list)
- {
- _list = list;
- }
- public bool HasNext()
- {
- return _position < _list.Count;
- }
- public T Next()
- {
- return _list[_position++];
- }
- public T Current => _list[_position];
- }
复制代码 聚合接口
- // 聚合接口
- public interface IAggregate<T>
- {
- IIterator<T> CreateIterator();
- }
复制代码 具体聚合类
- // 具体聚合类
- public class CustomList<T> : IAggregate<T>
- {
- private readonly List<T> _items = new List<T>();
- public void Add(T item)
- {
- _items.Add(item);
- }
- public IIterator<T> CreateIterator()
- {
- return new ListIterator<T>(_items);
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建聚合对象并添加元素
- CustomList<string> list = new CustomList<string>();
- list.Add("Item 1");
- list.Add("Item 2");
- list.Add("Item 3");
- // 创建迭代器并遍历元素
- IIterator<string> iterator = list.CreateIterator();
- while (iterator.HasNext())
- {
- string item = iterator.Next();
- Console.WriteLine(item);
- }
- }
- }
复制代码 运行效果
在这个例子中,我们创建了一个自定义的 CustomList 类,并为其提供了 ListIterator 作为具体的迭代器。ListIterator 实现了遍历列表元素的逻辑。客户端代码通过迭代器接口来遍历 CustomList 中的元素,而无需相识 CustomList 的内部结构。
2.16.4 特点
- 长处:
- 简化聚合类: 迭代器模式将遍历的职责从聚合类中分离出来,简化了聚合类的实现。
- 一致的接口: 迭代器模式提供了一致的接口用于遍历不同范例的聚合对象,无需关心其内部实现。
- 灵活性高: 可以自由更改迭代算法而不影响聚合类。
- 缺点:
- 开销增加: 对于非常简单的聚合类,引入迭代器模式大概会导致额外的开销和复杂性。
2.16.5 适用场景
- 需要遍历聚合对象: 当需要遍历一个聚合对象中的元素时,可以使用迭代器模式。
- 不同的遍历方式: 需要多种遍历方式或需要隐藏遍历的实现细节时,迭代器模式是一个合适的选择。
- 统一的遍历接口: 当需要为不同范例的聚合对象提供统一的遍历接口时,迭代器模式非常适用。
2.16.6 总结
迭代器模式提供了一种遍历聚合对象的标准方法,通过解耦遍历逻辑和聚合对象的实现,增强了体系的灵活性和可扩展性。该模式特别得当需要在不暴露对象内部结构的环境下对对象举行遍历的场景。
2.17 中介者模式(Mediator Pattern)
2.17.1 定义
中介者模式将体系中多个对象之间复杂的交互和依赖关系抽象为一个中介者对象,各个对象不直接引用彼此,而是通过中介者举行通信。这样做可以减少对象之间的直接依赖,从而使体系更加易于维护和扩展。
2.17.2 结构
中介者模式包罗以下角色:
- 中介者接口(Mediator): 定义一个接口用于与各同事对象通信。
- 具体中介者(ConcreteMediator): 实现中介者接口,协调各同事对象之间的交互。
- 同事类(Colleague): 每个同事类都知道中介者对象,并且在需要与其他同事通信时,都会通过中介者。
UML 类图
- +---------------------------------------------------+
- | Mediator |
- +---------------------------------------------------+
- | + Notify(sender: Colleague, event: string): void |
- +---------------------------------------------------+
- ^
- |
- +---------------------------------------------------+
- |ConcreteMediator |
- +---------------------------------------------------+
- | + Notify(sender: Colleague, event: string): void |
- | + RegisterColleague(colleague: Colleague): void |
- +---------------------------------------------------+
- +-------------------------------------------+
- | Colleague |
- +-------------------------------------------+
- | + SetMediator(mediator: Mediator): void |
- | + Send(event: string): void |
- +-------------------------------------------+
- ^ ^
- | |
- +-------------------------------+ +-------------------------------+
- | ColleagueA | | ColleagueB |
- +-------------------------------+ +-------------------------------+
- | + Send(event: string): void | | + Send(event: string): void |
- +-------------------------------+ +-------------------------------+
复制代码 2.17.3 示例代码
假设我们要实现一个谈天室体系,其中用户可以通过谈天室中介者互相发送消息。
中介者接口
- // 中介者接口
- public interface IChatMediator
- {
- void SendMessage(string message, User user);
- void RegisterUser(User user);
- }
复制代码 具体中介者
- // 具体中介者
- public class ChatMediator : IChatMediator
- {
- private readonly List<User> _users = new List<User>();
- public void RegisterUser(User user)
- {
- _users.Add(user);
- }
- public void SendMessage(string message, User sender)
- {
- foreach (var user in _users)
- {
- // 不要发给自己
- if (user != sender)
- {
- user.Receive(message);
- }
- }
- }
- }
复制代码 同事类
- // 同事类
- public abstract class User
- {
- protected IChatMediator _mediator;
- protected string _name;
- public User(IChatMediator mediator, string name)
- {
- _mediator = mediator;
- _name = name;
- }
- public abstract void Send(string message);
- public abstract void Receive(string message);
- }
- // 具体同事类
- public class ConcreteUser : User
- {
- public ConcreteUser(IChatMediator mediator, string name) : base(mediator, name)
- {
- }
- public override void Send(string message)
- {
- Console.WriteLine($"{_name} sends: {message}");
- _mediator.SendMessage(message, this);
- }
- public override void Receive(string message)
- {
- Console.WriteLine($"{_name} receives: {message}");
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- IChatMediator chatMediator = new ChatMediator();
- User user1 = new ConcreteUser(chatMediator, "User1");
- User user2 = new ConcreteUser(chatMediator, "User2");
- User user3 = new ConcreteUser(chatMediator, "User3");
- chatMediator.RegisterUser(user1);
- chatMediator.RegisterUser(user2);
- chatMediator.RegisterUser(user3);
- user1.Send("Hello, everyone!");
- }
- }
复制代码 运行效果
- User1 sends: Hello, everyone!
- User2 receives: Hello, everyone!
- User3 receives: Hello, everyone!
复制代码 在这个例子中,ChatMediator 是中介者,负责协调 User 对象之间的通信。用户通过调用中介者的 SendMessage 方法来发送消息,中介者会将消息转发给其他用户。
2.17.4 特点
- 长处:
- 低落耦合度: 中介者模式减少了对象之间的直接依赖,各个对象不需要知道其他对象的存在,只需要与中介者交互。
- 增强可维护性: 对象之间的交互逻辑会合在中介者中,使得修改交互逻辑变得更加轻易,而无需修改各个对象。
- 易于扩展: 可以通过增加新的中介者或同事类来扩展体系,而不会影响现有代码。
- 缺点:
- 中介者复杂性: 随着体系的复杂度增加,中介者大概变得巨大且复杂,难以维护。
- 大概引入单点故障: 中介者作为通信的中央,假如中介者失败,整个体系的通信大概会中断。
2.17.5 适用场景
- 复杂的对象交互: 当对象之间存在复杂的交互逻辑且相互依赖时,中介者模式可以简化这些交互。
- 体系易于扩展: 当需要在体系中添加新的交互对象而不盼望影响现有代码时,中介者模式非常合适。
- 分离体系的职责: 中介者模式可以帮助将不同对象的职责清晰地分离开来,从而减少耦合。
2.17.6 总结
中介者模式通过引入中介者对象来协调多个对象之间的交互,减少了对象之间的耦合度,并使体系更具可维护性和可扩展性。尽管中介者模式能够简化对象的交互逻辑,但需要注意中介者对象的复杂性管理,以避免其变得过于巨大。
2.18 备忘录模式(Memento Pattern)
2.18.1 定义
备忘录模式的焦点头脑是将对象的状态生存在一个备忘录对象中,并允许在未来的某个时刻恢复该状态。备忘录模式保证了状态的封装性,外部对象无法直接访问备忘录中的内容,从而掩护了原发器对象的内部细节。
2.18.2 结构
备忘录模式包罗以下角色:
- 原发器(Originator): 创建备忘录以记录当前状态,并可以使用备忘录恢复先前状态。
- 备忘录(Memento): 存储原发器的内部状态,备忘录对外部是不可见的,只能由原发器访问。
- 负责人(Caretaker): 负责存储和管理备忘录,但不会修改或查抄备忘录的内容。
UML 类图
- +---------------------------------------+
- | Originator |
- +---------------------------------------+
- | - state: String |
- | + CreateMemento(): Memento |
- | + SetMemento(memento: Memento): void |
- +---------------------------------------+
- |
- |
- +-----------------------+ +-----------------------+
- | Memento | | Caretaker |
- +-----------------------+ +-----------------------+
- | - state: String | | - memento: Memento |
- | + GetState(): String | +-----------------------+
- +-----------------------+
复制代码 2.18.3 示例代码
假设我们要实现一个简单的文本编辑器,它能够生存文本的状态,并在需要时撤销或恢复状态。
原发器
- // 原发器类
- public class TextEditor
- {
- private string _text;
- public void SetText(string text)
- {
- _text = text;
- }
- public string GetText()
- {
- return _text;
- }
- public Memento CreateMemento()
- {
- return new Memento(_text);
- }
- public void RestoreMemento(Memento memento)
- {
- _text = memento.GetState();
- }
- }
复制代码 备忘录
- // 备忘录类
- public class Memento
- {
- private readonly string _state;
- public Memento(string state)
- {
- _state = state;
- }
- public string GetState()
- {
- return _state;
- }
- }
复制代码 负责人
- // 负责人类
- public class Caretaker
- {
- private readonly Stack<Memento> _mementos = new Stack<Memento>();
- public void SaveMemento(Memento memento)
- {
- _mementos.Push(memento);
- }
- public Memento GetMemento()
- {
- if (_mementos.Count > 0)
- {
- return _mementos.Pop();
- }
- return null;
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- TextEditor editor = new TextEditor();
- Caretaker caretaker = new Caretaker();
- editor.SetText("Version 1");
- caretaker.SaveMemento(editor.CreateMemento());
- editor.SetText("Version 2");
- caretaker.SaveMemento(editor.CreateMemento());
- editor.SetText("Version 3");
- Console.WriteLine("Current Text: " + editor.GetText());
- editor.RestoreMemento(caretaker.GetMemento());
- Console.WriteLine("Restored Text: " + editor.GetText());
- editor.RestoreMemento(caretaker.GetMemento());
- Console.WriteLine("Restored Text: " + editor.GetText());
- }
- }
复制代码 运行效果
- Current Text: Version 3
- Restored Text: Version 2
- Restored Text: Version 1
复制代码 在这个例子中,TextEditor 是原发器,负责创建和恢复文本的状态;Memento 是备忘录,生存文本的状态;Caretaker 是负责人,管理备忘录的存储和恢复。在运行过程中,我们生存了多个文本状态,并通过恢复操作撤销了修改,返回到之前的状态。
2.18.4 特点
- 长处:
- 封装性: 备忘录模式保证了原发器状态的封装性,外部对象无法直接访问备忘录的内容。
- 状态恢复: 允许对象恢复到之前的状态,提供了实现“撤销/恢复”功能的简单方法。
- 简化复杂性: 通过将状态的存储和恢复职责分离到不同的类中,简化了复杂体系的管理。
- 缺点:
- 开销大: 假如原发器的状态占用大量资源(如内存),备忘录模式大概会导致开销较大,尤其是在频仍生存和恢复状态时。
- 管理复杂: 假如备忘录数量众多,管理这些备忘录大概变得复杂,尤其是在涉及多线程操作时。
2.18.5 适用场景
- 实现撤销/恢复功能: 当需要实现“撤销/恢复”操作时,备忘录模式非常适用。
- 需要记录历史状态: 在需要生存和恢复对象的多个历史状态的场景中,可以使用备忘录模式。
- 简化复杂对象的状态管理: 在复杂对象状态变化频仍且需要保证状态一致性的环境下,备忘录模式提供了一种有效的管理方式。
2.18.6 总结
备忘录模式通过生存对象的状态,提供了恢复该状态的机制。它通过封装状态,确保了对象内部细节的掩护,同时又允许状态的恢复。该模式非常得当用于实现“撤销/恢复”功能,并在需要管理复杂状态变动的场景中提供了很好的办理方案。
2.19 观察者模式(Observer Pattern)
2.19.1 定义
观察者模式的焦点头脑是当一个对象(被观察者)的状态改变时,所有依赖于它的对象(观察者)都会被关照并更新。这样一来,观察者模式实现了对象之间的松散耦合,使得一个对象的变化可以主动地传播到相关的对象。
2.19.2 结构
观察者模式包罗以下角色:
- 主题(Subject): 被观察的对象,维护着一组观察者对象的引用,提供注册、移除观察者的接口,并在状态发生变化时关照所有观察者。
- 观察者(Observer): 定义一个更新接口,当收到关照时举行相应的更新操作。
- 具体主题(ConcreteSubject): 具体的被观察对象,通常包罗状态,当状态发生变化时,关照所有已注册的观察者。
- 具体观察者(ConcreteObserver): 实现观察者接口,负责在状态变化时更新自身。
UML 类图
- +---------------------------+ +-------------------+
- | Subject | <------ | Observer |
- +---------------------------+ +-------------------+
- | + Attach(obs: Observer) | | + Update(): void |
- | + Detach(obs: Observer) | +-------------------+
- | + Notify(): void | ^
- +---------------------------+ |
- ^ |
- | |
- +-----------------------+ +-------------------+
- | ConcreteSubject | | ConcreteObserver |
- +-----------------------+ +-------------------+
- | - state: State | | - state: State |
- | + GetState(): State | | + Update(): void |
- | + SetState(State) | +-------------------+
- +-----------------------+
复制代码 2.19.3 示例代码
假设我们要实现一个天气站体系,天气站会记录当前的天气信息,并关照注册的体现设备(如手机应用、网站等)举行更新。
观察者接口
- // 观察者接口
- public interface IObserver
- {
- void Update(string temperature, string humidity, string pressure);
- }
复制代码 主题接口
- // 主题接口
- public interface ISubject
- {
- void RegisterObserver(IObserver observer);
- void RemoveObserver(IObserver observer);
- void NotifyObservers();
- }
复制代码 具体主题
- // 具体主题
- public class WeatherStation : ISubject
- {
- private List<IObserver> _observers;
- private string _temperature;
- private string _humidity;
- private string _pressure;
- public WeatherStation()
- {
- _observers = new List<IObserver>();
- }
- public void RegisterObserver(IObserver observer)
- {
- _observers.Add(observer);
- }
- public void RemoveObserver(IObserver observer)
- {
- _observers.Remove(observer);
- }
- public void NotifyObservers()
- {
- foreach (var observer in _observers)
- {
- observer.Update(_temperature, _humidity, _pressure);
- }
- }
- public void SetMeasurements(string temperature, string humidity, string pressure)
- {
- _temperature = temperature;
- _humidity = humidity;
- _pressure = pressure;
- NotifyObservers();
- }
- }
复制代码 具体观察者
- // 具体观察者
- public class PhoneDisplay : IObserver
- {
- private string _temperature;
- private string _humidity;
- private string _pressure;
- public void Update(string temperature, string humidity, string pressure)
- {
- _temperature = temperature;
- _humidity = humidity;
- _pressure = pressure;
- Display();
- }
- public void Display()
- {
- Console.WriteLine($"Phone Display -> Temperature: {_temperature}, Humidity: {_humidity}, Pressure: {_pressure}");
- }
- }
- public class WebDisplay : IObserver
- {
- private string _temperature;
- private string _humidity;
- private string _pressure;
- public void Update(string temperature, string humidity, string pressure)
- {
- _temperature = temperature;
- _humidity = humidity;
- _pressure = pressure;
- Display();
- }
- public void Display()
- {
- Console.WriteLine($"Web Display -> Temperature: {_temperature}, Humidity: {_humidity}, Pressure: {_pressure}");
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- WeatherStation weatherStation = new WeatherStation();
- IObserver phoneDisplay = new PhoneDisplay();
- IObserver webDisplay = new WebDisplay();
- weatherStation.RegisterObserver(phoneDisplay);
- weatherStation.RegisterObserver(webDisplay);
- weatherStation.SetMeasurements("30°C", "65%", "1013 hPa");
- weatherStation.RemoveObserver(phoneDisplay);
- weatherStation.SetMeasurements("28°C", "70%", "1012 hPa");
- }
- }
复制代码 运行效果
- Phone Display -> Temperature: 30°C, Humidity: 65%, Pressure: 1013 hPa
- Web Display -> Temperature: 30°C, Humidity: 65%, Pressure: 1013 hPa
- Web Display -> Temperature: 28°C, Humidity: 70%, Pressure: 1012 hPa
复制代码 在这个例子中,WeatherStation 是具体的主题,当天气数据发生变化时,它关照所有注册的观察者(如 PhoneDisplay 和 WebDisplay)举行更新并体现新的数据。
2.19.4 特点
- 长处:
- 松散耦合: 观察者和主题之间是松散耦合的,观察者可以独立于主题的变化而变化,增加了体系的灵活性。
- 动态更新: 观察者模式使得对象之间的通信更加灵活,可以动态添加或删除观察者,实时更新数据。
- 符合开放-封闭原则: 可以在不修改现有代码的环境下,增加新的观察者。
- 缺点:
- 关照开销: 假如有大量的观察者,关照所有观察者大概会引起开销,影响性能。
- 大概出现循环依赖: 假如观察者之间也相互依赖,大概会导致循环依赖题目,影响体系的稳固性。
2.19.5 适用场景
- 事件处理体系: 当需要对某个事件发生时,触发多个对象的相应时,观察者模式非常适用。
- 数据模子与视图同步: 在模子-视图架构中,当模子的数据变化时,需要关照视图更新体现,可以使用观察者模式。
- 广播通信: 当一个对象的状态改变需要关照多个对象时,可以使用观察者模式。
2.19.6 总结
观察者模式通过定义一对多的依赖关系,实现了对象间的松散耦合和动态通信。它允许对象主动关照相关的依赖对象并更新状态,非常得当用于事件驱动的体系和需要动态更新的场景。尽管大概会带来一定的关照开销和复杂性管理,但它依然是实现对象间动态通信的强盛工具。
2.20 状态模式(State Pattern)
2.20.1 定义
状态模式的焦点头脑是将与状态相关的行为封装在独立的状态对象中,并通过状态对象来管理对象的状态转换。这样,原始对象在其状态发生变化时,会主动切换到对应的状态对象,从而体现出不同的行为。
2.20.2 结构
状态模式包罗以下角色:
- 上下文(Context): 维护一个当前状态,并在状态发生变化时,切换到新的状态对象。上下文对象向客户端暴露接口,但行为的实现由状态对象负责。
- 状态接口(State): 定义状态的接口,声明在该状态下对象可以执行的行为。
- 具体状态(ConcreteState): 实现状态接口,定义该状态下的具体行为。当上下文处于此状态时,行为由具体状态对象执行。
UML 类图
- +-------------------+ +-----------------------------------+
- | Context | | State |
- +-------------------+ +-----------------------------------+
- | - state: State |<------| + Handle(context: Context): void |
- | + Request(): void | +-----------------------------------+
- +-------------------+ ^ ^
- | |
- | |
- +-----------------------------------+ +-----------------------------------+
- | ConcreteStateA | | ConcreteStateB |
- +-----------------------------------+ +-----------------------------------+
- | + Handle(context: Context): void | | + Handle(context: Context): void |
- +-----------------------------------+ +-----------------------------------+
复制代码 2.20.3 示例代码
假设我们要实现一个简单的电灯开关体系,电灯可以处于“开”和“关”两种状态,并且根据当前的状态来执行不同的操作。
状态接口
- // 状态接口
- public interface IState
- {
- void Handle(Context context);
- }
复制代码 具体状态类
- // 具体状态 - 开灯状态
- public class OnState : IState
- {
- public void Handle(Context context)
- {
- Console.WriteLine("The light is already ON.");
- context.SetState(new OffState());
- }
- }
- // 具体状态 - 关灯状态
- public class OffState : IState
- {
- public void Handle(Context context)
- {
- Console.WriteLine("The light is OFF. Turning it ON.");
- context.SetState(new OnState());
- }
- }
复制代码 上下文类
- // 上下文类
- public class Context
- {
- private IState _state;
- public Context(IState state)
- {
- _state = state;
- }
- public void SetState(IState state)
- {
- _state = state;
- }
- public void Request()
- {
- _state.Handle(this);
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- Context context = new Context(new OffState());
- // 初始状态为关灯状态
- context.Request(); // 关灯 -> 开灯
- // 再次请求
- context.Request(); // 开灯 -> 关灯
- // 再次请求
- context.Request(); // 关灯 -> 开灯
- }
- }
复制代码 运行效果
- The light is OFF. Turning it ON.
- The light is already ON.
- The light is OFF. Turning it ON.
复制代码 在这个例子中,Context 类维护一个当前状态,当调用 Request() 方法时,会根据当前状态执行相应的操作并切换到下一个状态。OnState 和 OffState 是两种具体的状态类,分别定义了在不同状态下的行为。
2.20.4 特点
- 长处:
- 减少复杂性: 通过将状态相关的行为封装在独立的状态对象中,状态模式消除了大量的条件分支语句,使代码更加清晰和易于维护。
- 状态转换灵活: 可以很轻易地添加、删除或修改状态对象,扩展体系的功能,而无需修改上下文类。
- 符合单一职责原则: 状态模式将与状态相关的行为封装在不同的状态类中,使得每个类只负责一种状态的行为,简化了代码的管理。
- 缺点:
- 增加类的数量: 每个状态都需要定义一个具体的状态类,当状态过多时,大概会导致类的数量急剧增加,增加体系的复杂性。
- 状态之间的依赖: 假如状态之间存在复杂的依赖关系,大概会导致状态之间的转换逻辑变得复杂,难以维护。
2.20.5 适用场景
- 对象的行为依赖于其状态: 当一个对象的行为依赖于其状态,并且它的状态会在运行时改变时,状态模式非常适用。
- 状态逻辑复杂: 当状态转换的逻辑非常复杂,或者状态之间的转换规则经常变化时,状态模式能够有效地管理这些逻辑。
- 消除条件分支: 假如代码中存在大量的条件分支语句来处理不同的状态,状态模式可以通过将状态逻辑分散到不同的状态类中来简化代码。
2.20.6 总结
状态模式通过将状态相关的行为封装在独立的状态对象中,简化了对象的状态管理逻辑。它消除了大量的条件分支语句,使代码更加清晰和易于扩展。尽管状态模式大概会增加类的数量,但它为管理复杂的状态转换逻辑提供了一种灵活且有效的办理方案。
2.21 战略模式(Strategy Pattern)
2.21.1 定义
战略模式的焦点头脑是将不同的算法或行为封装到独立的战略类中,并通过上下文类来管理和使用这些战略。客户端可以通过上下文类来动态选择使用哪种战略,而无需关心战略的具体实现细节。
2.21.2 结构
战略模式包罗以下角色:
- 上下文(Context): 维护一个战略对象的引用,供客户端使用。上下文不实现算法,而是将算法的实现委托给战略对象。
- 战略接口(Strategy): 定义一组算法的通用接口,所有具体战略类都实现这个接口。
- 具体战略(ConcreteStrategy): 实现战略接口,定义具体的算法或行为。
UML 类图
- +---------------------------+ +-----------------------+
- | Context | | Strategy |
- +---------------------------+ +-----------------------+
- | - strategy: Strategy | | + Algorithm(): void |
- | + SetStrategy(Strategy) | +-----------------------+
- | + ExecuteStrategy(): void | ^ ^
- +---------------------------+ | |
- | |
- | |
- +-----------------------+ +-----------------------+
- | ConcreteStrategyA | | ConcreteStrategyB |
- +-----------------------+ +-----------------------+
- | + Algorithm(): void | | + Algorithm(): void |
- +-----------------------+ +-----------------------+
复制代码 2.21.3 示例代码
假设我们要实现一个简单的付出体系,它支持多种付出方式,如信用卡付出、PayPal付出和比特币付出。我们可以使用战略模式来封装这些付出方式,并让客户端在运行时选择不同的付出战略。
战略接口
- // 策略接口
- public interface IPaymentStrategy
- {
- void Pay(double amount);
- }
复制代码 具体战略类
- // 具体策略 - 信用卡支付
- public class CreditCardPayment : IPaymentStrategy
- {
- private string _cardNumber;
- public CreditCardPayment(string cardNumber)
- {
- _cardNumber = cardNumber;
- }
- public void Pay(double amount)
- {
- Console.WriteLine($"Paid {amount} using Credit Card {_cardNumber}.");
- }
- }
- // 具体策略 - PayPal支付
- public class PayPalPayment : IPaymentStrategy
- {
- private string _email;
- public PayPalPayment(string email)
- {
- _email = email;
- }
- public void Pay(double amount)
- {
- Console.WriteLine($"Paid {amount} using PayPal account {_email}.");
- }
- }
- // 具体策略 - 比特币支付
- public class BitcoinPayment : IPaymentStrategy
- {
- private string _walletAddress;
- public BitcoinPayment(string walletAddress)
- {
- _walletAddress = walletAddress;
- }
- public void Pay(double amount)
- {
- Console.WriteLine($"Paid {amount} using Bitcoin wallet {_walletAddress}.");
- }
- }
复制代码 上下文类
- // 上下文类
- public class PaymentContext
- {
- private IPaymentStrategy _paymentStrategy;
- public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
- {
- _paymentStrategy = paymentStrategy;
- }
- public void Pay(double amount)
- {
- _paymentStrategy.Pay(amount);
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- PaymentContext context = new PaymentContext();
- // 使用信用卡支付
- context.SetPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
- context.Pay(100.0);
- // 使用PayPal支付
- context.SetPaymentStrategy(new PayPalPayment("user@example.com"));
- context.Pay(200.0);
- // 使用比特币支付
- context.SetPaymentStrategy(new BitcoinPayment("1BitcoinAddressXYZ"));
- context.Pay(300.0);
- }
- }
复制代码 运行效果
- Paid 100 using Credit Card 1234-5678-9012-3456.
- Paid 200 using PayPal account user@example.com.
- Paid 300 using Bitcoin wallet 1BitcoinAddressXYZ.
复制代码 在这个例子中,PaymentContext 是上下文类,它持有一个 IPaymentStrategy 战略接口的引用。客户端可以动态设置不同的付出战略,如 CreditCardPayment、PayPalPayment 和 BitcoinPayment,并通过 Pay() 方法执行付出操作。这样,付出方式的变化不会影响客户端代码。
2.21.4 特点
- 长处:
- 算法的灵活性: 战略模式允许在运行时选择不同的算法或行为,增加了体系的灵活性和扩展性。
- 避免使用条件语句: 通过将不同的算法封装在独立的战略类中,避免了在客户端代码中使用大量的条件分支语句。
- 遵照开放-封闭原则: 可以在不修改现有代码的环境下,通过添加新的战略类来扩展体系的功能。
- 缺点:
- 增加类的数量: 每个战略都需要一个具体的战略类,大概会导致类的数量增加,增加体系的复杂性。
- 战略选择的复杂性: 在一些环境下,战略的选择逻辑大概本身就比较复杂,怎样选择合适的战略大概会成为一个挑衅。
2.21.5 适用场景
- 多种算法需要互换: 当一个体系有多个相似的算法或行为,并且这些算法或行为需要在不怜悯况下互换使用时,战略模式非常适用。
- 消除条件分支语句: 假如代码中存在大量的条件分支语句来选择不同的算法或行为,战略模式可以通过将这些算法封装到独立的战略类中来简化代码。
- 算法的频仍变化: 当算法或行为经常发生变化时,可以使用战略模式,使得每种算法封装在独立的战略类中,便于维护和扩展。
2.21.6 总结
战略模式通过将不同的算法封装到独立的战略类中,实现了算法的灵活互换。它消除了大量的条件分支语句,使代码更加清晰和可扩展。尽管战略模式大概会增加类的数量,但它为体系的算法选择和扩展提供了一种灵活且强盛的办理方案。在需要灵活选择算法或行为的场景中,战略模式是一种非常有效的计划模式。
2.22 模板方法模式(Template Method Pattern)
2.22.1 定义
模板方法模式通过在基类中定义一个模板方法,该方法封装了一个算法的固定步骤,然后允许子类实现或重写这些步骤。这样,子类可以定制算法的具体行为,而无需改变算法的团体结构。
2.22.2 结构
模板方法模式包罗以下角色:
- 抽象类(AbstractClass): 定义模板方法和算法的骨架。模板方法按照固定的步骤调用抽象方法或具体方法。
- 具体类(ConcreteClass): 继承自抽象类,实现或重写抽象方法,定义算法的具体步骤。
UML 类图
- +---------------------------+
- | AbstractClass |
- +---------------------------+
- | + TemplateMethod(): void |
- | + Step1(): void |
- | + Step2(): void |
- | - Step3(): void |
- +---------------------------+
- ^
- |
- +-------------------+
- | ConcreteClass |
- +-------------------+
- | - Step3(): void |
- +-------------------+
复制代码 2.22.3 示例代码
假设我们要制作一杯饮料,制作的过程包括煮水、冲泡、倒入杯中、添加配料等步骤。咖啡和茶是两种不同的饮料,它们在制作过程中的步骤根本相同,但在某些步骤上有所不同。我们可以使用模板方法模式来实现这个场景。
抽象类
- // 抽象类 - 饮料制作过程
- public abstract class Beverage
- {
- // 模板方法
- public void PrepareRecipe()
- {
- BoilWater();
- Brew();
- PourInCup();
- AddCondiments();
- }
- // 具体方法 - 煮水
- private void BoilWater()
- {
- Console.WriteLine("Boiling water");
- }
- // 抽象方法 - 冲泡
- protected abstract void Brew();
- // 具体方法 - 倒入杯中
- private void PourInCup()
- {
- Console.WriteLine("Pouring into cup");
- }
- // 抽象方法 - 添加配料
- protected abstract void AddCondiments();
- }
复制代码 具体类
- // 具体类 - 茶
- public class Tea : Beverage
- {
- protected override void Brew()
- {
- Console.WriteLine("Steeping the tea");
- }
- protected override void AddCondiments()
- {
- Console.WriteLine("Adding lemon");
- }
- }
- // 具体类 - 咖啡
- public class Coffee : Beverage
- {
- protected override void Brew()
- {
- Console.WriteLine("Dripping coffee through filter");
- }
- protected override void AddCondiments()
- {
- Console.WriteLine("Adding sugar and milk");
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- Beverage tea = new Tea();
- tea.PrepareRecipe(); // 制作茶
- Console.WriteLine();
- Beverage coffee = new Coffee();
- coffee.PrepareRecipe(); // 制作咖啡
- }
- }
复制代码 运行效果
- Boiling water
- Steeping the tea
- Pouring into cup
- Adding lemon
- Boiling water
- Dripping coffee through filter
- Pouring into cup
- Adding sugar and milk
复制代码 在这个例子中,Beverage 是抽象类,定义了一个模板方法 PrepareRecipe(),它包罗了制作饮料的固定步骤。这些步骤中,有些是具体实现的(如 BoilWater() 和 PourInCup()),而有些是抽象方法,由子类 Tea 和 Coffee 来实现(如 Brew() 和 AddCondiments())。客户端代码可以使用不同的具体类来制作不同的饮料,而不需要关心具体的实现细节。
2.22.4 特点
- 长处:
- 代码复用: 将通用的算法步骤封装到基类中,子类只需要实现差异化的部分,减少了重复代码。
- 扩展性强: 子类可以根据需要重写或扩展某些步骤,增加算法的灵活性。
- 控制算法结构: 父类定义了算法的骨架,确保了算法的团体结构不被子类粉碎。
- 缺点:
- 增加代码复杂性: 由于引入了继承关系和抽象方法,大概会使代码结构变得复杂。
- 对子类的依赖: 父类依赖子类来实现某些步骤,大概导致子类必须实现某些方法,纵然这些方法在特定环境下并不需要。
2.22.5 适用场景
- 多个类有相似的操作步骤: 当多个类的操作步骤大抵相同时,可以使用模板方法模式将相同的部分提取到抽象类中。
- 算法需要多个步骤: 当算法需要分解为多个步骤,并且这些步骤中有些是固定的,有些是可变的,可以使用模板方法模式。
- 控制算法的流程: 当需要确保算法的某些步骤必须按照一定的顺序执行时,可以使用模板方法模式。
2.22.6 总结
模板方法模式通过将通用的算法步骤封装到抽象类中,允许子类重写或扩展特定的步骤,实现了算法的复用和扩展。它确保了算法的团体结构不被粉碎,同时为子类提供了灵活性。在多个类具有相似的操作步骤时,模板方法模式是一种非常有效的计划模式。
2.23 访问者模式(Visitor Pattern)
2.23.1 定义
访问者模式通过引入一个访问者接口,使得你可以在元素类中接受访问者,并让访问者决定对元素的具体操作。访问者模式的关键在于分离算法和数据结构,使得新的操作可以轻松地添加而不影响已有的数据结构。
2.23.2 结构
访问者模式包罗以下角色:
- 访问者接口(Visitor): 为每种元素范例定义一个访问方法。访问者接口通常提供一个Visit方法,针对不同的元素类有不同的实现。
- 具体访问者(ConcreteVisitor): 实现访问者接口的具体操作,对元素执行具体的操作。
- 元素接口(Element): 声明一个Accept方法,该方法接受访问者对象并调用访问者的Visit方法。
- 具体元素(ConcreteElement): 实现元素接口,定义元素的具体行为,并在Accept方法中调用访问者的对应方法。
- 对象结构(ObjectStructure): 通常是一个包罗多个不同范例元素的聚集,它可以遍历这些元素并让访问者访问它们。
UML 类图
- +---------------------------+ +---------------------------+
- | Visitor | <------ | Element |
- +---------------------------+ +---------------------------+
- | + VisitElementA():void | | + Accept(v:Visitor): void |
- | + VisitElementB():void | +---------------------------+
- +---------------------------+ ^
- ^ |
- | |
- +---------------------------+ +---------------------------+
- | ConcreteVisitor | | ConcreteElement |
- +---------------------------+ +---------------------------+
- | + VisitElementA():void | | + Accept(v:Visitor): void |
- | + VisitElementB():void | | + OperationA(): void |
- +---------------------------+ +---------------------------+
复制代码 2.23.3 示例代码
假设我们要实现一个报表体系,体系中包罗不同范例的员工(如工程师和司理),每种员工有不同的报表要求。我们可以使用访问者模式来实现报表的天生,使得报表的天生与员工范例的实现分离。
访问者接口
- // 访问者接口
- public interface IVisitor
- {
- void Visit(Engineer engineer);
- void Visit(Manager manager);
- }
复制代码 具体访问者
- // 具体访问者 - 报表生成器
- public class ReportGenerator : IVisitor
- {
- public void Visit(Engineer engineer)
- {
- Console.WriteLine($"Generating report for Engineer: {engineer.Name}");
- }
- public void Visit(Manager manager)
- {
- Console.WriteLine($"Generating report for Manager: {manager.Name} with {manager.SubordinatesCount} subordinates.");
- }
- }
复制代码 元素接口
- // 元素接口
- public interface IEmployee
- {
- void Accept(IVisitor visitor);
- }
复制代码 具体元素类
- // 具体元素 - 工程师
- public class Engineer : IEmployee
- {
- public string Name { get; private set; }
- public Engineer(string name)
- {
- Name = name;
- }
- public void Accept(IVisitor visitor)
- {
- visitor.Visit(this);
- }
- }
- // 具体元素 - 经理
- public class Manager : IEmployee
- {
- public string Name { get; private set; }
- public int SubordinatesCount { get; private set; }
- public Manager(string name, int subordinatesCount)
- {
- Name = name;
- SubordinatesCount = subordinatesCount;
- }
- public void Accept(IVisitor visitor)
- {
- visitor.Visit(this);
- }
- }
复制代码 对象结构
- // 对象结构 - 员工列表
- public class EmployeeStructure
- {
- private List<IEmployee> _employees = new List<IEmployee>();
- public void Attach(IEmployee employee)
- {
- _employees.Add(employee);
- }
- public void Detach(IEmployee employee)
- {
- _employees.Remove(employee);
- }
- public void Accept(IVisitor visitor)
- {
- foreach (var employee in _employees)
- {
- employee.Accept(visitor);
- }
- }
- }
复制代码 客户端代码
- class Program
- {
- static void Main(string[] args)
- {
- // 创建员工结构
- EmployeeStructure employeeStructure = new EmployeeStructure();
- // 添加员工
- employeeStructure.Attach(new Engineer("John"));
- employeeStructure.Attach(new Manager("Alice", 5));
- // 创建报表生成器
- ReportGenerator reportGenerator = new ReportGenerator();
- // 生成报表
- employeeStructure.Accept(reportGenerator);
- }
- }
复制代码 运行效果
- Generating report for Engineer: John
- Generating report for Manager: Alice with 5 subordinates.
复制代码 在这个例子中,IVisitor 定义了对不同员工范例(Engineer 和 Manager)的访问方法。ReportGenerator 是具体的访问者,实现了天生报表的逻辑。IEmployee 接口定义了 Accept 方法,Engineer 和 Manager 作为具体的元素,实现了接受访问者的逻辑。EmployeeStructure 作为对象结构,管理了所有的员工,并允许访问者访问这些员工。
2.23.4 特点
- 长处:
- 增加新的操作轻易: 可以在不修改元素类的环境下,通过添加新的访问者类来增加新的操作。
- 将操作与对象结构分离: 访问者模式将数据结构和操作分离,使得数据结构和操作各自独立,符合单一职责原则。
- 扩展性好: 可以很轻易地增加新的访问者来实现新的功能。
- 缺点:
- 增加元素类的复杂性: 每个元素类都必须实现接受访问者的方法,这大概会增加类的复杂性。
- 违背开闭原则: 假如需要修改元素的结构或添加新的元素范例,则需要修改所有的访问者类,这与开闭原则相违背。
- 双分派: 访问者模式要求举行双分派,即根据元素范例和访问者范例分别调用相应的方法,这大概会导致体系的复杂性增加。
2.23.5 适用场景
- 对象结构稳固: 当对象结构相对稳固,但操作经常变化时,使用访问者模式可以有效地管理这些变化。
- 需要对对象结构中的对象举行复杂操作: 访问者模式适用于对对象结构中的元素举行复杂操作,且这些操作大概会频仍变化的场景。
- 对象结构中包罗多个不相关的类: 当对象结构中包罗多个不相关的类,需要对这些类执行某些操作时,访问者模式可以通过统一的访问者接口来处理这些操作。
2.23.6 总结
访问者模式通过将操作分离到独立的访问者对象中,使得在不修改元素类的环境下,可以增加新的操作。它适用于对象结构稳固但操作经常变化的场景。然而,由于需要对每个元素类增加接受访问者的方法,并且大概导致违背开闭原则,因此在使用时需要衡量利弊。在需要对复杂对象结构举行扩展和管理时,访问者模式是一种强盛的计划模式。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |