【UE4 设计模式】命令模式 Command Pattern

概述

描述

  • 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

  • 命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

  • 建造者模式将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无需知道复杂对象的内部组成与装配方式,主需要知道所需的建造者即可。类似工厂方法,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式则返回一系列相关的产品。以汽车为例,工厂方法可以看成不同汽车配件的生成,而建造者模式则可以看成时汽车的组装

    image

套路

  • 客户端 Client 
    命令的发起者。确定接下来要执行什么命令。
  • 调用者 Invoker
     命令的管理者,不关心每个命令具体是做什么内容,根据客户端的指示按序执行命令。
  • 抽象命令 Command
    命令接口协议,确定每个命令需要提供的功能,这里要求每个命令类都提供执行方法。
  • 具体命令 ConcreteCommand
    包含执行一个命令所需的所有上下文信息,例如执行接收者的哪个方法,以及方法所需要的参数,甚至命令作为GUI 显示时的相关信息,例如应该显示的图标路径。具体命令类是命令模式中的核心节点,需要重点理解。
  • 接收者 Receiver:命令所对应任务的实际执行者,位于调用链条的末端。

使用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要将一组操作组合在一起,即支持宏命令
  • 示例
    • 按键、快捷键映射、玩家输入
    • 撤销(Undo)、恢复(Redo),维护命令列表
    • 新手引导

优缺点

  • 优点
    • 降低系统的耦合度。
    • 新的命令可以很容易地加入到系统中。
    • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
    • 可以方便地实现对请求的Undo和Redo。
  • 缺点
    • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

UE4 实践

  • 写一个事件触发器,点击地图地点触发场景传送;点击资料片播放过场动画

  • 创建接收者抽象类、具体类 —— 场景传送、触发剧情

    • 此处抽象类非必要,但实际使用可能会有多个派生类多种 action 成员函数被使用
    // 抽象接收者类 Receiver
    UCLASS()
    class DESIGNPATTERNS_API UCmdReceiver : public UObject
    {
    	GENERATED_BODY()
    public:
    
    	virtual void Action() { check(0 && "You must override this"); }
    };
    
    // 具体接收者类 Receiver —— 场景传送
    UCLASS()
    class DESIGNPATTERNS_API ULevelPortal : public UCmdReceiver
    {
    	GENERATED_BODY()
    public:
    	virtual void Action() override {
    		UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 传送到下一个场景"));
    	}
    };
    
    // 具体接收者类 Receiver —— 资料片播放
    UCLASS()
    class DESIGNPATTERNS_API UCutscene : public UCmdReceiver
    {
    	GENERATED_BODY()
    public:
    	virtual void Action() override {
    		UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 播放剧情动画"));
    	}
    };
    
  • 抽象命令类、具体命令类 —— 场景传送命令、资料片播放命令

    • 此处 接收者 采用了继承方式,因此和具体命令搭起来,代码显得重复。实际使用时,Execute成员函数可能会重载,调用不同类对象进行处理
    // 抽象命令类
    UCLASS(Abstract)
    class DESIGNPATTERNS_API UCommand : public UObject
    {
    	GENERATED_BODY()
    public:
    
    	void SetReceiver(UCmdReceiver* pCmdReceiver) { m_pCmdReceiver = pCmdReceiver; }
    	
    	// 调用接收者的 Action
    	virtual void Execute() { 
    		if (m_pCmdReceiver)
    		{
    			m_pCmdReceiver->Action();
    		}
    	}
    	// virtual void undo()
    
    protected:
    	UCmdReceiver *m_pCmdReceiver;
    };
    
    // 具体命令类 —— 场景传送命令
    UCLASS()
    class DESIGNPATTERNS_API UPortalCommand : public UCommand
    {
    	GENERATED_BODY()
    public:
    
    	// 可重载做些额外的工作
    	//virtual void Execute() override {  }
    };
    
    // 具体命令类 ——  资料片播放命令
    UCLASS()
    class DESIGNPATTERNS_API UCutsceneCommand : public UCommand
    {
    	GENERATED_BODY()
    public:
    
    	// 可重载做些额外的工作
    	//virtual void Execute() override {  }
    };
    
  • 调用者 Invoker

    UCLASS()
    class DESIGNPATTERNS_API ACommandActor : public AActor
    {
    	GENERATED_BODY()
    public:
    
    	void BeginPlay() override {		
    
    		// 创建接收者 场景传送
    		ULevelPortal* LevelPortal = NewObject<ULevelPortal>();
    		
    		// 创建命令对象
    		UPortalCommand* PortalCommand = NewObject<UPortalCommand>();
    		PortalCommand->SetReceiver(LevelPortal);
    		
    		// this 当做调用者 Invoker
    		PortalCommand->Execute();
    
    		// 创建接收者  资料片播放
    		UCutscene* Cutscene = NewObject<UCutscene>();
    
    		// 创建命令对象
    		UCutsceneCommand* CutsceneCommand = NewObject<UCutsceneCommand>();
    		CutsceneCommand->SetReceiver(Cutscene);
    
    		// this 当做调用者 Invoker
    		CutsceneCommand->Execute();
    	}
    };
    
  • 调式输出

    LogTemp: Warning: ULevelPortal::Action 传送到下一个场景
    LogTemp: Warning: UCutscene::Action 播放剧情动画
    

参考

posted @ 2021-06-06 00:35  砥才人  阅读(748)  评论(0编辑  收藏  举报