文章目录
行为型模式(Behavioral Pattern)
行为型模式是一种设计模式范式,旨在抽象化不同对象之间的责任和算法。它专注于类和对象之间的交互,不仅关注它们的结构,还关注它们在运行时的相互作用。这些模式帮助清晰地划分了类和对象的职责,并研究了在运行时对象实例之间的交互。
行为型模式可以分为两种类型:
1. 类行为型模式:这些模式使用继承关系在多个类之间分配行为。主要通过多态等方式在父类和子类之间分配职责。
2. 对象行为型模式:这些模式使用对象的聚合关联关系来分配行为。主要通过对象关联等方式在两个或多个类之间分配职责。基于“合成复用原则”,它们强调在系统中尽量使用关联关系取代继承关系。
行为型模式允许系统中的对象在运行时相互通信与协作,一个对象的行为可能会影响到其他对象的运行。这些模式的重点是对象之间的交互和协作,以实现系统中复杂功能的完成。
1.职责链模式(Chain of Responsibility)
1.1.定义
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
理解:责任链模式是一种行为型设计模式,用于构建一个对象链来处理请求,并将请求沿链传递直到被处理为止。在这个模式中,接收请求的对象会沿着链逐个判断是否能够处理请求,如果能够处理则处理请求,否则将请求传递给链中的下一个对象,直到请求被处理或者到达链的末端。
主要角色包括:
1. Handler(处理者):定义处理请求的接口,并持有一个对下一个处理者的引用。如果自己不能处理请求,则将请求传递给下一个处理者。
2. ConcreteHandler(具体处理者):实现处理请求的方法,如果能够处理则处理请求,否则将请求传递给下一个处理者。
责任链模式能够将请求的发送者和接收者解耦,每个处理者只需关注自己能够处理的请求,并决定是否将请求传递给下一个处理者。这样可以提高系统的灵活性和可扩展性,同时减少了发送者和接收者之间的直接依赖。
这种模式适用于以下场景:有多个对象可以处理同一个请求,但具体哪个对象处理该请求在运行时刻可知,需要动态指定处理者时,以及不确定请求的处理者时。
1.2.结构
责任链模式包含如下角色:
- Handler: 抽象处理者
- Concrete Handler: 具体处理者
1.3.代码实现
场景1:当一个支出需要审批流程时,可以使用责任链模式。假设有不同级别的审批人员,低级别的审批人员审批不了的情况下会将审批请求传递给下一级别的审批人员。
using System; // 请求类 class ExpenseRequest { public double Amount { get; set; } public string Purpose { get; set; } public ExpenseRequest(double amount, string purpose) { Amount = amount; Purpose = purpose; } } // 抽象处理者类 abstract class Approver { protected Approver _successor; // 下一个处理者 public void SetSuccessor(Approver successor) { _successor = successor; } public abstract void ProcessRequest(ExpenseRequest request); } // 具体处理者类 - 主管 class Supervisor : Approver { public override void ProcessRequest(ExpenseRequest request) { if (request.Amount <= 100) { Console.WriteLine($"主管审批通过:{request.Purpose}"); } else if (_successor != null) { _successor.ProcessRequest(request); // 如果主管审批不了则传递给下一个处理者 } } } // 具体处理者类 - 经理 class Manager : Approver { public override void ProcessRequest(ExpenseRequest request) { if (request.Amount <= 500) { Console.WriteLine($"经理审批通过:{request.Purpose}"); } else if (_successor != null) { _successor.ProcessRequest(request); // 如果经理审批不了则传递给下一个处理者 } } } // 具体处理者类 - 总经理 class GeneralManager : Approver { public override void ProcessRequest(ExpenseRequest request) { if (request.Amount > 500) { Console.WriteLine($"总经理审批通过:{request.Purpose}"); } } } class Program { static void Main(string[] args) { // 创建责任链 Approver supervisor = new Supervisor(); Approver manager = new Manager(); Approver generalManager = new GeneralManager(); supervisor.SetSuccessor(manager); manager.SetSuccessor(generalManager); // 发送审批请求 ExpenseRequest request1 = new ExpenseRequest(80, "购买办公用品"); supervisor.ProcessRequest(request1); ExpenseRequest request2 = new ExpenseRequest(300, "举办团建活动"); supervisor.ProcessRequest(request2); ExpenseRequest request3 = new ExpenseRequest(1000, "购买新设备"); supervisor.ProcessRequest(request3); } }
示例中,每个处理者类负责处理特定范围内的审批请求。如果自己不能处理请求,就会将请求传递给下一个处理者,直到有人处理请求或者到达责任链的末尾。
场景2:假设有一个购买审批系统,不同级别的员工可以审批不同金额范围内的购买请求。低级别的审批者不能处理请求时,请求会被转发给更高级别的审批者。
using System; // 请求类 - 购买申请 class PurchaseRequest { public double Amount { get; } public string Purpose { get; } public PurchaseRequest(double amount, string purpose) { Amount = amount; Purpose = purpose; } } // 抽象处理者类 - 审批人员 abstract class Approver { protected Approver _successor; // 下一个处理者 public void SetSuccessor(Approver successor) { _successor = successor; } public abstract void ProcessRequest(PurchaseRequest request); } // 具体处理者类 - 员工 class Employee : Approver { public override void ProcessRequest(PurchaseRequest request) { if (request.Amount <= 100) { Console.WriteLine($"员工审批通过:{request.Purpose}"); } else if (_successor != null) { _successor.ProcessRequest(request); // 如果员工审批不了则传递给下一个处理者 } } } // 具体处理者类 - 主管 class Supervisor : Approver { public override void ProcessRequest(PurchaseRequest request) { if (request.Amount <= 1000) { Console.WriteLine($"主管审批通过:{request.Purpose}"); } else if (_successor != null) { _successor.ProcessRequest(request); // 如果主管审批不了则传递给下一个处理者 } } } // 具体处理者类 - 经理 class Manager : Approver { public override void ProcessRequest(PurchaseRequest request) { if (request.Amount <= 5000) { Console.WriteLine($"经理审批通过:{request.Purpose}"); } else if (_successor != null) { _successor.ProcessRequest(request); // 如果经理审批不了则传递给下一个处理者 } } } // 具体处理者类 - 总经理 class GeneralManager : Approver { public override void ProcessRequest(PurchaseRequest request) { if (request.Amount > 5000) { Console.WriteLine($"总经理审批通过:{request.Purpose}"); } } } class Program { static void Main(string[] args) { // 创建责任链 Approver employee = new Employee(); Approver supervisor = new Supervisor(); Approver manager = new Manager(); Approver generalManager = new GeneralManager(); employee.SetSuccessor(supervisor); supervisor.SetSuccessor(manager); manager.SetSuccessor(generalManager); // 发送购买申请 PurchaseRequest request1 = new PurchaseRequest(80, "购买办公用品"); employee.ProcessRequest(request1); PurchaseRequest request2 = new PurchaseRequest(300, "购买电脑"); employee.ProcessRequest(request2); PurchaseRequest request3 = new PurchaseRequest(2500, "购买办公家具"); employee.ProcessRequest(request3); PurchaseRequest request4 = new PurchaseRequest(10000, "购买机器设备"); employee.ProcessRequest(request4); } }
不同级别的员工处理不同范围内的购买申请。如果某个员工无法处理请求,它会将请求传递给下一个处理者,直到有人能够处理请求或者到达责任链的末尾。
场景3:例如有多个验证规则需要按顺序执行并且任一规则失败就停止验证时,可以使用责任链模式。模拟一个用户注册的验证过程,包括验证用户名、邮箱和密码。
using System; // 请求类 - 用户信息 class User { public string Username { get; } public string Email { get; } public string Password { get; } public User(string username, string email, string password) { Username = username; Email = email; Password = password; } } // 抽象处理者类 - 验证器 abstract class Validator { protected Validator _successor; // 下一个验证器 public void SetSuccessor(Validator successor) { _successor = successor; } public abstract bool Validate(User user); } // 具体处理者类 - 用户名验证器 class UsernameValidator : Validator { public override bool Validate(User user) { if (user.Username.Length >= 5) { Console.WriteLine("用户名验证通过"); return _successor == null || _successor.Validate(user); // 如果有下一个验证器则继续验证 } else { Console.WriteLine("用户名长度不足"); return false; } } } // 具体处理者类 - 邮箱验证器 class EmailValidator : Validator { public override bool Validate(User user) { if (user.Email.Contains("@")) { Console.WriteLine("邮箱验证通过"); return _successor == null || _successor.Validate(user); // 如果有下一个验证器则继续验证 } else { Console.WriteLine("邮箱格式不正确"); return false; } } } // 具体处理者类 - 密码验证器 class PasswordValidator : Validator { public override bool Validate(User user) { if (user.Password.Length >= 8) { Console.WriteLine("密码验证通过"); return _successor == null || _successor.Validate(user); // 如果有下一个验证器则继续验证 } else { Console.WriteLine("密码长度不足"); return false; } } } class Program { static void Main(string[] args) { // 创建责任链 Validator usernameValidator = new UsernameValidator(); Validator emailValidator = new EmailValidator(); Validator passwordValidator = new PasswordValidator(); usernameValidator.SetSuccessor(emailValidator); emailValidator.SetSuccessor(passwordValidator); // 创建用户并进行验证 User user1 = new User("user123", "[email protected]", "password123"); bool result1 = usernameValidator.Validate(user1); Console.WriteLine($"用户注册是否通过验证:{result1}"); User user2 = new User("user", "userexample.com", "123"); bool result2 = usernameValidator.Validate(user2); Console.WriteLine($"用户注册是否通过验证:{result2}"); } }
示例中,每个具体处理者类都负责一个验证规则,如果某个规则验证失败,就会停止验证并返回结果。责任链会依次验证用户名、邮箱和密码,只有当所有验证规则都通过时,用户才能成功注册。
1.4.优缺点
优点:
1. 松耦合性(解耦):责任链模式可以将请求的发送者和接收者解耦,发送者无需知道接收者的具体处理方式,只需将请求传递给责任链即可。
2. 灵活性和扩展性:可以灵活地增加或修改处理者,方便扩展和维护。
3. 职责分离:将请求处理者按照责任范围划分,每个处理者只需关注自己能够处理的请求,符合单一职责原则。
4. 动态性:可以动态地修改责任链,根据需要动态地调整或修改链中的处理者。
缺点:
1. 请求过长:如果责任链过长或者处理者不合理,可能导致请求在链中传递的时间过长,影响性能。
2. 请求可能无法处理:当责任链没有正确配置或者出现遗漏,导致请求无法被处理。
3. 调试困难:责任链中的每个处理者都可能处理请求或者将请求传递给下一个处理者,这可能会增加调试复杂度,特别是在大型责任链中。
责任链模式适用于将多个对象处理同一请求的情况,并希望避免请求发送者和接收者直接耦合的情形。但需要小心配置和管理责任链,以避免请求无法被正确处理或者链过长影响性能。
1.5.使用场景
- 请求需要按顺序处理:当一个请求需要被多个对象处理,且处理顺序有要求,这些处理者可以形成一个链式结构,按顺序逐个处理请求。
- 避免发送者和接收者直接耦合:发送者无需知道请求最终由哪个对象处理,只需要将请求发送给责任链的第一个处理者,处理者之间解耦,增加系统的灵活性。
- 多个对象可以处理同一请求:有多个对象可以处理同一个请求,但具体由哪个对象处理可能是动态决定的。
- 动态添加/移除处理者:可以动态地增加、删除或调整责任链中的处理者,以便根据需求灵活地修改处理流程。
- 避免请求发送者知晓处理者:需要避免请求发送者与处理者直接交互,将请求转发给责任链上的下一个处理者,实现解耦。
在实际应用中,责任链模式常用于请求的过滤、拦截、验证、审批等场景,例如:Web请求的过滤器、身份验证、日志记录,或者审批流程中的多级审批等情况。
1.6.总结
- 角色分配:包含抽象处理者和具体处理者。抽象处理者定义了处理请求的接口,具体处理者实现了处理请求的具体逻辑,并能决定是否将请求传递给下一个处理者。
- 链式结构:多个处理者构成一条责任链,每个处理者都有一个指向下一个处理者的引用,请求会沿着链传递直到找到合适的处理者为止。
- 请求处理:请求发送者无需知道处理请求的具体处理者,只需将请求发送给链中的第一个处理者,处理者根据能力决定是否处理请求或将其传递给下一个处理者。
- 动态性和灵活性:可以动态地修改链的结构,增加、删除或重新排列处理者,以适应不同的处理流程需求。
- 解耦和单一职责:实现了请求发送者和接收者之间的解耦,每个具体处理者只负责自己能够处理的请求,符合单一职责原则。
- 应用场景:常用于请求的过滤、验证、审批等场景,或者处理多个对象处理同一个请求的情况。
责任链模式提供了一种灵活、可扩展的方式来处理复杂的请求处理流程,但在使用时需要注意链的设置和维护,以及避免链过长导致性能问题。
2.命令模式(Command)
2.1.定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
理解:命令模式用于将请求封装成对象,以便在不同的请求之间进行参数化和操作。它使得可以将方法调用、请求或操作封装到单独的对象中,从而使发送者和接收者之间解耦,支持请求排队、日志记录、可撤销操作等功能。
在命令模式中,关键要素包括:
- 命令(Command):定义了执行操作的接口,通常包含一个执行方法,它使得请求的发送者和具体执行操作的接收者解耦。
- 具体命令(Concrete Command):实现了命令接口,包含了执行实际操作的代码,并持有一个接收者对象引用,用于调用接收者的方法完成具体操作。
- 接收者(Receiver):包含了实际执行操作的对象,命令对象会调用接收者的方法来执行请求。
- 调用者(Invoker):负责触发命令执行的对象,它不了解具体的命令,只知道如何调用命令来完成请求。
- 客户端(Client):创建命令对象,并将其与接收者绑定,设置命令的参数,然后将命令传递给调用者来执行。
命令模式的关键在于将请求和操作封装为一个对象,使得可以动态地传递、排队、记录和撤销请求,同时也提高了系统的灵活性和可扩展性。
2.2.结构
命令模式包含如下角色:
- Command: 抽象命令类
- ConcreteCommand: 具体命令类
- Invoker: 调用者
- Receiver: 接收者
- Client:客户类
2.3.时序图
2.4.代码实现
场景1:假设有一个智能家居系统,用户可以通过语音或者遥控器来控制不同的家居设备。我们使用命令模式来实现这个场景,让每个家居设备都能够被命令所控制。
using System; // 命令接口 interface ICommand { void Execute(); } // 具体命令 - 开灯 class TurnOnCommand : ICommand { private Light _light; public TurnOnCommand(Light light) { _light = light; } public void Execute() { _light.TurnOn(); } } // 具体命令 - 关灯 class TurnOffCommand : ICommand { private Light _light; public TurnOffCommand(Light light) { _light = light; } public void Execute() { _light.TurnOff(); } } // 接收者 - 灯 class Light { public void TurnOn() { Console.WriteLine("灯被打开了"); } public void TurnOff() { Console.WriteLine("灯被关闭了"); } } // 调用者 - 遥控器 class RemoteControl { private ICommand _onCommand; private ICommand _offCommand; public void SetCommands(ICommand onCommand, ICommand offCommand) { _onCommand = onCommand; _offCommand = offCommand; } public void PressButtonOn() { _onCommand.Execute(); } public void PressButtonOff() { _offCommand.Execute(); } } class Program { static void Main(string[] args) { // 创建接收者 - 灯 Light light = new Light(); // 创建具体命令并传入接收者 ICommand turnOnCommand = new TurnOnCommand(light); ICommand turnOffCommand = new TurnOffCommand(light); // 创建调用者 - 遥控器并设置命令 RemoteControl remote = new RemoteControl(); remote.SetCommands(turnOnCommand, turnOffCommand); // 使用遥控器控制灯 remote.PressButtonOn(); // 打开灯 remote.PressButtonOff(); // 关闭灯 } }
例子中,我们有一个Light
类代表灯,它有打开和关闭的方法。然后,我们创建了具体的命令TurnOnCommand
和TurnOffCommand
,它们分别对应打开和关闭操作。遥控器RemoteControl
中有两个按钮,分别对应打开和关闭命令。当按下不同的按钮时,遥控器会执行对应的命令,间接地控制了灯的打开和关闭。
场景2:模拟一个的智能家居场景,其中有电视和音响设备,我们将使用命令模式来控制这些设备。
using System; // 家居设备接口 - 定义了所有家居设备共同的操作 interface IDevice { void TurnOn(); void TurnOff(); } // 具体设备 - 电视 class TV : IDevice { public void TurnOn() { Console.WriteLine("电视被打开了"); } public void TurnOff() { Console.WriteLine("电视被关闭了"); } } // 具体设备 - 音响 class Stereo : IDevice { public void TurnOn() { Console.WriteLine("音响被打开了"); } public void TurnOff() { Console.WriteLine("音响被关闭了"); } } // 命令接口 interface ICommand { void Execute(); } // 具体命令 - 开启设备 class TurnOnCommand : ICommand { private IDevice _device; public TurnOnCommand(IDevice device) { _device = device; } public void Execute() { _device.TurnOn(); } } // 具体命令 - 关闭设备 class TurnOffCommand : ICommand { private IDevice _device; public TurnOffCommand(IDevice device) { _device = device; } public void Execute() { _device.TurnOff(); } } // 调用者 - 遥控器 class RemoteControl { private ICommand _onCommand; private ICommand _offCommand; public void SetCommands(ICommand onCommand, ICommand offCommand) { _onCommand = onCommand; _offCommand = offCommand; } public void PressButtonOn() { _onCommand.Execute(); } public void PressButtonOff() { _offCommand.Execute(); } } class Program { static void Main(string[] args) { // 创建具体设备 - 电视和音响 IDevice tv = new TV(); IDevice stereo = new Stereo(); // 创建具体命令并传入设备 ICommand turnOnTvCommand = new TurnOnCommand(tv); ICommand turnOffTvCommand = new TurnOffCommand(tv); ICommand turnOnStereoCommand = new TurnOnCommand(stereo); ICommand turnOffStereoCommand = new TurnOffCommand(stereo); // 创建调用者 - 遥控器并设置命令 RemoteControl remote = new RemoteControl(); remote.SetCommands(turnOnTvCommand, turnOffTvCommand); // 使用遥控器控制电视 Console.WriteLine("使用遥控器控制电视:"); remote.PressButtonOn(); // 打开电视 remote.PressButtonOff(); // 关闭电视 // 更新遥控器命令并控制音响 remote.SetCommands(turnOnStereoCommand, turnOffStereoCommand); Console.WriteLine("\n使用遥控器控制音响:"); remote.PressButtonOn(); // 打开音响 remote.PressButtonOff(); // 关闭音响 } }
示例中,我们有两种家居设备:电视和音响。通过命令模式,我们创建了针对这些设备的具体命令,然后使用遥控器调用这些命令来控制设备的开启和关闭。这种设计使得系统更具灵活性,可以随时更改设备和遥控器之间的关联,而不需要修改遥控器本身的代码。
2.5.优缺点
优点:
1. 解耦合:命令模式能够将发送者与接收者解耦,发送者无需知道接收者的具体操作,只需要知道如何发送命令即可。
2. 可扩展性:新的命令可以很容易地添加到系统中,而无需修改已有的代码结构。
3. 撤销和重做:容易实现对操作的撤销和重做,只需维护一个命令历史记录,使得系统更加灵活和可靠。
4. 适用于操作队列和日志系统:可以轻松地实现命令队列、日志记录等功能,对于系统的追踪和回溯具有很好的支持。
5. 高内聚性:每个具体命令都被封装成一个对象,这样有利于实现命令对象的独立性,使得命令模式有较高的内聚性。
缺点:
1. 类爆炸问题:在有多个命令的情况下,会产生大量的具体命令类,可能导致类的数量急剧增加。
2. 代码复杂性:如果每个命令都要实现一个具体类,会导致代码结构相对复杂,不易管理。
3. 增加系统复杂性:引入了额外的抽象层,可能增加系统的复杂性和理解成本。
虽然命令模式具有一些缺点,但在需要实现请求发送者和请求接收者解耦,以及对操作进行灵活控制的情景下,命令模式仍然是一个强大的设计模式选择。
2.6.使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令
2.7.总结
- 在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或事务模式。
- 命令模式包含四个角色:抽象命令类中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作;具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求;接收者执行与请求相关的操作,它具体实现对请求的业务处理。
- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 命令模式的主要优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复;其主要缺点在于可能会导致某些系统有过多的具体命令类。
- 命令模式适用情况包括:需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;需要在不同的时间指定请求、将请求排队和执行请求;需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。
3.解释器模式(Interpreter)
3.1.定义
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
理解:解释器模式是一种行为型设计模式,用于解决特定类型的问题,其中一种常见的情况是需要解释、解析、执行特定语言或规则的情况。
关键概念:
- 文法规则:定义了语言的语法结构和规则,解释器模式通过创建表示文法规则的类来解释这些规则。
- 抽象表达式:定义了文法规则中的抽象语法树节点,并声明了解释操作的接口。
- 终结符表达式和非终结符表达式:终结符表达式表示文法规则中的基本元素,非终结符表达式表示复合元素,包含终结符表达式。
- 上下文环境:包含了解释器模式执行所需的全局信息或状态,供解释器使用。
- 解释器:实现了抽象表达式接口的具体类,通过递归调用解释自身,解释并执行文法规则。
解释器模式通常用于处理特定语言的解析,例如编译器、解释器、规则引擎等领域。它能够将复杂的问题分解成易于理解的解释器对象,但在处理复杂语法时,可能会导致解释器类的增多,并且难以维护。
3.2.结构
解释器模式包含如下角色:
- Abstract Expression: 抽象表达式,定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()
- Terminal Expression: 具体处理者,抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- Nonterminal Expression:非终结符表达式,抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- Context:环境,包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
3.3.代码实现
场景:当涉及到某种简单的自定义语言解释时,解释器模式很有用。我们以一个简单的数学表达式解释器为例来演示:假设我们有一种可以解释简单数学表达式的语言,其中支持加法和减法操作。能够通过解释器模式来解析和计算这些表达式。
using System; // 抽象表达式接口 interface IExpression { int Interpret(); } // 终结符表达式:数字表达式 class NumberExpression : IExpression { private int _number; public NumberExpression(int number) { _number = number; } public int Interpret() { return _number; } } // 非终结符表达式:加法表达式 class AddExpression : IExpression { private IExpression _left; private IExpression _right; public AddExpression(IExpression left, IExpression right) { _left = left; _right = right; } public int Interpret() { return _left.Interpret() + _right.Interpret(); } } // 非终结符表达式:减法表达式 class SubtractExpression : IExpression { private IExpression _left; private IExpression _right; public SubtractExpression(IExpression left, IExpression right) { _left = left; _right = right; } public int Interpret() { return _left.Interpret() - _right.Interpret(); } } class Program { static void Main(string[] args) { // 构建一个简单的数学表达式:(5 + 2) - 3 IExpression expression = new SubtractExpression( new AddExpression(new NumberExpression(5), new NumberExpression(2)), new NumberExpression(3)); // 解释并计算表达式结果 int result = expression.Interpret(); Console.WriteLine("解释器计算结果: " + result); // 应输出 4 } }
示例中,我们定义了抽象表达式接口 IExpression
以及终结符表达式和非终结符表达式的具体实现。然后构建了一个简单的数学表达式 (5 + 2) - 3
,并使用解释器模式解释并计算了这个表达式的结果。
3.4.优缺点
优点:
1. 灵活性:允许动态地添加新的解释规则或语法,因此在适应不同类型问题时很灵活。
2. 可扩展性:可以通过组合不同的表达式构建复杂的解释器,从而扩展解释能力。
3. 可维护性:每个表达式或规则的解释都由单独的类表示,易于维护和扩展。
4. 易于实现语法树:解释器模式可以方便地实现语法树,有助于更直观地表示语言结构。
缺点:
1. 复杂性:当解释器规则变得复杂时,可能会导致类的数量急剧增加,并且维护和理解这些类变得更加困难。
2. 性能问题:对于复杂的解释器,可能会存在性能问题,因为解释器可能需要对多个表达式进行解析和执行。
虽然解释器模式在特定场景下非常有用,但在实际应用中,需要权衡其灵活性和复杂性。在简单的解释逻辑或表达式解析中,解释器模式可能是个不错的选择,但对于复杂的解释器需求,需要谨慎设计以避免过度复杂化。
3.5.使用场景
- 特定语言解析:当需要解析和执行特定语言、规则或表达式时,比如编程语言解析、正则表达式、查询语言等,解释器模式很有用。
- 配置文件解析:当需要解析配置文件或特定格式的数据时,解释器模式能够帮助解析和处理这些配置信息。
- 复杂规则处理:对于需要处理复杂规则、算法或工作流程的情况,解释器模式可以帮助构建灵活且可扩展的解释器,进行处理和执行。
- 通用模式匹配:例如,在编写解析器以匹配和处理日志文件、文本解析、编译器等场景中,解释器模式有其用武之地。
- 数据库查询语言:解释器模式可用于构建数据库查询语言解释器,将用户输入的查询语句解析并执行。
当需要解析、处理或执行特定语言、规则或模式时,解释器模式可以被用来构建相应的解释器,从而实现灵活且可扩展的解析和执行功能。
3.6.总结
- 定义语法规则:定义特定语言或规则的语法结构和规则。
- 抽象表达式:定义了解释器模式中的抽象表达式接口,表示文法规则中的各种表达式或操作。
- 终结符表达式和非终结符表达式:终结符表达式代表文法规则中的基本元素,而非终结符表达式表示复合元素,它包含和操作多个终结符或非终结符表达式。
- 上下文环境:包含了解释器模式执行所需的全局信息或状态。
- 解释器:实现了抽象表达式接口的具体类,通过递归调用解释自身,解释和执行文法规则。
解释器模式通常用于解析和执行特定语言、规则或模式。它允许动态添加新的解释规则,并支持构建灵活且可扩展的解释器。然而,对于复杂的解释规则,可能会导致类的数量急剧增加,维护和理解变得更加困难。
解释器模式适用于需要解析、处理或执行特定语言、规则或模式的场景,可以通过构建解释器来实现相应的解析和执行功能。
4.迭代器模式(Iterator)
4.1.定义
迭代器模式(Iterator Pattern)用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
理解:迭代器模式是一种行为型设计模式,用于提供一种方法来顺序访问一个聚合对象中的各个元素,而不暴露其底层表示方式。
关键要点:
1. 迭代器接口(Iterator):定义了访问和遍历聚合对象中各个元素的通用方法,包括获取下一个元素、判断是否还有元素等。
2. 具体迭代器(ConcreteIterator):实现了迭代器接口,负责实现对具体聚合对象的遍历和访问。
3. 聚合对象接口(Aggregate):定义了创建相应迭代器的接口方法。
4. 具体聚合对象(ConcreteAggregate):实现了聚合对象接口,返回具体的迭代器。
迭代器模式允许客户端在不关心集合内部结构的情况下遍历聚合对象的元素。它将遍历的责任分离到迭代器对象中,使得客户端可以通过迭代器对象遍历聚合对象,而无需知道聚合对象内部的结构。
这种模式有助于提供一种统一的方式来遍历各种类型的集合,使得对集合的遍历更为简洁和可控。同时,通过迭代器的使用,可以减少对聚合对象内部实现的依赖,提高了程序的灵活性和可维护性。
4.2.结构
迭代器模式包含如下角色:
- Aggregate:抽象聚合
- ConcreteAggregate: 具体聚合
- Iterator:抽象迭代器
- Concretelterator:具体迭代器
4.3.代码实现
场景1:有一个购物车,里面装有各种商品,并且希望能够遍历打印购物车中的商品信息时,可以使用迭代器模式。
using System; using System.Collections; // 商品类 class Product { public string Name { get; } public double Price { get; } public Product(string name, double price) { Name = name; Price = price; } } // 购物车类 class ShoppingCart : IEnumerable { private ArrayList _products = new ArrayList(); // 添加商品到购物车 public void AddProduct(Product product) { _products.Add(product); } // 实现 IEnumerable 接口的 GetEnumerator 方法,返回迭代器 public IEnumerator GetEnumerator() { return new ShoppingCartIterator(this); } } // 购物车迭代器类 class ShoppingCartIterator : IEnumerator { private int _currentIndex = -1; private readonly ShoppingCart _cart; public ShoppingCartIterator(ShoppingCart cart) { _cart = cart; } // 实现 MoveNext 方法 public bool MoveNext() { _currentIndex++; return _currentIndex < _cart.Count; } // 重置迭代器 public void Reset() { _currentIndex = -1; } // 获取当前迭代的商品 public object Current { get { return _cart[_currentIndex]; } } } class Program { static void Main(string[] args) { // 创建一个购物车 var cart = new ShoppingCart(); cart.AddProduct(new Product("手机", 999.99)); cart.AddProduct(new Product("电视", 1499.99)); cart.AddProduct(new Product("笔记本电脑", 1999.99)); // 使用迭代器遍历打印购物车中的商品信息 foreach (Product product in cart) { Console.WriteLine($"商品:{product.Name},价格:{product.Price:C}"); } } }
示例中,我们创建了一个购物车类 ShoppingCart
,它包含一个 ArrayList
来存储商品对象。购物车类实现了 IEnumerable
接口,并提供了一个 GetEnumerator
方法返回购物车迭代器 ShoppingCartIterator
。购物车迭代器实现了 IEnumerator
接口,负责实现遍历购物车内部商品的逻辑。
通过迭代器模式,我们能够遍历并打印购物车中的商品信息,而无需了解购物车内部的实现细节。
场景2:有一个电影库,并且希望能够以不同的方式遍历和访问电影库中的电影信息。
using System; using System.Collections; using System.Collections.Generic; // 电影类 class Movie { public string Title { get; } public string Genre { get; } public int Year { get; } public Movie(string title, string genre, int year) { Title = title; Genre = genre; Year = year; } } // 电影库类 class MovieLibrary : IEnumerable<Movie> { private List<Movie> _movies = new List<Movie>(); // 添加电影到电影库 public void AddMovie(Movie movie) { _movies.Add(movie); } // 实现 IEnumerable 接口的 GetEnumerator 方法,返回迭代器 public IEnumerator<Movie> GetEnumerator() { return _movies.GetEnumerator(); } // 必须同时实现非泛型的 GetEnumerator 方法 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class Program { static void Main(string[] args) { // 创建一个电影库 var movieLibrary = new MovieLibrary(); movieLibrary.AddMovie(new Movie("阿凡达", "科幻", 2009)); movieLibrary.AddMovie(new Movie("肖申克的救赎", "剧情", 1994)); movieLibrary.AddMovie(new Movie("盗梦空间", "科幻", 2010)); // 使用迭代器遍历打印电影库中的电影信息 Console.WriteLine("电影库中的电影:"); foreach (var movie in movieLibrary) { Console.WriteLine($"电影:{movie.Title},类型:{movie.Genre},年份:{movie.Year}"); } } }
示例中,创建了一个电影类 Movie
,它包含了电影的标题、类型和年份信息。然后创建了一个电影库类 MovieLibrary
,它包含一个 List<Movie>
来存储电影对象。
MovieLibrary
实现了 IEnumerable<Movie>
接口,并提供了 GetEnumerator
方法,返回了电影库迭代器,该迭代器可用于遍历电影库中的电影对象。
通过迭代器模式,我们能够以简单且统一的方式遍历和访问电影库中的电影信息。利用迭代器模式,我们可以轻松遍历并获取电影库中的电影信息,而不用了解其内部的实现细节。
4.4.优缺点
优点:
1. 简化遍历:提供了一种统一的方式遍历集合对象,不需要暴露集合的内部结构,使得遍历更加简单和统一。
2. 解耦合:分离了集合对象的遍历行为和集合对象本身,降低了彼此之间的依赖关系,提高了代码的灵活性和可维护性。
3. 支持多种遍历方式:可以针对同一个集合提供多种不同的遍历方式,满足不同场景的需求。
4. 隐藏内部细节:客户端只需关心如何遍历集合,而不需要关心集合的具体实现细节,提高了代码的封装性。
缺点:
1. 性能问题:在某些特定场景下,迭代器模式可能引起性能问题,尤其是在大数据集合的情况下,可能会有一定的性能开销。
2. 增加复杂性:如果仅仅需要遍历一个简单的数据结构,使用迭代器模式可能会增加代码的复杂性,不利于代码的可读性。
迭代器模式是一种非常有用的设计模式,特别适用于需要遍历集合对象的场景,并且能够使得遍历行为更加灵活和可控。但在某些特定场景下,可能需要权衡使用该模式带来的性能开销和代码复杂性。
4.5.使用场景
- 需要统一遍历集合对象:当需要提供一种统一的方式来遍历不同类型的集合对象时,迭代器模式非常有用。它可以将遍历行为从集合中抽离出来,使得客户端可以以统一的方式遍历各种不同类型的集合。
- 隐藏集合内部结构:当希望隐藏集合的内部结构而提供一种访问方式时,迭代器模式可以帮助实现这一目标。它使得客户端无需了解集合的实现细节,只需通过迭代器进行遍历操作。
- 支持多种遍历方式:当需要为同一个集合对象提供多种不同的遍历方式时,迭代器模式很有用。通过定义不同的迭代器,可以让客户端根据需求选择合适的遍历方式。
- 遍历行为变化频繁:如果集合对象的遍历行为经常发生变化,而又不希望影响到客户端代码,使用迭代器模式可以将遍历的逻辑抽象出来,使得集合对象的变化不会影响到遍历方式。
迭代器模式适用于需要对集合对象进行统一遍历、隐藏内部实现、提供多种遍历方式以及遍历行为经常变化的情况。它能够提供一种统一且灵活的遍历方式,并且使得集合和遍历行为之间的耦合度降低。
4.6.总结
- 遍历的统一性:迭代器模式提供了一种统一的方式来遍历不同类型的集合对象,使得客户端可以使用统一的接口来访问不同类型的集合。
- 封装集合内部结构:它允许客户端以统一的方式遍历集合,同时隐藏了集合的内部结构细节,提高了代码的封装性和可维护性。
- 多种遍历方式支持:可以为同一个集合对象提供多种不同的遍历方式,由不同的具体迭代器来实现,满足不同遍历需求。
- 减少耦合性:迭代器模式减少了客户端与集合对象之间的耦合,使得集合的实现变化不会影响客户端代码。
- 简化客户端代码:客户端不需要了解集合的具体结构,只需通过迭代器进行遍历,简化了客户端代码的编写。
- 灵活性与可扩展性:可以根据需要灵活地扩展新的迭代器,以支持不同类型的集合对象,同时也便于新增或修改集合的遍历方式。
- 性能问题和复杂性:在某些情况下,可能存在性能问题,尤其是对于大型集合,同时在简单遍历场景下可能会增加代码的复杂性。
5.中介者模式(Mediator)
5.1.定义
中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
理解:中介者模式用于通过一个中介对象来封装一系列对象之间的交互。它能够使对象之间的通信变得松散耦合,所有对象不再直接相互通信,而是通过中介者对象进行通信。
在中介者模式中,中介者对象充当了对象之间交互的中心枢纽,对象不再直接相互引用,而是通过中介者对象进行通信。这种方式可以减少对象之间的耦合度,提高系统的可维护性和灵活性。中介者模式通常包含以下角色:
中介者(Mediator):定义了各个对象之间交互的接口,负责协调各个同事对象的行为。
具体中介者(Concrete Mediator):实现了中介者接口,具体实现了各个对象之间的交互关系。
同事对象(Colleague):每个同事对象都知道中介者对象,并通过中介者对象与其他同事对象进行通信。
具体同事类(Concrete Colleague):实现了同事接口,每个具体同事对象都需要与其他同事对象进行交互,通过中介者来完成。
中介者模式适用于系统中多个对象之间存在复杂的交互关系,通过引入中介者对象,可以简化对象之间的通信结构,使系统更易于维护和扩展。
5.2.结构
中介者模式包含如下角色:
- Mediator: 抽象中介者
- ConcreteMediator: 具体中介者
- Colleague: 抽象同事类
- ConcreteColleague: 具体同事类
5.3.时序图
5.4.代码实现
场景1:假设有一个聊天室系统,其中用户可以发送消息给其他用户,但是所有消息必须通过一个中介者传递。
using System; using System.Collections.Generic; // 中介者接口 interface IChatRoomMediator { void SendMessage(string message, User user); } // 具体中介者类 class ChatRoom : IChatRoomMediator { private List<User> users = new List<User>(); public void AddUser(User user) { users.Add(user); } public void SendMessage(string message, User sender) { foreach (var user in users) { if (user != sender) // 排除发送者本身 user.Receive(message); } } } // 同事类 - 用户 class User { private readonly string name; private readonly IChatRoomMediator chatRoom; public User(string name, IChatRoomMediator chatRoom) { this.name = name; this.chatRoom = chatRoom; } public void Send(string message) { Console.WriteLine($"{name} 发送消息: {message}"); chatRoom.SendMessage(message, this); } public void Receive(string message) { Console.WriteLine($"{name} 收到消息: {message}"); } } class Program { static void Main(string[] args) { // 创建中介者 - 聊天室 IChatRoomMediator chatRoom = new ChatRoom(); // 创建具体同事对象 - 用户 User user1 = new User("Alice", chatRoom); User user2 = new User("Bob", chatRoom); User user3 = new User("Charlie", chatRoom); // 将用户加入聊天室 chatRoom.AddUser(user1); chatRoom.AddUser(user2); chatRoom.AddUser(user3); // 用户发送消息,由中介者转发给其他用户 user1.Send("大家好!"); user2.Send("欢迎!"); } }
场景中,聊天室是中介者,它能够将用户发送的消息转发给其他用户。用户通过中介者发送消息,而不是直接与其他用户进行通信,从而实现了对象之间的解耦。
场景2:多架飞机在同一空域飞行,需要通过交通管制中心进行通信协调避免相撞,设计一个飞机交通管制系统作为中介者进行通讯。
using System; using System.Collections.Generic; // 中介者接口 interface IAirTrafficControl { void SendMessage(string message, Aircraft aircraft); } // 具体中介者类 class AirTrafficControl : IAirTrafficControl { private List<Aircraft> aircrafts = new List<Aircraft>(); public void RegisterAircraft(Aircraft aircraft) { aircrafts.Add(aircraft); } public void SendMessage(string message, Aircraft sender) { foreach (var aircraft in aircrafts) { if (aircraft != sender) // 避免向自己发送消息 aircraft.Receive(message); } } } // 飞机类 - 同事类 class Aircraft { private readonly string callSign; private readonly IAirTrafficControl atc; public Aircraft(string callSign, IAirTrafficControl atc) { this.callSign = callSign; this.atc = atc; } public void Send(string message) { Console.WriteLine($"{callSign} 发送消息: {message}"); atc.SendMessage(message, this); } public void Receive(string message) { Console.WriteLine($"{callSign} 收到消息: {message}"); } } class Program { static void Main(string[] args) { // 创建中介者 - 交通管制中心 IAirTrafficControl atc = new AirTrafficControl(); // 创建具体飞机对象 Aircraft plane1 = new Aircraft("Flight 001", atc); Aircraft plane2 = new Aircraft("Flight 002", atc); Aircraft plane3 = new Aircraft("Flight 003", atc); // 注册飞机到交通管制中心 atc.RegisterAircraft(plane1); atc.RegisterAircraft(plane2); atc.RegisterAircraft(plane3); // 飞机之间通过交通管制中心通信 plane1.Send("在降落过程中,请注意避让"); plane2.Send("收到,已调整航线"); } }
AirTrafficControl
充当中介者,负责协调飞机之间的通信。每架飞机通过中介者向其他飞机发送消息,而不需要直接与其他飞机进行通信,这样可以降低飞机之间的耦合度。
场景3:以一个简单的游戏场景为例,假设我们有一个游戏中的角色,它们可以相互攻击,但是当其中一个角色被攻击时,其他角色需要做出相应的反应。这时候就可以使用中介者模式来实现。
using System; using System.Collections.Generic; // 中介者接口 interface IGameMediator { void Notify(Character character, string action); } // 具体中介者类 class GameMediator : IGameMediator { public void Notify(Character character, string action) { Console.WriteLine($"{character.Name} {action}"); } } // 同事类 - 角色 class Character { public string Name { get; private set; } private readonly IGameMediator mediator; public Character(string name, IGameMediator mediator) { Name = name; this.mediator = mediator; } public void Attack(Character target) { Console.WriteLine($"{Name} 攻击 {target.Name}"); mediator.Notify(target, "被攻击!"); } } class Program { static void Main(string[] args) { // 创建中介者 - 游戏中介者 IGameMediator mediator = new GameMediator(); // 创建具体角色对象 Character player1 = new Character("玩家一", mediator); Character player2 = new Character("玩家二", mediator); Character monster = new Character("怪物", mediator); // 角色相互攻击,并通过中介者通知 player1.Attack(player2); player2.Attack(monster); monster.Attack(player1); } }
GameMediator
充当中介者,Character
代表角色,它们通过中介者通知其他角色的状态。当角色发起攻击时,通过中介者通知其他角色被攻击的情况。这种设计方式使得角色之间的交互和通知通过中介者完成,降低了角色之间的直接耦合。
5.5.优缺点
优点:
1. 降低耦合度:中介者模式将对象之间的交互集中到一个中介者对象中,使得对象之间不需要直接相互引用,降低了对象之间的耦合度。
2. 简化对象:各个对象只需要了解中介者的接口,不需要了解其他对象的细节信息,使得对象更加简单和易于维护。
3. 方便扩展:添加新的同事类或者中介者类都比较容易,因为修改只会发生在一个地方,不会影响到其他对象。
4. 集中控制:中介者作为一个集中的控制中心,可以更方便地管理、调控对象之间的交互逻辑。
缺点:
1. 单一化:中介者模式可能导致中介者对象过于复杂,承担了过多的职责,使得中介者成为系统的瓶颈。
2. 过度集中:过度使用中介者模式可能导致系统中所有对象都依赖于中介者,增加了系统的复杂性和单点故障的风险。
3. 引入中介者对象:引入了中介者对象,可能会增加系统中类的数量和复杂性,需要仔细权衡设计。
中介者模式适用于对象之间交互复杂,对象之间存在多对多关系的情况下,它能够降低系统中对象之间的耦合度,提高系统的可维护性和灵活性。但是在使用时需要注意不要滥用中介者模式,避免将所有的对象关系都集中到一个中介者中,应该根据实际情况慎重使用。
5.6.使用场景
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象。
- 交互的公共行为,如果需要改变行为则可以增加新的中介者类。
5.7.总结
- 中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
- 中介者模式包含四个角色:抽象中介者用于定义一个接口,该接口用于与各同事对象之间的通信;具体中介者是抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的引用;抽象同事类定义各同事的公有方法;具体同事类是抽象同事类的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中定义的方法。
- 通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,中介者承担了中转作用和协调作用。中介者类是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互,还可以对对象间的交互进行进一步的控制。
- 中介者模式的主要优点在于简化了对象之间的交互,将各同事解耦,还可以减少子类生成,对于复杂的对象之间的交互,通过引入中介者,可以简化各同事类的设计和实现;中介者模式主要缺点在于具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
- 中介者模式适用情况包括:系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解;一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象;想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
6.备忘录模式(Memento)
6.1.定义
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
理解:备忘录模式是一种行为型设计模式,旨在捕获对象的内部状态,并在需要时将其保存到备忘录中,从而在以后能够恢复到先前的状态。这个模式通过三个主要角色实现:发起人(Originator)、备忘录(Memento)和管理者(Caretaker)。
- 发起人(Originator):发起人对象能够创建备忘录并在需要时将其状态保存到备忘录中。发起人可以使用备忘录对象恢复其先前的状态。
- 备忘录(Memento):备忘录对象负责存储发起人对象的内部状态。它提供了对其状态的受限访问,只有发起人可以访问其内部状态。备忘录不会直接暴露其状态给其他对象。
- 管理者(Caretaker):管理者对象负责保存备忘录对象,但并不对备忘录的内容进行修改。它提供了一个容器,用于存储和检索备忘录对象,以便在需要时将状态恢复给发起人对象。
备忘录模式适用于需要保存和恢复对象状态历史的场景。它使得对象能够在不破坏其封装性的情况下捕获和恢复状态,通常用于需要提供撤销/恢复功能的应用程序或系统。通过备忘录模式,可以有效地管理对象的状态变化,提供了一种灵活的历史状态管理机制。
6.2.结构
备忘录模式包含如下角色:
- Originator:发起人
- Memento: 备忘录
- Caretaker:管理者
6.3.代码实现
场景:有一个游戏对象,玩家可以修改其状态(比如玩家的位置、生命值等)。我们想要实现一个保存和恢复游戏状态的功能,允许玩家保存游戏状态并在需要时恢复到之前的状态。
using System; using System.Collections.Generic; // 备忘录:游戏状态 class GameState { public int Level { get; } public int Health { get; } public GameState(int level, int health) { Level = level; Health = health; } } // 发起人:游戏对象 class Game { private int level; private int health; public void Play() { // 游戏进行中,修改游戏状态 level++; health -= 10; Console.WriteLine($"玩家正在玩游戏,当前等级:{level},生命值:{health}"); } public GameState SaveState() { // 保存当前游戏状态 return new GameState(level, health); } public void RestoreState(GameState state) { // 恢复游戏状态 level = state.Level; health = state.Health; Console.WriteLine($"恢复游戏状态,当前等级:{level},生命值:{health}"); } } // 管理者:状态管理器 class StateManager { private readonly Stack<GameState> states = new Stack<GameState>(); public void SaveState(GameState state) { // 保存游戏状态到栈中 states.Push(state); } public GameState RestoreState() { // 从栈中弹出并返回最近保存的游戏状态 return states.Pop(); } } class Program { static void Main(string[] args) { Game game = new Game(); StateManager stateManager = new StateManager(); // 玩家进行游戏并保存状态 game.Play(); stateManager.SaveState(game.SaveState()); // 玩家再次进行游戏,状态改变 game.Play(); // 恢复到之前的状态 GameState savedState = stateManager.RestoreState(); game.RestoreState(savedState); } }
模拟了一个游戏的简单状态管理。玩家玩游戏时,游戏对象会修改其状态。玩家可以通过保存当前状态并在需要时恢复到之前的状态。
6.4.优缺点
优点:
1. 状态保存和恢复:允许在不破坏对象封装性的情况下捕获和保存其内部状态,随后能够恢复到先前的状态。
2. 封装性良好:备忘录对象只能由发起人访问和修改,对外部对象隐藏了状态细节,提高了对象的封装性。
3. 历史状态管理:允许对象保存历史状态记录,提供了一种良好的状态管理机制,使得对象状态的管理更加灵活。
4. 支持撤销和恢复:可以用于实现撤销和重做功能,允许在需要时恢复到先前的状态。
缺点:
1. 资源消耗:如果需要存储大量的状态信息,可能会消耗大量的内存。
2. 状态封装:在某些情况下,如果需要保留的状态信息较多,可能会导致备忘录对象过于复杂。
备忘录模式在需要保存和恢复对象状态、提供撤销/重做功能以及对象状态历史记录管理等场景下非常有用。然而,需要考虑存储大量状态信息可能带来的内存消耗,并在设计时注意备忘录对象的复杂性。
6.5.使用场景
- 撤销和重做功能:当需要实现撤销和重做的功能时,备忘录模式可以很好地支持,允许在需要时恢复到先前的状态。
- 历史记录管理:当需要管理对象的历史状态记录或跟踪对象状态变化时,备忘录模式可以帮助记录并管理对象状态的变化。
- 编辑器和文档管理器:在文本编辑器、图形编辑器或其他应用程序中,备忘录模式可以用于保存不同版本或历史状态,允许用户在编辑文档时进行撤销和恢复操作。
- 游戏状态管理:在游戏开发中,备忘录模式可以用于保存游戏状态、关卡进度或玩家位置等信息,允许玩家在需要时恢复到之前的状态。
- 事务处理系统:在需要实现事务处理和回滚的系统中,备忘录模式可以记录事务开始前的状态,以便在需要时回滚到之前的状态。
备忘录模式主要用于需要捕获对象状态并能在需要时恢复到先前状态的情况。它提供了一种有效的状态管理机制,使得对象能够保留历史状态,并在需要时进行恢复,常用于需要支持撤销/重做、历史记录管理和状态保存等功能的应用程序或系统中。
6.6.总结
- 状态保存与恢复:备忘录模式允许在不破坏对象封装性的情况下,捕获和保存其内部状态,并能够随后恢复到先前的状态。
- 封装性良好:备忘录对象封装了状态信息,并由发起人对象管理,对外部对象隐藏了状态细节,提高了对象的封装性。
- 历史状态管理:通过备忘录模式,可以建立状态历史记录,提供了一种有效的状态管理机制,使得对象状态的管理更加灵活。
7.观察者模式(Observer)
7.1.定义
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
理解:观察者模式它建立了一种一对多的依赖关系,让多个观察者对象同时监听并且被通知目标对象的任何变化或事件。
在观察者模式中,有两个主要角色:
1. Subject(目标):它是被观察的对象,它包含了观察者对象列表,以及增加、删除和通知观察者的方法。当目标的状态发生变化时,会通知所有注册的观察者。
2. Observer(观察者):观察者是监听目标对象状态变化的对象。当目标对象发生变化时,观察者会收到通知,并进行相应的更新操作。
这种模式可以被用来构建松耦合的系统,因为目标对象和观察者对象之间不直接通信,而是通过订阅/通知机制。这样目标对象的变化不会直接影响到观察者对象,使得系统更加灵活和可维护。
观察者模式常用于需要触发复杂联动操作或通知广播的场景,例如图形界面中的事件处理、消息通知机制等。
7.2.结构
观察者模式包含如下角色:
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者
7.3.时序图
7.4.代码实现
场景1:当用户在一个社交平台上关注某个用户时,被关注的用户发布新的动态消息时,关注者能够接收到通知。
using System; using System.Collections.Generic; // 主题接口 - 被观察的对象 interface ISubject { void Attach(IObserver observer); // 添加观察者 void Detach(IObserver observer); // 移除观察者 void Notify(); // 通知观察者 } // 观察者接口 interface IObserver { void Update(string message); // 更新操作 } // 具体主题 - 被关注的用户 class User : ISubject { private List<IObserver> observers = new List<IObserver>(); private string latestMessage; public void Attach(IObserver observer) { observers.Add(observer); } public void Detach(IObserver observer) { observers.Remove(observer); } public void PostMessage(string message) { latestMessage = message; Notify(); // 发布消息时通知观察者 } public void Notify() { foreach (var observer in observers) { observer.Update(latestMessage); // 通知所有关注者 } } } // 具体观察者 - 关注者 class Follower : IObserver { private string name; public Follower(string name) { this.name = name; } public void Update(string message) { Console.WriteLine($"{name} 收到新消息:{message}"); } } class Program { static void Main(string[] args) { User user = new User(); // 创建具体观察者 - 关注者 Follower follower1 = new Follower("小明"); Follower follower2 = new Follower("小红"); // 用户添加关注者 user.Attach(follower1); user.Attach(follower2); // 用户发布消息,关注者会收到通知 user.PostMessage("今天天气真好!"); // 某个关注者取消关注 user.Detach(follower2); // 用户再次发布消息 user.PostMessage("分享一张美景照片。"); } }
例子中,User
作为被观察者对象,Follower
作为观察者对象。用户发布消息时会通知所有关注者,关注者收到通知后进行更新操作(显示新消息)。这样实现了用户发布消息和关注者接收消息的简单社交平台场景。
场景2:当一个商店销售新产品时,它需要通知已经订阅了该产品的顾客。
using System; using System.Collections.Generic; // 主题接口 - 被观察的对象 interface ISubject { void Attach(IObserver observer); // 添加观察者 void Detach(IObserver observer); // 移除观察者 void Notify(); // 通知观察者 } // 观察者接口 interface IObserver { void Update(string productName); // 更新操作 } // 具体主题 - 商店 class Store : ISubject { private List<IObserver> subscribers = new List<IObserver>(); private string latestProduct; public void Attach(IObserver observer) { subscribers.Add(observer); } public void Detach(IObserver observer) { subscribers.Remove(observer); } public void AddProduct(string product) { latestProduct = product; Notify(); // 添加新产品时通知观察者 } public void Notify() { foreach (var subscriber in subscribers) { subscriber.Update(latestProduct); // 通知所有顾客 } } } // 具体观察者 - 顾客 class Customer : IObserver { private string name; public Customer(string name) { this.name = name; } public void Update(string productName) { Console.WriteLine($"{name} 收到新产品消息:{productName}"); } } class Program { static void Main(string[] args) { Store store = new Store(); // 创建具体观察者 - 顾客 Customer customer1 = new Customer("小张"); Customer customer2 = new Customer("小王"); // 商店添加订阅者 store.Attach(customer1); store.Attach(customer2); // 商店发布新产品,订阅者会收到通知 store.AddProduct("新产品A"); // 某个顾客取消订阅 store.Detach(customer2); // 商店再次发布新产品 store.AddProduct("新产品B"); } }
例子中,Store
作为被观察者对象,Customer
作为观察者对象。商店添加新产品时会通知所有订阅了该产品的顾客,顾客收到通知后进行更新操作(显示新产品消息)。这样模拟了商店销售新产品时通知顾客的场景。
7.5.优缺点
优点:
1. 松耦合:观察者模式将被观察者和观察者解耦,被观察者不需要知道观察者的具体细节,只需要知道观察者接口,降低了对象之间的耦合度。
2. 可扩展性:可以根据需求在任何时候增加或删除观察者,而不需要对被观察者进行修改,使得系统更加灵活,易于扩展。
3. 通知机制:观察者模式通过订阅/通知机制,实现了一对多的依赖关系,当被观察对象的状态发生变化时,能够通知所有观察者对象。
4. 支持广播通信:被观察者状态改变时能够通知多个观察者,适用于需要广播通知的场景。
缺点:
1. 可能导致性能问题:当观察者过多或者观察者的处理操作耗时时,可能会影响被观察者的性能,因为被观察者需要遍历通知所有观察者。
2. 循环依赖问题:观察者和被观察者相互持有引用时,容易产生循环依赖,需要谨慎设计。
3. 避免消息过多问题:观察者模式的通知是广播式的,当被观察者状态频繁变化时,可能会导致观察者接收大量的无用通知。
观察者模式适用于场景中的一对多的依赖关系,当一个对象的改变需要通知其他多个对象时,观察者模式是一个很好的选择。但需要注意在设计时避免上述的缺点,合理控制观察者数量和通知频率,避免性能问题和循环依赖。
7.6.使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
7.7.总结
- 观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。观察者模式是一种对象行为型模式。
- 观察者模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
- 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
- 观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。
8.状态模式(State)
8.1.定义
状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
理解:状态模式它允许对象在内部状态改变时改变它的行为。状态模式通过将对象的不同状态封装成独立的类,使得对象在不同状态下拥有不同的行为,并且能够在状态之间切换而不改变其上下文。
在状态模式中,有三个主要角色:
1. Context(环境):它定义了客户感兴趣的接口,维护一个当前状态对象的引用,可以是状态对象的上下文,负责实际使用状态对象处理请求。
2. State(状态):定义了一个接口,封装了与Context的一个特定状态相关的行为。具体的状态类实现了这个接口,每个具体状态类负责对应状态下的行为逻辑。
3. ConcreteState(具体状态):具体状态类实现了状态接口,定义了在状态转移时的具体行为。
状态模式的关键在于根据不同的状态将相应的行为封装在具体状态类中,使得状态之间的转换变得简单灵活。对象的行为会随着状态的改变而改变,但对象本身的接口不会改变,这符合开闭原则。
这种模式适用于当一个对象的行为取决于它的状态,并且需要根据状态不同而有不同行为时,或者对象的行为包含大量的条件语句时,能够有效地简化代码结构和提高可维护性。
8.2.结构
状态模式包含如下角色:
- Context: 环境类
- State: 抽象状态类
- ConcreteState: 具体状态类
8.3.时序图
8.4.代码实现
场景1:当一个订单在不同的状态下需要进行不同的处理时,订单状态包括待处理、已确认、已发货和已完成等状态,每个状态下订单的行为不同。
using System; // 订单状态接口 interface IOrderState { void ProcessOrder(); // 处理订单 } // 具体状态类 - 待处理状态 class PendingState : IOrderState { public void ProcessOrder() { Console.WriteLine("订单处于待处理状态,等待确认..."); } } // 具体状态类 - 已确认状态 class ConfirmedState : IOrderState { public void ProcessOrder() { Console.WriteLine("订单已确认,准备发货..."); } } // 具体状态类 - 已发货状态 class ShippedState : IOrderState { public void ProcessOrder() { Console.WriteLine("订单已发货,正在配送中..."); } } // 具体状态类 - 已完成状态 class CompletedState : IOrderState { public void ProcessOrder() { Console.WriteLine("订单已完成,感谢您的购买!"); } } // 订单类 - Context环境 class Order { private IOrderState currentState; // 当前订单状态 public Order() { currentState = new PendingState(); // 初始状态为待处理状态 } public void SetState(IOrderState state) { currentState = state; } public void ProcessOrder() { currentState.ProcessOrder(); // 根据当前状态处理订单 } } class Program { static void Main(string[] args) { Order order = new Order(); // 订单处于待处理状态 order.ProcessOrder(); // 确认订单,状态变为已确认 order.SetState(new ConfirmedState()); order.ProcessOrder(); // 发货,状态变为已发货 order.SetState(new ShippedState()); order.ProcessOrder(); // 完成订单,状态变为已完成 order.SetState(new CompletedState()); order.ProcessOrder(); } }
例子中,Order
类是环境类,持有一个当前状态对象。不同的状态对应不同的具体状态类,每个具体状态类实现了订单状态接口,并根据自身状态提供了不同的处理方法。通过改变订单状态并处理订单,模拟了订单在不同状态下的处理场景。
场景2:当一个游戏角色在不同状态下具有不同的行为时,游戏角色包括正常状态、加速状态和受伤状态,每个状态下角色的行为不同。
using System; // 角色状态接口 interface ICharacterState { void Move(); // 移动行为 } // 具体状态类 - 正常状态 class NormalState : ICharacterState { public void Move() { Console.WriteLine("角色正常移动"); } } // 具体状态类 - 加速状态 class SpeedUpState : ICharacterState { public void Move() { Console.WriteLine("角色加速移动"); } } // 具体状态类 - 受伤状态 class InjuredState : ICharacterState { public void Move() { Console.WriteLine("角色受伤,移动缓慢"); } } // 角色类 - Context环境 class Character { private ICharacterState currentState; // 当前角色状态 public Character() { currentState = new NormalState(); // 初始状态为正常状态 } public void SetState(ICharacterState state) { currentState = state; } public void Move() { currentState.Move(); // 根据当前状态移动角色 } } class Program { static void Main(string[] args) { Character character = new Character(); // 角色处于正常状态 character.Move(); // 角色进入加速状态 character.SetState(new SpeedUpState()); character.Move(); // 角色受伤,状态变为受伤状态 character.SetState(new InjuredState()); character.Move(); } }
例子中,Character
类是环境类,持有一个当前状态对象。不同的状态对应不同的具体状态类,每个具体状态类实现了角色状态接口,并根据自身状态提供了不同的移动行为。通过改变角色状态并移动角色,
模拟了游戏角色在不同状态下的移动行为。
8.5.优缺点
优点:
1. 封装状态:将不同状态的行为封装在不同的状态类中,使得每个状态的变化都成为一个独立的模块,易于管理和维护。
2. 简化条件逻辑:避免了大量的条件语句,因为状态模式使得对象的行为与其状态相关联,状态变化时对象的行为也相应改变。
3. 开闭原则:新状态的加入不会影响现有状态类的代码,易于扩展。
4. 符合单一职责原则: 每个状态都有对应的类,使得各个状态的逻辑分离清晰,每个类只负责自己的行为。
缺点:
1. 类数量增多:如果状态过多,会产生大量的具体状态类,导致类的数量增加,维护成本增加。
2. 逻辑分散:如果状态转换逻辑复杂,状态之间相互影响,可能会使代码变得复杂且难以理解。
3. 状态切换开销:对象状态切换时需要改变对象的状态,可能引入一定的开销。
状态模式在需要根据状态改变对象行为的情况下非常有用,能够提高代码的可维护性和可扩展性。然而,在应用状态模式时,需要根据具体情况权衡好处和代价,避免过度设计。
8.6.使用场景
- 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
- 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。
8.7.总结
- 状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象,状态模式是一种对象行为型模式。
- 状态模式包含三个角色:环境类又称为上下文类,它是拥有状态的对象,在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象,可以定义初始状态;抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的行为;具体状态类是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
- 状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
- 状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。
- 状态模式适用情况包括:对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为;代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。
9.策略模式(Strategy)
9.1.定义
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
理解:策略模式是一种行为型设计模式,它允许在运行时选择算法的行为。它通过将每个算法封装成单独的类,使得这些算法可以相互替换,而且使得它们可以独立于客户端而变化。
在策略模式中,有三个主要角色:
1. Context(上下文):上下文类是包含了一个策略接口的引用,通过这个接口与策略对象交互,可以让客户端选择不同的算法。
2. Strategy(策略):策略接口是定义了一个算法族的公共接口,具体的策略类实现了这个接口,每个策略类封装了一个具体的算法。
3. ConcreteStrategy(具体策略):具体策略类实现了策略接口,包含了具体的算法实现。
策略模式的核心思想是将不同的算法封装成独立的策略类,然后在上下文中根据需要动态选择不同的策略,使得算法的变化不影响到使用算法的客户端。
策略模式适用于当一个系统有多种算法,且这些算法经常需要在运行时切换,或者当一个对象有多种行为并且需要动态选择时,能够提高系统的灵活性、可维护性和可扩展性。
9.2.结构
策略模式包含如下角色:
- Context: 环境类
- Strategy: 抽象策略类
- ConcreteStrategy: 具体策略类
9.3.时序图
9.4.代码实现
场景1:当一个商场针对不同类型的顾客提供不同的折扣策略时,商场根据顾客的类型(普通顾客、会员或高级会员)应用不同的折扣策略。
using System; // 折扣策略接口 interface IDiscountStrategy { double ApplyDiscount(double amount); } // 具体折扣策略类 - 普通顾客折扣策略 class RegularCustomerDiscount : IDiscountStrategy { public double ApplyDiscount(double amount) { return amount; // 普通顾客不打折 } } // 具体折扣策略类 - 会员折扣策略 class MemberDiscount : IDiscountStrategy { public double ApplyDiscount(double amount) { return amount * 0.9; // 会员打9折 } } // 具体折扣策略类 - 高级会员折扣策略 class VIPDiscount : IDiscountStrategy { public double ApplyDiscount(double amount) { return amount * 0.8; // 高级会员打8折 } } // 上下文类 - 商场销售 class ShoppingMall { private IDiscountStrategy _discountStrategy; // 折扣策略 public void SetDiscountStrategy(IDiscountStrategy strategy) { _discountStrategy = strategy; } public double CalculateFinalAmount(double amount) { // 应用折扣策略 return _discountStrategy.ApplyDiscount(amount); } } class Program { static void Main(string[] args) { ShoppingMall mall = new ShoppingMall(); double originalAmount = 1000; // 普通顾客购买 mall.SetDiscountStrategy(new RegularCustomerDiscount()); double finalAmountRegular = mall.CalculateFinalAmount(originalAmount); Console.WriteLine($"普通顾客购买,原价:{originalAmount},折后价:{finalAmountRegular}"); // 会员购买 mall.SetDiscountStrategy(new MemberDiscount()); double finalAmountMember = mall.CalculateFinalAmount(originalAmount); Console.WriteLine($"会员购买,原价:{originalAmount},折后价:{finalAmountMember}"); // 高级会员购买 mall.SetDiscountStrategy(new VIPDiscount()); double finalAmountVIP = mall.CalculateFinalAmount(originalAmount); Console.WriteLine($"高级会员购买,原价:{originalAmount},折后价:{finalAmountVIP}"); } }
例子中,ShoppingMall
类是上下文类,负责设置折扣策略并计算最终价格。不同的折扣策略对应不同的具体策略类,每个具体策略类实现了IDiscountStrategy
接口,并根据具体的折扣逻辑提供不同的打折方法。通过设置不同的折扣策略,商场可以根据顾客的类型应用不同的折扣,实现了动态切换不同的算法。
场景2:当一个图像处理应用根据用户选择的滤镜不同,对图像进行不同的处理时,这个图像处理应用支持黑白滤镜、怀旧滤镜和模糊滤镜。
using System; // 策略接口 - 滤镜策略 interface IFilterStrategy { void ApplyFilter(string image); } // 具体策略类 - 黑白滤镜 class BlackAndWhiteFilter : IFilterStrategy { public void ApplyFilter(string image) { Console.WriteLine($"应用黑白滤镜于图像: {image}"); } } // 具体策略类 - 怀旧滤镜 class SepiaFilter : IFilterStrategy { public void ApplyFilter(string image) { Console.WriteLine($"应用怀旧滤镜于图像: {image}"); } } // 具体策略类 - 模糊滤镜 class BlurFilter : IFilterStrategy { public void ApplyFilter(string image) { Console.WriteLine($"应用模糊滤镜于图像: {image}"); } } // 上下文类 - 图像处理应用 class ImageProcessingApp { private IFilterStrategy _filterStrategy; // 滤镜策略 public void SetFilterStrategy(IFilterStrategy strategy) { _filterStrategy = strategy; } public void ApplyFilter(string image) { _filterStrategy.ApplyFilter(image); } } class Program { static void Main(string[] args) { ImageProcessingApp imageApp = new ImageProcessingApp(); string selectedImage = "example.jpg"; // 假设要处理的图像 // 应用黑白滤镜 imageApp.SetFilterStrategy(new BlackAndWhiteFilter()); imageApp.ApplyFilter(selectedImage); // 应用怀旧滤镜 imageApp.SetFilterStrategy(new SepiaFilter()); imageApp.ApplyFilter(selectedImage); // 应用模糊滤镜 imageApp.SetFilterStrategy(new BlurFilter()); imageApp.ApplyFilter(selectedImage); } }
例子中,ImageProcessingApp
类是上下文类,负责设置滤镜策略并应用相应的滤镜。不同的滤镜对应不同的具体策略类,每个具体策略类实现了IFilterStrategy
接口,并根据具体的滤镜效果提供不同的处理方法。通过设置不同的滤镜策略,图像处理应用可以根据用户选择应用不同的滤镜,实现了动态切换不同的算法。
场景3:有一个图形绘制系统,该系统支持绘制不同形状(圆形、矩形、三角形等)。不同的图形绘制方式可以视为不同的策略。
using System; // 策略接口 - 绘制策略 interface IDrawStrategy { void Draw(); } // 具体策略类 - 绘制圆形 class DrawCircleStrategy : IDrawStrategy { public void Draw() { Console.WriteLine("绘制圆形"); } } // 具体策略类 - 绘制矩形 class DrawRectangleStrategy : IDrawStrategy { public void Draw() { Console.WriteLine("绘制矩形"); } } // 具体策略类 - 绘制三角形 class DrawTriangleStrategy : IDrawStrategy { public void Draw() { Console.WriteLine("绘制三角形"); } } // 上下文类 - 图形绘制 class ShapeDrawer { private IDrawStrategy _drawStrategy; // 绘制策略 public void SetDrawStrategy(IDrawStrategy strategy) { _drawStrategy = strategy; } public void DrawShape() { // 使用策略绘制图形 _drawStrategy.Draw(); } } class Program { static void Main(string[] args) { ShapeDrawer drawer = new ShapeDrawer(); // 绘制圆形 drawer.SetDrawStrategy(new DrawCircleStrategy()); drawer.DrawShape(); // 绘制矩形 drawer.SetDrawStrategy(new DrawRectangleStrategy()); drawer.DrawShape(); // 绘制三角形 drawer.SetDrawStrategy(new DrawTriangleStrategy()); drawer.DrawShape(); } }
例子中,ShapeDrawer
类是上下文类,负责设置绘制策略并执行绘制操作。不同的绘制策略对应不同的具体策略类,每个具体策略类实现了IDrawStrategy
接口,并根据具体的绘制逻辑提供不同的绘制方法。通过设置不同的绘制策略,图形绘制系统可以在运行时动态选择不同的绘制方式,实现了策略模式的灵活性和可扩展性。
9.5.优缺点
优点:
1. 可扩展性:策略模式允许在运行时动态地添加、删除或更改算法,而无需修改上下文或客户端代码。
2. 易于维护:每个具体策略类都是相对独立的,易于理解、修改和测试。
3. 减少代码重复:策略模式可以避免使用大量的条件语句或switch语句,因此减少了代码重复。
4. 符合开闭原则:可以通过添加新的策略类来扩展系统的行为,而无需修改现有代码。
5. 解耦性良好:策略模式将算法与使用算法的客户端分离,降低了彼此之间的依赖关系。
缺点:
1. 增加类的数量:每个具体策略都需要一个独立的类,如果策略较多,会导致类的数量增加。
2. 客户端必须知晓策略:客户端需要了解不同的策略类,选择合适的策略并将其传递给上下文,这可能增加客户端的复杂性。
3. 上下文选择策略的责任:上下文类需要选择合适的策略,可能需要依赖外部信息来做出选择,这可能使得上下文类本身变得复杂。
策略模式在需要动态地选择算法或行为时非常有用,能够提高代码的灵活性和可维护性。然而,要根据具体情况权衡使用,避免过度使用策略模式。
9.6.使用场景
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
9.7.总结
- 在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式。策略模式是一种对象行为型模式。
- 策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在抽象策略类中定义的算法。
- 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。
- 策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。
- 策略模式适用情况包括:在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;一个系统需要动态地在几种算法中选择一种;避免使用难以维护的多重条件选择语句;希望在具体策略类中封装算法和与相关的数据结构。
10.模板方法模式(Template Method)
10.1.定义
模板方法模式(Template Method Pattern):一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
理解:模板方法模式是一种行为型设计模式。它允许定义一个算法的骨架,在方法中定义了算法的结构,但将一些步骤的具体实现延迟到子类中。这样可以在不改变算法整体结构的情况下,允许子类重新定义特定步骤的实现。
具体来说,模板方法模式包含一个抽象类,该类定义了一个模板方法作为算法的主要骨架,该方法包含一系列步骤的实现。这些步骤可以是抽象的或默认实现,用于完成算法中的不同阶段或操作。子类可以通过重写这些步骤来提供特定的实现,从而定制算法的行为,但整体算法的流程结构保持不变。
模板方法模式使得算法骨架固定,但某些步骤的具体实现可以在子类中根据需求灵活变化。这样的设计模式有助于减少重复代码,提高代码的可维护性和可扩展性,同时允许统一管理算法的整体结构。
10.2.结构
模板方法模式包含如下角色:
- Abstract Class:抽象类/抽象模板,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- Concrete Class: 具体子类/具体实现,具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
10.3.代码实现
场景:如糕点的规范制作
using System; // 抽象类定义了糕点制作的模板方法 abstract class PastryMaker { // 制作糕点的模板方法,定义了制作糕点的步骤 public void MakePastry() { PrepareIngredients(); MixIngredients(); Bake(); Cool(); if (AddToppings()) { AddToppings(); } } // 准备材料 public abstract void PrepareIngredients(); // 搅拌材料 public abstract void MixIngredients(); // 烘烤 public abstract void Bake(); // 冷却 public abstract void Cool(); // 添加额外的配料(可选步骤) public virtual bool AddToppings() { return false; } } // 具体的糕点类 - 蛋糕 class CakeMaker : PastryMaker { public override void PrepareIngredients() { Console.WriteLine("准备蛋糕原料"); } public override void MixIngredients() { Console.WriteLine("搅拌蛋糕材料"); } public override void Bake() { Console.WriteLine("烘烤蛋糕"); } public override void Cool() { Console.WriteLine("冷却蛋糕"); } public override bool AddToppings() { Console.WriteLine("添加蛋糕装饰"); return true; } } // 具体的糕点类 - 饼干 class CookieMaker : PastryMaker { public override void PrepareIngredients() { Console.WriteLine("准备饼干原料"); } public override void MixIngredients() { Console.WriteLine("搅拌饼干材料"); } public override void Bake() { Console.WriteLine("烘烤饼干"); } public override void Cool() { Console.WriteLine("冷却饼干"); } } class Program { static void Main(string[] args) { Console.WriteLine("制作蛋糕:"); PastryMaker cake = new CakeMaker(); cake.MakePastry(); Console.WriteLine("\n制作饼干:"); PastryMaker cookie = new CookieMaker(); cookie.MakePastry(); } }
抽象类 PastryMaker
中的 MakePastry()
方法规定了制作糕点的步骤流程,而具体的糕点类(如 CakeMaker
和 CookieMaker
)则根据自己的需求实现了这些步骤。
这种设计允许在不同的糕点制作过程中共享通用步骤,同时又能够在特定类型的糕点制作中灵活地进行个性化定制。
10.4.优缺点
优点:
1. 代码复用性高:定义了一个算法骨架,具体步骤由子类实现,提高了代码的复用性。
2. 结构清晰:将通用步骤放入抽象类中,子类只需实现特定步骤,使得整体结构更清晰。
3. 便于扩展:子类可以根据需要扩展、重写特定步骤,灵活性较高。
4. 符合开闭原则:可以方便地添加新的实现类,不需要修改现有抽象类的代码。
缺点:
1. 限制灵活性:如果某些步骤对于所有子类都是相同的,但是又需要灵活修改,可能需要修改抽象类,影响其他子类。
2. 增加了类的数量:每个子类都需要实现抽象类中的方法,可能会导致类的数量增加,增加了系统复杂度。
要合理运用模板方法模式,确保其中的通用步骤在绝大多数子类中都是一致的,同时保持对扩展的支持,避免滥用导致系统变得过于复杂。
10.5.使用场景
- 定义一个算法骨架:当需要定义一个算法的骨架,但允许具体步骤由子类实现时,模板方法能够提供一个通用的模板,定义算法的流程。
- 多个子类共享通用步骤:如果多个类有一些共同的行为,但又有部分细节上的差异,可以将共通的部分放在抽象类中实现,让子类只需实现各自的特定步骤。
- 控制算法的流程:当需要控制算法的执行流程,但不希望子类重写整个算法时,可以使用模板方法模式。
- 遵循开闭原则:在需要保持算法整体结构稳定的情况下,允许细节上的扩展,满足开闭原则,模板方法模式是一个不错的选择。
当需要定义一个算法的基本结构并允许其子类根据需要扩展或修改特定步骤时,可以考虑使用模板方法模式。
10.6.总结
- 定义算法骨架:模板方法模式定义了算法的骨架结构,将算法的通用部分放在抽象类中的模板方法中实现,而将具体步骤延迟到子类中实现。
- 重用性和扩展性:允许子类通过继承抽象类,并根据需要重写或者补充算法的特定步骤,从而提高代码的重用性和扩展性。
- 控制算法流程:抽象类中的模板方法定义了算法的流程和顺序,能够控制算法执行的整体过程,但又允许子类根据需要改变或扩展部分步骤。
- 遵循设计原则:符合开闭原则,允许系统在不修改原有算法结构的情况下,增加新的算法步骤或变体。
- 分离通用和特定部分:将算法中的通用部分与具体实现分离,使得算法骨架更清晰,易于理解和维护。
- 实现算法的可变部分:模板方法模式允许在子类中灵活地定义或重写具体实现步骤,使得算法的部分内容可以在运行时改变。
11.访问者模式(Visitor)
11.1.定义
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
理解:访问者模式它允许你定义一系列操作(访问者),并在不改变元素类的情况下将这些操作应用到对象结构中的元素。这种模式分离了数据和操作,允许你添加新的操作而无需修改现有元素类的结构。
在访问者模式中,有两个核心概念:访问者和元素。访问者定义了可能对各种对象执行的操作,而元素则允许访问者访问自己,并根据需要调用访问者的方法。
这个模式的关键在于可以为一个对象结构(例如一个复杂的对象树或集合)定义一系列操作,同时还可以保持这些操作与对象结构本身的无关性。这种方式使得在不同的元素类上执行相同的操作变得简单,并且可以通过添加新的访问者来扩展操作,而无需修改现有元素类。
访问者模式能够分离算法和对象结构,允许你在不改变对象结构的情况下为其添加新的操作,从而提高了系统的灵活性和可扩展性。
11.2.结构
访问者模式包含如下角色:
- Visitor:抽象访问者,定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- ConcreteVisitor:具体访问者,实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- Element:抽象元素,声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- ConcreteElement:具体元素,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- Object Structure:对象结构,是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
11.3.代码实现
场景:假设我们有不同类型的动物(如猫和狗),我们希望对它们进行不同类型的检查。
首先,我们创建一个访问者接口,表示访问者可以对不同类型的动物执行不同的检查操作:
// 访问者接口 public interface IAnimalVisitor { void VisitCat(Cat cat); void VisitDog(Dog dog); }
接着,我们定义两种动物类(猫和狗),并在其中实现接受访问者的方法:
// 猫类 public class Cat { public void Accept(IAnimalVisitor visitor) { visitor.VisitCat(this); } } // 狗类 public class Dog { public void Accept(IAnimalVisitor visitor) { visitor.VisitDog(this); } }
现在,我们创建不同的访问者来执行特定的检查操作:
// 具体的访问者类 public class Veterinarian : IAnimalVisitor { public void VisitCat(Cat cat) { Console.WriteLine("兽医检查了猫。"); } public void VisitDog(Dog dog) { Console.WriteLine("兽医检查了狗。"); } } public class Groomer : IAnimalVisitor { public void VisitCat(Cat cat) { Console.WriteLine("美容师检查了猫。"); } public void VisitDog(Dog dog) { Console.WriteLine("美容师检查了狗。"); } }
最后,我们在客户端代码中使用访问者模式:
class Program { static void Main(string[] args) { var cat = new Cat(); var dog = new Dog(); var veterinarian = new Veterinarian(); var groomer = new Groomer(); cat.Accept(veterinarian); dog.Accept(veterinarian); cat.Accept(groomer); dog.Accept(groomer); } }
例子中,我们创建了不同的访问者(兽医和美容师),并让它们对猫和狗执行不同的操作,而无需更改动物类的代码。这展示了访问者模式的灵活性,可以轻松地为对象结构添加新的操作。
11.4.优缺点
优点:
1. 分离关注点:访问者模式将数据结构与数据操作分离开来,使得添加新的操作不影响现有数据结构,反之亦然。
2. 增加新的操作容易:通过增加新的访问者类,可以很容易地在不修改现有代码的情况下添加新的操作。
3. 类间关系清晰:访问者模式将数据结构和数据操作的关系清晰定义,使得代码更易于理解和维护。
缺点:
1. 增加新的数据结构困难:如果需要在现有的访问者中添加新的数据结构,可能需要修改所有的访问者类,这样会增加维护成本。
2. 违反封装性:可能会破坏对象的封装性,因为访问者模式要求数据结构暴露自己的内部细节给访问者。
虽然访问者模式有其优点,但它也需要在设计时慎重考虑,确保它符合当前系统的需求并且能够方便地进行维护和扩展。
11.5.使用场景
- 对象结构稳定,但操作多变:当对象结构相对稳定(很少变化),但需要对其进行多种不同的操作时,可以考虑使用访问者模式。这种情况下,访问者模式可以使得增加新操作更为简单,而不会影响现有对象结构。
- 操作依赖于对象的具体类:如果系统中有多个不同的对象类型,且需要对这些类型执行相似但不同的操作,访问者模式可以将这些操作封装在不同的访问者类中。
- 数据结构和操作相对固定:当数据结构不经常变化,但需要对其执行多种不同操作时,访问者模式能够更好地管理这种情况。
访问者模式适用于需要对稳定的对象结构进行多种不同操作的场景。
11.6.总结
- 分离数据结构和操作:访问者模式旨在将数据结构和操作分离开来,使得可以独立添加新的操作而无需修改现有的数据结构。
- 适用于稳定的对象结构:最适用于对象结构相对稳定但操作需求多变的场景。当对象结构不经常变化,但需要多种不同操作时,访问者模式更为合适。
- 易于增加新操作:通过添加新的访问者类,可以轻松扩展新的操作,而不会影响已有代码。
- 不利于增加新的数据结构:若需要增加新的数据结构,可能需要修改所有现有的访问者类,这会增加维护成本。
- 对象封装可能受影响:为了让访问者能够访问数据结构的内部,可能需要暴露对象的一些细节,这会破坏对象的封装性。
- 操作集中在访问者类中:访问者模式将操作分散到不同的访问者类中,可能会导致代码可读性和维护性下降。
标签:状态,大法,对象,void,模式,new,设计模式,甩锅,public From: https://www.cnblogs.com/hxjcore/p/17952596