在 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
原型的支持。
我正在尝试为游戏引擎编写一个控制台,它允许我输入命令来执行任务,例如更改地图、生成敌人等。
我一直在尝试使用命令模式来执行此操作(遵循 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
原型的支持。