命令模式
原理:将一个请求封装成一个对象,从而允许你使用不同的请求、队列或日志将客户端参数化。同时支持请求操作的撤销与恢复。命令就是面向对象化的回调。亦可理解为“后悔药”
作用 :“撤销”与”再做”最常见的便是用在输入控制上。我们可以将输入的信号转换成对应的指令,存入命令队列中,依次执行。多用于 服务器与客户端的开发
例:红警(红色警戒)中训练美国大兵的命令、当点击时,开始倒计时产生大兵,再次点击时,显示数字“2”,表示要训练两个大兵也就是第二个命令被暂存当右键点击时,命令还可以撤销,数字由“2”变为了“1”,也就是第二个命令被撤销了
如果当用户删掉了某一个物体,下一刻他要执行撤销时,会发现那个物体的对象已不存在,这样的话撤销操作就会出现找不到对象的问题,所以根据克隆对象的方式,当用户删掉物体再还原时,我们并非还原他原本的那个物体,而是根据操作记录里面记载的对象删除前的属性重新克隆一个物体。
(1)第一种实现原理:
首先定义一个所有命令的父类( CommandBase)、有且仅有两个操作,但是只在父类中进行描述、在子类中进行实现、因为有的命令是不一样的
- 执行命令
- 撤销命令
//==========================
// - FileName: BaseCommand.cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 执行以及撤销
//==========================
public class BaseCommand : object
{
//命令描述
private string _commandDescribe;
public string CommandDescribe
{
set {
_commandDescribe = value;
}
get {
return _commandDescribe;
}
}
//执行命令
public virtual void ExecuteCommand()
{
}
//撤销命令
public virtual void RevocationCommand()
{
}
}
针对不同的对象,定制各自的命令类****这里我暂时只针对UGUI的InputField和角色Player进行命令收集,通过命令收集者来监听对象的一举一动,记录每一条命令的属性,比如InputField的操作,我们需要的属性也就只有一个,那就是InputField的输入值,所以将之纳为命令的收集目标即可。
//==========================
// - FileName: InputFieldCommand.cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 具体实现
//==========================
using UnityEngine.UI;
/// <summary>
/// InputField操作的命令
/// </summary>
public class InputFieldCommand : BaseCommand
{
#region 命令操作、撤销所涉及到的属性
//目标
private InputField _commandTarget;
//目标的值
private string _commandValue;
#endregion
public InputFieldCommand(InputField commandTarget, string commandValue, string commandDescribe)
{
_commandTarget = commandTarget;
_commandValue = commandValue;
CommandDescribe = commandDescribe;
}
/// <summary>
/// 执行命令
/// </summary>
public override void ExecuteCommand()
{
base.ExecuteCommand();
_commandTarget.text = _commandValue;
}
/// <summary>
/// 撤销命令
/// </summary>
public override void RevocationCommand()
{
base.RevocationCommand();
_commandTarget.text = _commandValue;
}
}
我暂时只记录他的移动命令,也就是说只有一个移动位置的属性需要被命令收集者监测。
//==========================
// - FileName: PlayerCommand.cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 主角命令
//==========================
using UnityEngine;
public class PlayerCommand : BaseCommand
{
#region 命令操作、撤销所涉及到的属性
//目标
private Player _commandTarget;
//目标的位置
private Vector3 _commandPosition;
#endregion
public PlayerCommand(Player commandTarget, Vector3 commandPosition, string commandDescribe)
{
_commandTarget = commandTarget;
_commandPosition = commandPosition;
CommandDescribe = commandDescribe;
}
/// <summary>
/// 执行命令
/// </summary>
public override void ExecuteCommand()
{
base.ExecuteCommand();
_commandTarget.transform.position = _commandPosition;
}
/// <summary>
/// 撤销命令
/// </summary>
public override void RevocationCommand()
{
base.RevocationCommand();
_commandTarget.transform.position = _commandPosition;
}
}
命令收集者
//==========================
// - FileName: CommandManager.cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 命令收集
//==========================
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 命令收集者
/// </summary>
public class CommandManager
{
/// 命令集
private List<BaseCommand> CommandSet;
public CommandManager()
{
CommandSet = new List<BaseCommand>();
}
/// <summary>
/// 执行新的命令
/// </summary>
public void ExecutiveCommand(BaseCommand command)
{
CommandSet.Add(command);
command.ExecuteCommand();
Debug.Log("执行命令:" + command.CommandDescribe);
}
/// <summary>
/// 撤销上一个命令
/// </summary>
public void RevocationCommand()
{
if (CommandSet.Count > 0)
{
BaseCommand command = CommandSet[CommandSet.Count - 1];
CommandSet.Remove(command);
command.RevocationCommand();
Debug.Log("撤销命令:" + command.CommandDescribe);
}
}
}
角色类
//==========================
// - FileName: Player .cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 角色类
//==========================
using UnityEngine;
public class Player : MonoBehaviour
{
//角色移动事件
public delegate void MoveDelegate(Vector3 pos);
public event MoveDelegate MoveEvent;
/// <summary>
/// 角色移动
/// </summary>
public void Move(Vector3 pos)
{
if (MoveEvent != null)
MoveEvent(pos);
}
}
测试脚本
//==========================
// - FileName: CommandModeTest.cs
// - Created: true.
// - CreateTime: 2020/02/27 23:10:01
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 测试
//==========================
using UnityEngine;
using UnityEngine.UI;
public class CommandModeTest : MonoBehaviour
{
public Camera MainCamera;
public GameObject UIPanel;
public InputField userName;
public InputField passWord;
public Player player;
private CommandManager _commandManager;
private void Awake()
{
Init();
}
private void Init()
{
//定义一个命令收集者
_commandManager = new CommandManager();
//命令收集者:监听userName的值改变操作
userName.onEndEdit.AddListener((string value) =>
{
_commandManager.ExecutiveCommand(new InputFieldCommand(userName, value, "修改用户名输入框的值为 " + value));
});
//命令收集者:监听passWord的值改变操作
passWord.onEndEdit.AddListener((string value) =>
{
_commandManager.ExecutiveCommand(new InputFieldCommand(passWord, value, "修改密码输入框的值为 " + value));
});
//命令收集者:监听player的移动操作
player.MoveEvent += (Vector3 pos) =>
{
_commandManager.ExecutiveCommand(new PlayerCommand(player, pos, "角色移动到 " + pos));
};
}
}
定义一个命令收集者,并对场景中的两个InputField输入框和Player进行操作监听,当目标处在被监听中的事件发生时,记录此时需要记录的属性,并存入命令集。然后我们做一个点击地面让角色移动的简易例子,以便于我们测试。
/// <summary>
/// 点击地面移动角色
/// </summary>
private void PlayerMove()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = MainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit) && hit.transform.name == "Root")
{
player.Move(hit.point);
}
}
}
/// <summary>
/// 撤销操作
/// </summary>
private void RevocationCommand()
{
if (Input.GetKeyDown(KeyCode.Space))
{
_commandManager.RevocationCommand();
}
}
综上所述,命令模式的本质其实就是记录每一步操作为一个可再次访问的对象,当然一般为了解耦合,标准命令模式会将命令的触发者和调用者都分开处理,也就是说一方面只管触发新的命令,另一方面只管将命令分发给他的调用者执行,实现一种高强度的松耦合形式。