首页 > 解决方案 > 在运行时创建 Command 对象

问题描述

我正在尝试为游戏引擎编写一个控制台,它允许我输入命令来执行任务,例如更改地图、生成敌人等。

我一直在尝试使用命令模式来做到这一点(按照 gameprogrammingpatterns.com 的示例)。有关我当前代码结构的概述,请参见下文。

parseCommand处理string来自用户的,提取命令名称和参数(当前仅使用空格分隔)。下一步是我卡住的地方。我需要创建一个Command*可以调用execute的方法,但我只有string命令的名称。

我的函数中可能有一大堆if语句parseCommand,例如:

if (cmdName == "spawn")
   return new SpawnEnemyCommand();

或者,我可以在我的类中存储一个指向每个命令的指针Console,例如Command *spawnNewEnemy = new SpawnNewEnemy();然后在parseCommanddo中if (cmdName == "spawn") spawnNewEnemy->execute();。第二个选项似乎是 gameprogrammingpatterns book 是如何做到的。

如果我最终得到数百个控制台命令,这些选项似乎都不是很实用。我已经研究了我能找到的关于这种模式的所有文章和帖子,但这并没有帮助我澄清情况。

如何Command从内部干净地实例化正确的对象parseCommand

命令接口基类:

class Command {
public:
    virtual ~Command() { }
    virtual void execute() = 0;
};

示例接口实现:

class SpawnEnemyCommand : public Command {
public:
    void execute() {
        // method calls to perform command go here
    }
};

控制台类头:

class Console {
public:
    Command* parseCommand(std::string);
    bool validateCommand(std::string, std::vector<std::string>);
};

标签: c++design-patternsconsole

解决方案


通过依赖将命令标识符(即对象)映射到对象的字典(例如,std::unordered_map或) ,您可以为您的对象设计一个具有动态注册表的工厂。std::mapstd::stringCommandCommand

首先,Command通过包含另一个虚拟成员函数来扩展clone(),它允许我们实现原型模式

class Command {
public:
   // ...
   virtual std::unique_ptr<Command> clone() const = 0;
};

虚成员函数的clone()作用正如其名:它克隆对象。也就是说,将通过以下方式SpawnEnemyCommand覆盖:Command::clone()

class SpawnEnemyCommand : public Command {
public:
   // ...
   std::unique_ptr<Command> clone() const override {
      // simply create a copy of itself
      return std::make_unique<SpawnEnemyCommand>(*this);
   }
};

这样,您的命令对象可以通过接口多态Command地复制——也就是说,您不需要知道要复制的命令的具体类型。复制对象所需要做的Command就是调用它的clone()虚拟成员函数。例如,以下函数将Command传递的作为参数复制,而不管底层的具体类型如何:

std::unique_ptr<Command> CopyCommand(const Command& cmd) {
    return cmd.clone();
}

考虑到这一点,您已经准备好为命令对象设计一个工厂CommandFactory,它支持动态注册您的命令对象:

class CommandFactory {
public:
   void registerCommand(std::string id, std::unique_ptr<Command>);
   std::unique_ptr<Command> createCommand(std::string id) const;
private:
   std::unordered_map<std::string, std::unique_ptr<Command>> registry_;
};

这一切都归结为一个std::unordered_map<std::string, std::unique_ptr<Command>>数据成员。通过命令标识符(即 an )索引此数据成员std::string,我们检索一个Command对象——这是我们将用于克隆的原型对象。

registerCommand()成员函数将原型Command添加到注册表:

void CommandFactory::registerCommand(std::string cmdId, std::unique_ptr<Command> cmd) {
   registry_[cmdId] = std::move(cmd);
}

createCommand()成员函数克隆Command对应于请求的命令标识符的原型:

std::unique_ptr<Command> CommandFactory::createCommand(std::string cmdId) const {
   auto const it = registry_.find(cmdId);
   if (it == registry_.end())
      return nullptr; // no such a command in the registry

   auto const& cmdPtr = it->second;
   return cmdPtr->clone();
}

作为示例程序:

auto main() -> int {
   CommandFactory factory;
   factory.registerCommand("spawn", std::make_unique<SpawnEnemyCommand>());

   // ...

   auto cmdPtr = factory.createCommand("spawn");
   cmdPtr->execute();
}

您还可以扩展此工厂以添加对动态取消注册已注册Command原型的支持。


推荐阅读