在 run-time 创建命令 objects

Creating Command objects at run-time

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

我一直在尝试使用命令模式来执行此操作(遵循 gameprogrammingpatterns.com 中的示例)。请参阅下面我当前代码结构的概述。

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

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

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

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

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

如何从 parseCommand 中干净地实例化正确的 Command object?

命令界面基础class:

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

接口实现示例:

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

控制台 class header:

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

依靠映射命令标识符(即std::string 对象)到 Command 对象,您可以为 Command 对象设计一个带有动态注册表的工厂。

首先,通过包含另一个虚拟成员函数 clone() 来扩展 Command,它允许我们实现 The Prototype Pattern:

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>> 数据成员。通过命令标识符(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();
}

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