Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】
目录
Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】
四、责任链模式(Chain of Responsibility Pattern)
一、简单介绍
设计模式 是指在软件开发中为解决常见问题而总结出的一套 可复用的解决方案。这些模式是经过长期实践证明有效的 编程经验总结,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的 抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。
二、命令模式(Command Pattern)
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成对象,从而使得可以使用不同的请求、队列或日志请求,以及支持可撤销的操作。命令模式通常包含四个主要角色:命令(Command)、接收者(Receiver)、请求者(Invoker)和客户端(Client)。命令对象持有请求的详细信息,而接收者则执行这些请求。
1、什么时候使用命令模式
- 需要支持撤销/重做功能时:例如文本编辑器、绘图软件等需要频繁撤销和重做操作的应用。
- 需要将请求发送者与接收者解耦时:当请求的发送者和接收者之间的依赖关系需要降低时,可以使用命令模式。
- 需要记录请求日志时:当需要跟踪请求的历史以进行审计时,命令模式提供了一种优雅的解决方案。
- 需要实现异步请求时:可以将请求放入队列中,并在未来的某个时间点执行。
2、使用命令模式的好处
- 解耦请求与执行:命令模式将请求的发送者与接收者解耦,使得两者之间的关系更加灵活。
- 支持撤销操作:通过将操作封装成命令对象,可以轻松实现撤销(Undo)和重做(Redo)功能。
- 支持日志请求:可以将请求的历史记录进行存储,方便后续审计或分析。
- 可以实现队列请求:可以将命令对象放入队列中,按顺序执行或延迟执行。
3、使用时的注意事项
- 复杂性:命令模式可能会引入额外的复杂性,尤其是在命令对象数量较多时。需要确保命令的设计和管理是清晰的。
- 性能考虑:由于命令模式会创建多个命令对象,因此可能会增加内存开销。在性能敏感的场合,需要谨慎使用。
- 过度设计:对于简单的应用,使用命令模式可能会导致过度设计。在这些情况下,直接的方法调用可能更为简单和有效。
命令模式通过将请求封装为对象,实现了解耦、可撤销性和请求队列等功能。它适用于需要支持撤销、日志记录或异步请求的场合,但在设计时需要考虑到可能引入的复杂性和性能问题。
三、在 Unity 中使用 命令模式
在 Unity 中实现 命令模式 的示例,我们可以创建一个简单的场景,其中玩家可以使用命令来控制一个角色的移动和跳跃。这个示例将演示如何使用命令模式来处理这些操作。
使用命令模式控制角色
参考类图如下:
1、定义命令接口
首先,我们定义一个命令接口,描述执行命令的基本操作。
public interface ICommand
{
void Execute();
void Undo();
}
2、定义接收者类
接下来,我们定义一个接收者类,代表要执行命令的对象(如角色)。
using UnityEngine;
public class PlayerCharacter : MonoBehaviour
{
public void Move(Vector3 direction)
{
transform.position += direction;
Debug.Log($"Moved to: {transform.position}");
}
public void Jump(float height)
{
transform.position += Vector3.up * height;
Debug.Log($"Jumped to: {transform.position}");
}
}
3、实现具体命令类
然后,我们实现具体的命令类,分别用于角色的移动和跳跃操作。
public class MoveCommand : ICommand
{
private PlayerCharacter player;
private Vector3 direction;
public MoveCommand(PlayerCharacter player, Vector3 direction)
{
this.player = player;
this.direction = direction;
}
public void Execute()
{
player.Move(direction);
}
public void Undo()
{
player.Move(-direction); // 反向移动
}
}
public class JumpCommand : ICommand
{
private PlayerCharacter player;
private float height;
public JumpCommand(PlayerCharacter player, float height)
{
this.player = player;
this.height = height;
}
public void Execute()
{
player.Jump(height);
}
public void Undo()
{
player.Move(-Vector3.up * height); // 反向跳跃
}
}
4、实现请求者类
我们实现一个请求者类,用于调用命令。
using System.Collections.Generic;
using UnityEngine;
public class CommandInvoker : MonoBehaviour
{
private Stack<ICommand> commandHistory = new Stack<ICommand>();
public void ExecuteCommand(ICommand command)
{
command.Execute();
commandHistory.Push(command);
}
public void UndoCommand()
{
if (commandHistory.Count > 0)
{
ICommand lastCommand = commandHistory.Pop();
lastCommand.Undo();
}
}
}
5、创建游戏管理器
最后,我们创建一个游戏管理器类,处理用户输入并执行命令。
using UnityEngine;
public class GameManager : MonoBehaviour
{
public PlayerCharacter playerCharacter;
private CommandInvoker commandInvoker;
void Start()
{
commandInvoker = gameObject.AddComponent<CommandInvoker>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
var moveCommand = new MoveCommand(playerCharacter, Vector3.forward);
commandInvoker.ExecuteCommand(moveCommand);
}
else if (Input.GetKeyDown(KeyCode.S))
{
var moveCommand = new MoveCommand(playerCharacter, Vector3.back);
commandInvoker.ExecuteCommand(moveCommand);
}
else if (Input.GetKeyDown(KeyCode.Space))
{
var jumpCommand = new JumpCommand(playerCharacter, 2.0f);
commandInvoker.ExecuteCommand(jumpCommand);
}
else if (Input.GetKeyDown(KeyCode.U))
{
commandInvoker.UndoCommand(); // 撤销上一个命令
}
}
}
6、在 Unity 中测试
- 创建一个带有 Collider 的 Cube 作为玩家角色,并附加
PlayerCharacter
脚本。 - 创建一个空的 GameObject,命名为 GameManager,并附加
GameManager
脚本。 - 运行游戏,使用
W
和S
键控制前后移动,使用Space
键跳跃,使用U
键撤销上一个命令。
7、示例分析
- 命令(ICommand):定义执行和撤销的基本操作。
- 接收者(PlayerCharacter):实现角色的具体操作。
- 具体命令(MoveCommand 和 JumpCommand):封装角色的移动和跳跃逻辑。
- 请求者(CommandInvoker):负责执行和撤销命令。
这个示例展示了如何在 Unity 中实现 命令模式,通过封装角色的操作为命令对象,使得用户能够灵活地控制角色,同时支持撤销操作。这种模式在需要记录和管理用户输入的场景中非常有用。
四、责任链模式(Chain of Responsibility Pattern)
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,使得多个对象都有机会处理请求。将这些对象连接成一条链,并沿着这条链传递请求,直到有对象处理它为止。该模式通常包含四个主要角色:请求(Request)、处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client)。
1、什么时候使用责任链模式
- 需要多个对象处理请求时:当一个请求需要被多个对象处理时,可以使用责任链模式。
- 处理请求的逻辑不明确时:当不确定哪个对象能够处理请求,或者希望多个对象有机会处理请求时,可以采用责任链模式。
- 请求处理的顺序可能变化时:如果请求的处理顺序可能发生变化,责任链模式可以提供更好的灵活性。
- 需要支持可扩展性时:当系统可能需要增加新处理者而不影响现有代码时,责任链模式是一个良好的选择。
2、使用责任链模式的好处
- 降低耦合度:发送者和接收者之间的关系被解耦,发送者不需要知道哪个具体的处理者来处理请求。
- 灵活性:可以动态添加或修改处理者,使得系统更加灵活和可扩展。
- 简化代码:通过将请求处理逻辑分散到多个处理者中,可以使得每个处理者的代码更加简洁,提高代码的可读性。
- 动态处理请求:可以通过改变责任链的结构和顺序来改变请求的处理方式。
4、使用时的注意事项
- 请求处理的顺序:责任链模式可能导致请求处理的顺序变得不易理解,因此需要明确责任链的顺序。
- 链的长度:过长的责任链可能导致性能下降,尤其是在链中的处理者都不处理请求的情况下。
- 处理失败的情况:需要设计好处理失败的情况,以免请求在链中被无效地传递。
- 调试困难:由于请求在链中可能被多个处理者处理,调试请求的流程可能会比较困难。
责任链模式通过将请求和处理者解耦,实现了请求的灵活处理和动态配置。适用于多个对象可能处理请求的场景,但在使用时需要考虑链的顺序、长度及调试难度等因素。
五、在 Unity 中使用 责任链模式
在 Unity 中实现 责任链模式 的示例,我们可以创建一个简单的游戏事件处理系统,其中多个处理者可以处理玩家的不同输入事件,例如跳跃、攻击和防御。这个示例将演示如何使用责任链模式来管理这些事件。
使用责任链模式处理玩家输入事件
参考类图如下:
1、定义请求类
首先,我们定义一个请求类,表示输入事件。
public class InputRequest
{
public string Action { get; }
public InputRequest(string action)
{
Action = action;
}
}
2、定义处理者接口
接下来,我们定义一个处理者接口,包含处理请求的方法。
public interface IInputHandler
{
void SetNextHandler(IInputHandler nextHandler);
void Handle(InputRequest request);
}
3、实现具体处理者类
然后,我们实现几个具体的处理者类,分别用于处理不同的输入事件。
using UnityEngine;
public class JumpHandler : IInputHandler
{
private IInputHandler nextHandler;
public void SetNextHandler(IInputHandler nextHandler)
{
this.nextHandler = nextHandler;
}
public void Handle(InputRequest request)
{
if (request.Action == "Jump")
{
Debug.Log("Handling Jump action");
}
else
{
nextHandler?.Handle(request);
}
}
}
public class AttackHandler : IInputHandler
{
private IInputHandler nextHandler;
public void SetNextHandler(IInputHandler nextHandler)
{
this.nextHandler = nextHandler;
}
public void Handle(InputRequest request)
{
if (request.Action == "Attack")
{
Debug.Log("Handling Attack action");
}
else
{
nextHandler?.Handle(request);
}
}
}
public class DefendHandler : IInputHandler
{
private IInputHandler nextHandler;
public void SetNextHandler(IInputHandler nextHandler)
{
this.nextHandler = nextHandler;
}
public void Handle(InputRequest request)
{
if (request.Action == "Defend")
{
Debug.Log("Handling Defend action");
}
else
{
nextHandler?.Handle(request);
}
}
}
4、创建请求者类
我们创建一个请求者类,负责发送输入请求。
using UnityEngine;
public class InputInvoker : MonoBehaviour
{
private IInputHandler inputHandlerChain;
void Start()
{
// 设置责任链
JumpHandler jumpHandler = new JumpHandler();
AttackHandler attackHandler = new AttackHandler();
DefendHandler defendHandler = new DefendHandler();
jumpHandler.SetNextHandler(attackHandler);
attackHandler.SetNextHandler(defendHandler);
inputHandlerChain = jumpHandler;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
InputRequest jumpRequest = new InputRequest("Jump");
inputHandlerChain.Handle(jumpRequest);
}
else if (Input.GetKeyDown(KeyCode.A))
{
InputRequest attackRequest = new InputRequest("Attack");
inputHandlerChain.Handle(attackRequest);
}
else if (Input.GetKeyDown(KeyCode.D))
{
InputRequest defendRequest = new InputRequest("Defend");
inputHandlerChain.Handle(defendRequest);
}
else if (Input.GetKeyDown(KeyCode.X))
{
InputRequest unknownRequest = new InputRequest("Unknown");
inputHandlerChain.Handle(unknownRequest);
}
}
}
5、在 Unity 中测试
- 创建一个空的 GameObject,命名为 InputManager,并附加
InputInvoker
脚本。 - 运行游戏,按
Space
、A
、D
和X
键测试不同的输入事件。
6、示例分析
- 请求(InputRequest):封装输入事件的信息。
- 处理者(IInputHandler 和具体处理者类):定义如何处理不同的输入事件,形成责任链。
- 请求者(InputInvoker):负责接收输入并将其传递给责任链中的处理者。
这个示例展示了如何在 Unity 中实现 责任链模式,通过将输入事件处理逻辑分散到多个处理者中,使得系统灵活且可扩展。每个处理者负责处理特定的输入事件,并可以根据需求轻松添加或修改处理者。