参考材料
1. 【UE4 设计模式】策略模式 Strategy Pattern
2. 责任链模式
3. 游戏开发设计模式之责任链模式
4. Unity3D游戏开发设计模式——责任链模式
1. 概述
1.1 描述
$\cdot$ 责任链模式(Chain of Responsibility Pattern) 为请求创建了一个接收者对象的链. 这种模式给予请求的类型, 对请求的发送者和接收者进行解耦. 这种类型的设计模式属于行为型模式.
$\\$ 责任链模式通过将多个处理器(处理对象) 以链式结构连接起来, 使得请求沿着这条链传递, 直到有一个处理器处理该请求为止.
$\\$ 责任链模式允许多个对象都有机会处理请求, 从而避免请求的发送者和接收者之间的耦合关系. 将这些对象连成一条链, 并沿着这条链传递请求.
1.2 套路
$\cdot$ 实现方式:
$\\$ 1) 定义处理者接口: 所有处理者必须实现同一个接口.
$\\$ 2) 创建具体处理者: 实现接口的具体类, 包含请求处理逻辑和指向链中下一个处理者的引用.
$\cdot$ 关键代码:
$\\$ 1) Handler接口: 定义一个方法用于处理请求.
$\\$ 2) ConcreteHandler类: 实现Handler接口, 包含请求处理逻辑和对下一个处理者的引用.
$\cdot$ 结构:
$\\$ 主要涉及到以下几个核心角色:
$\\$ 1) 抽象处理者(Handler): 定义一个处理请求的接口, 通常包含一个处理请求的方法(如handleRequest) 和一个指向下一个处理者的引用(后继者).
$\\$ 2) 具体处理者(ConcreteHandler): 实现了抽象处理者接口, 负责处理请求. 如果能够处理该请求, 则直接处理; 否则, 将请求传递给下一个处理者.
$\\$ 3) 客户端(Client): 创建处理者对象, 并将它们连接成一条责任链. 通常, 客户端只需要将请求发送给责任链的第一个处理者, 无需关心请求的具体处理过程.
1.3 使用场景
$\cdot$ 当有多个对象可以处理请求, 且具体由哪个对象处理由运行时决定时.
$\cdot$ 当需要向多个对象中的一个提交请求, 而不想明确指定接收者时.
1.4 优缺点
$\cdot$ 优点:
$\\$ 1) 降低耦合度: 发送者和接收者之间解耦.
$\\$ 2) 简化对象: 对象不需要知道链的结构.
$\\$ 3) 灵活性: 通过改变链的成员或顺序, 动态地新增或删除责任.
$\\$ 4) 易于扩展: 增加新的请求处理类很方便.
$\cdot$ 缺点:
$\\$ 1) 请求未被处理: 不能保证请求一定会被链中的某个处理者接收.
$\\$ 2) 性能影响: 可能影响系统性能, 且调试困难, 可能导致循环调用.
$\\$ 3) 难以观察: 运行时特征不明显, 可能妨碍除错.
2. 游戏开发应用实例
在游戏开发中, 责任链模式可以用于事件处理, 状态管理等方面. 例如, 在游戏中, 不同的事件(如按键按下, 鼠标点击等) 可以通过责任链模式传递给不同的处理器进行处理.
$\\$ 责任链模式是一种有效的设计模式, 能够帮助开发者构建松散耦合, 灵活且易于扩展的系统. 通过合理使用责任链模式, 可以显著提升游戏开发的效率和质量.
$\\$ 具体来说, 责任链模式在游戏开发中的应用案例包括:
$\\$ $\cdot$ 游戏事件处理: 在游戏开发中, 事件处理是一个常见的需求. 责任链模式可以用于将不同的事件处理器组织成一条链, 当一个事件发生时, 它会沿着这条链传递, 直到有一个处理器处理了该事件. 这样可以避免将事件处理者和事件本身耦合在一起, 提高了代码的可维护性和可扩展性.
$\\$ $\cdot$ 输入处理: 在游戏开发中, 输入处理是另一个重要的环节. 责任链模式可以用于将不同的输入处理器组织成一条链, 当一个输入事件发生时, 它会沿着这条链传递, 直到有一个处理器处理了该输入事件. 这样可以避免将输入处理器和输入事件耦合在一起, 提高了代码的可维护性和可扩展性.
$\\$ $\cdot$ 状态更新: 在游戏开发中, 状态更新是另一个常见的需求. 责任链模式可以用于将不同的状态更新处理器组织成一条链, 当一个状态更新事件发生时, 它会沿着这条链传递, 直到有一个处理器处理了该状态更新事件. 这样可以避免将状态更新处理器和状态更新事件耦合在一起, 提高了代码的可维护性和可扩展性.
2.1 如何在责任链模式中处理大量处理器以避免性能问题?
在责任链模式中处理大量处理器以避免性能问题, 可以采取以下几种方法:
$\\$ $\cdot$ 使用缓存或其他优化技术: 如果责任链较长或处理者较多, 可以考虑使用缓存或其他优化技术来提高性能. 例如, 可以将一些常见的请求结果缓存起来, 减少不必要的计算和处理时间.
$\\$ $\cdot$ 保持链的简洁性: 尽量避免不必要的处理器, 以保持链的简洁性和高效性. 每个处理器只负责其特定的职责, 避免过度复杂化.
$\\$ $\cdot$ 明确业务需求和节点数量: 在运用责任链模式时, 应明确业务需求, 适合多个处理器依次处理请求且顺序可变的场景. 同时, 遵循单一职责原则, 确保每个节点只处理一件事, 并尽量减少节点的数量.
$\\$ $\cdot$ 文档化链的结构和行为: 为了便于维护和调试, 应详细记录链的结构, 每个处理器的职责以及处理流程. 这有助于在出现问题时快速定位和解决.
2.2 责任链模式与其他设计模式(如观察者模式, 命令模式) 的结合使用有哪些实例?
责任链模式与其他设计模式(如观察者模式, 命令模式) 的结合使用在实际应用中可以实现更复杂和灵活的系统功能. 以下是一些实例:
$\\$ 在一个简单的遥控器控制吊扇的场景中, 吊扇可以有多种转动速度, 也可以被关闭. 在这种情况下, 责任链模式可以用来确定哪个对象处理特定的请求, 而策略模式则可以用来定义不同的转动速度策略. 具体来说, 每个转动速度可以作为一个策略对象, 当遥控器发送请求时, 责任链模式会自动确定并调用相应的策略对象来处理请求.
$\\$ 在一个工作流设计中, 多个对象可以处理一个请求, 而责任链模式可以用来确定哪个对象处理该请求. 命令模式则可以用来封装请求, 使得请求可以在不改变请求类代码的情况下进行参数化, 排队, 记录和撤销. 例如, 在一个任务管理系统中, 不同的任务可以被分配给不同的处理者, 而每个处理者可以使用命令模式来处理任务.
$\\$ 责任链模式可以将发送者和接收者解耦, 并提供更大的灵活性. 观察者模式则可以用来在对象状态改变时通知相关对象. 例如, 在一个消息系统中, 不同的消息处理器可以使用责任链模式来处理不同类型的消息, 而观察者模式则可以用来在消息处理器状态改变时通知相关订阅者.
$\\$ 在一个命令管理系统中, 单例模式可以用来确保只有一个命令管理器实例, 而责任链模式则可以用来确定哪个命令处理特定的请求. 例如, 在一个日志系统中, 不同的日志处理器可以使用责任链模式来处理不同类型的日志记录请求, 而单例模式则可以确保只有一个日志管理器实例.
2.3 在实现责任链模式时,如何确保处理者的顺序正确且高效?
在实现责任链模式时, 确保处理者的顺序正确且高效的方法主要包括以下几个方面:
$\\$ $\cdot$ 动态配置处理者顺序: 通过配置文件, 数据库等方式来动态配置处理者的顺序和条件, 而不需要修改代码. 这样可以灵活地调整处理者的顺序, 以适应不同的业务需求.
$\\$ $\cdot$ 控制处理者的顺序: 在设计和实现中, 要根据实际业务场景的需要进行灵活调整, 以达到最佳的解耦和可扩展性. 通过控制处理者的顺序, 可以确保责任链的执行顺序符合业务需求.
$\\$ $\cdot$ 使用单向链表或集合迭代器实现: 责任链模式的实现方式有单向链表实现和集合迭代器实现, 优化方式可以使用函数式编程和AOP来简化实现和提高可扩展性.
$\\$ $\cdot$ 定义请求接口和维护后继链接: 每个处理者类不仅定义了请求接口, 还维护了后继链接, 并提供默认实现来转发请求. 这样可以降低耦合度并增强灵活性.
$\\$ $\cdot$ 取消传递请求: 处理者可以决定不再沿着链传递请求, 这可高效地取消不必要的处理, 从而提高效率.
$\\$ $\cdot$ 避免循环依赖: 在使用责任链模式时需要注意存在循环依赖的问题, 确保链的结构是合理的.
————————————————
版权声明:本小节内容来源于CSDN博主「数学小师Yq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2302_80644606/article/details/141462770
3. UE5实践
我们创建抽象类FAbstractLogger, 带有详细的日志记录级别. 然后我们创建三种类型的记录器, 都继承了FAbstractLogger. 每个记录器消息的级别是否属于自己的级别, 如果是则相应地打印出来, 否则将不打印并把消息传给下一个记录器.

