Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】

目录

Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】

一、简单介绍

二、命令模式(Command Pattern)

1、什么时候使用命令模式

2、使用命令模式的好处

3、使用时的注意事项

三、在 Unity 中使用 命令模式

1、定义命令接口

2、定义接收者类

3、实现具体命令类

4、实现请求者类

5、创建游戏管理器

6、在 Unity 中测试

7、示例分析

四、责任链模式(Chain of Responsibility Pattern)

1、什么时候使用责任链模式

2、使用责任链模式的好处

4、使用时的注意事项

1、定义请求类

2、定义处理者接口

3、实现具体处理者类

4、创建请求者类

5、在 Unity 中测试

6、示例分析


一、简单介绍

设计模式 是指在软件开发中为解决常见问题而总结出的一套 可复用的解决方案。这些模式是经过长期实践证明有效的 编程经验总结,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的 抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。

二、命令模式(Command Pattern)

命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成对象,从而使得可以使用不同的请求、队列或日志请求,以及支持可撤销的操作。命令模式通常包含四个主要角色:命令(Command)接收者(Receiver)请求者(Invoker)客户端(Client)。命令对象持有请求的详细信息,而接收者则执行这些请求。

1、什么时候使用命令模式

  1. 需要支持撤销/重做功能时:例如文本编辑器、绘图软件等需要频繁撤销和重做操作的应用。
  2. 需要将请求发送者与接收者解耦时:当请求的发送者和接收者之间的依赖关系需要降低时,可以使用命令模式。
  3. 需要记录请求日志时:当需要跟踪请求的历史以进行审计时,命令模式提供了一种优雅的解决方案。
  4. 需要实现异步请求时:可以将请求放入队列中,并在未来的某个时间点执行。

2、使用命令模式的好处

  1. 解耦请求与执行:命令模式将请求的发送者与接收者解耦,使得两者之间的关系更加灵活。
  2. 支持撤销操作:通过将操作封装成命令对象,可以轻松实现撤销(Undo)和重做(Redo)功能。
  3. 支持日志请求:可以将请求的历史记录进行存储,方便后续审计或分析。
  4. 可以实现队列请求:可以将命令对象放入队列中,按顺序执行或延迟执行。

3、使用时的注意事项

  1. 复杂性:命令模式可能会引入额外的复杂性,尤其是在命令对象数量较多时。需要确保命令的设计和管理是清晰的。
  2. 性能考虑:由于命令模式会创建多个命令对象,因此可能会增加内存开销。在性能敏感的场合,需要谨慎使用。
  3. 过度设计:对于简单的应用,使用命令模式可能会导致过度设计。在这些情况下,直接的方法调用可能更为简单和有效。

命令模式通过将请求封装为对象,实现了解耦、可撤销性和请求队列等功能。它适用于需要支持撤销、日志记录或异步请求的场合,但在设计时需要考虑到可能引入的复杂性和性能问题。

三、在 Unity 中使用 命令模式

在 Unity 中实现 命令模式 的示例,我们可以创建一个简单的场景,其中玩家可以使用命令来控制一个角色的移动和跳跃。这个示例将演示如何使用命令模式来处理这些操作。

使用命令模式控制角色

参考类图如下:

Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】-LMLPHP

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 中测试
  1. 创建一个带有 Collider 的 Cube 作为玩家角色,并附加 PlayerCharacter 脚本。
  2. 创建一个空的 GameObject,命名为 GameManager,并附加 GameManager 脚本。
  3. 运行游戏,使用 WS 键控制前后移动,使用 Space 键跳跃,使用 U 键撤销上一个命令。
7、示例分析
  • 命令(ICommand):定义执行和撤销的基本操作。
  • 接收者(PlayerCharacter):实现角色的具体操作。
  • 具体命令(MoveCommand 和 JumpCommand):封装角色的移动和跳跃逻辑。
  • 请求者(CommandInvoker):负责执行和撤销命令。

这个示例展示了如何在 Unity 中实现 命令模式,通过封装角色的操作为命令对象,使得用户能够灵活地控制角色,同时支持撤销操作。这种模式在需要记录和管理用户输入的场景中非常有用。

四、责任链模式(Chain of Responsibility Pattern)

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,使得多个对象都有机会处理请求。将这些对象连接成一条链,并沿着这条链传递请求,直到有对象处理它为止。该模式通常包含四个主要角色:请求(Request)处理者(Handler)具体处理者(ConcreteHandler)客户端(Client)

1、什么时候使用责任链模式

  1. 需要多个对象处理请求时:当一个请求需要被多个对象处理时,可以使用责任链模式。
  2. 处理请求的逻辑不明确时:当不确定哪个对象能够处理请求,或者希望多个对象有机会处理请求时,可以采用责任链模式。
  3. 请求处理的顺序可能变化时:如果请求的处理顺序可能发生变化,责任链模式可以提供更好的灵活性。
  4. 需要支持可扩展性时:当系统可能需要增加新处理者而不影响现有代码时,责任链模式是一个良好的选择。

2、使用责任链模式的好处

  1. 降低耦合度:发送者和接收者之间的关系被解耦,发送者不需要知道哪个具体的处理者来处理请求。
  2. 灵活性:可以动态添加或修改处理者,使得系统更加灵活和可扩展。
  3. 简化代码:通过将请求处理逻辑分散到多个处理者中,可以使得每个处理者的代码更加简洁,提高代码的可读性。
  4. 动态处理请求:可以通过改变责任链的结构和顺序来改变请求的处理方式。

4、使用时的注意事项

  1. 请求处理的顺序:责任链模式可能导致请求处理的顺序变得不易理解,因此需要明确责任链的顺序。
  2. 链的长度:过长的责任链可能导致性能下降,尤其是在链中的处理者都不处理请求的情况下。
  3. 处理失败的情况:需要设计好处理失败的情况,以免请求在链中被无效地传递。
  4. 调试困难:由于请求在链中可能被多个处理者处理,调试请求的流程可能会比较困难。

责任链模式通过将请求和处理者解耦,实现了请求的灵活处理和动态配置。适用于多个对象可能处理请求的场景,但在使用时需要考虑链的顺序、长度及调试难度等因素。

五、在 Unity 中使用 责任链模式

在 Unity 中实现 责任链模式 的示例,我们可以创建一个简单的游戏事件处理系统,其中多个处理者可以处理玩家的不同输入事件,例如跳跃、攻击和防御。这个示例将演示如何使用责任链模式来管理这些事件。

使用责任链模式处理玩家输入事件

参考类图如下:

Unity 设计模式 之 行为型模式-【命令模式】【责任链模式】-LMLPHP

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 中测试

  1. 创建一个空的 GameObject,命名为 InputManager,并附加 InputInvoker 脚本。
  2. 运行游戏,按 SpaceADX 键测试不同的输入事件。

6、示例分析

  • 请求(InputRequest):封装输入事件的信息。
  • 处理者(IInputHandler 和具体处理者类):定义如何处理不同的输入事件,形成责任链。
  • 请求者(InputInvoker):负责接收输入并将其传递给责任链中的处理者。

这个示例展示了如何在 Unity 中实现 责任链模式,通过将输入事件处理逻辑分散到多个处理者中,使得系统灵活且可扩展。每个处理者负责处理特定的输入事件,并可以根据需求轻松添加或修改处理者。

09-25 07:37