3.1 步骤1
创建抽象的记录器类.
#pragma once
#include "CoreMinimal.h"
class FAbstractLogger
{
public:
void SetNextLogger(FAbstractLogger* InNextLogger)
{
NextLogger = InNextLogger;
}
FAbstractLogger* GetNextLogger()
{
return NextLogger;
}
void LogMessage(int32 InLevel, const FString& Message)
{
if (Level <= InLevel)
{
Write(Message);
}
if (NextLogger != nullptr)
{
NextLogger->LogMessage(InLevel, Message);
}
}
protected:
virtual void Write(const FString& Message) = 0;
public:
static int32 Info = 1;
static int32 Debug = 2;
static int32 Error = 3;
protected:
int32 Level;
FAbstractLogger* NextLogger;
};
3.2 步骤2
创建继承了该记录器类的实体类.
#pragma once
#pragma once
#include "AbstractLogger.h"
class FConsoleLogger : public FAbstractLogger
{
public:
FConsoleLogger(int32 InLevel) : Level(InLevel)
{
}
virtual void Write(const FString& Message)
{
UE_LOG(LogTemp, Log, TEXT("Standard Console::Logger: %s"), *Message);
}
};
#pragma once
#include "AbstractLogger.h"
class FErrorLogger : public FAbstractLogger
{
public:
FErrorLogger(int32 InLevel) : Level(InLevel)
{
}
virtual void Write(const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Error Console::Logger: %s"), *Message);
}
};
#pragma once
#include "AbstractLogger.h"
class FFileLogger : public FAbstractLogger
{
public:
FFileLogger(int32 InLevel) : Level(InLevel)
{
}
virtual void Write(const FString& Message)
{
UE_LOG(LogTemp, Log, TEXT("File::Logger: %s"), *Message);
}
};
3.3 步骤3
创建不同类型的记录器. 赋予它们不同的错误级别, 并在每个记录器中设置下一个记录器. 每个记录器中的下一个记录器代表的是链的一部分.
#include "ConsoleLogger.h"
#include "ErrorLogger.h"
#include "FileLogger.h"
static FAbstractLogger* GetChainOfLoggers()
{
FAbstractLogger* ErrorLogger = new FErrorLogger(FAbstractLogger::Error);
FAbstractLogger* FileLogger = new FErrorLogger(FAbstractLogger::Debug);
FAbstractLogger* ConsoleLogger = new FErrorLogger(FAbstractLogger::Info);
ErrorLogger->SetNextLogger(FileLogger);
FileLogger->SetNextLogger(ConsoleLogger);
return ErrorLogger;
}
int main()
{
FAbstractLogger* LoggerChain = GetChainOfLoggers();
LoggerChain->LogMessage(FAbstractLogger::Info, TEXT("This is an information."));
LoggerChain->LogMessage(FAbstractLogger::Debug, TEXT("This is a debug level information."));
LoggerChain->LogMessage(FAbstractLogger::Error, TEXT("This is an error information."));
while (LoggerChain != nullptr)
{
FAbstractLogger* NextLogger = LoggerChain->GetNextLogger();
delete LoggerChain;
LoggerChain = NextLogger;
}
LoggerChain = nullptr;
return 0;
}