基于OOP命令模式与多态的用户系统命令接口实现难题
多态下派生类特有方法调用问题的解决方案
问题描述
我尝试采用OOP设计结构与原则构建一个类命令式的用户系统接口:
- 包含抽象
User类及多个派生用户类; - 单例
CommandInterface类持有User基类指针,提供login、signup、exit命令; - 登录后需调用不同派生用户类的特有方法(如
User1的a()、User2的b()),但仅通过User基类指针无法调用这些方法,导致代码中ACMD和BCMD类出现方法未找到的错误。
核心问题:如何处理多态下派生类方法被“遮蔽”的情况?
问题代码
#include <vector> #include <string> #include <unordered_map> #include <memory> class CommandInterface { static std::unordered_map<std::string, std::unique_ptr<Command>> interfaceCommands; std::shared_ptr<User> loggedUser = nullptr; // 单例实现 static CommandInterface* instance; CommandInterface() = default; std::unique_ptr<System> sys = System::getInstance(); // 持有用户系统单例实例 public: // 禁用拷贝,允许移动 CommandInterface(const CommandInterface& other) = delete; CommandInterface(CommandInterface&& other) = default; CommandInterface& operator=(const CommandInterface& other) = delete; CommandInterface& operator=(CommandInterface&& other) = default; ~CommandInterface() = default; static CommandInterface* getInstance(); // 命令方法 void signup(const std::string& name, uint8_t age, std::string& userType); // 向系统添加用户 void login(const std::string& name) { if(loggedUser) { throw std::exception(); } else { loggedUser = sys->getUser(name); } } void exitCmd() { if(loggedUser) { loggedUser = nullptr; } else { // 抛出异常以确保析构函数被调用(std::exit(0)不会调用析构) throw std::runtime_error("Exit."); } } std::shared_ptr<User>& getLoggedUser(); // 获取当前登录用户,供命令类使用 void executeCommand(const std::string& command, const std::vector<std::string>& args) { if(!loggedUser) { // 若命令不存在则抛出异常 interfaceCommands[command]->execute(args); } else { // 不同用户持有各自的命令映射表,若命令不存在则抛出异常 loggedUser->getCommand(command)->execute(args); // 纯虚方法 } } }; // 初始化静态成员 std::unordered_map<std::string, std::unique_ptr<Command>> CommandInterface::interfaceCommands = {}; CommandInterface* CommandInterface::instance = nullptr; class Command { public: virtual ~Command() = default; virtual void execute(const std::vector<std::string>& args) const = 0; }; class PrintCMD : public Command { public: void execute(const std::vector<std::string>& args) const override { CommandInterface::getInstance()->getLoggedUser()->print(); } }; class ACMD : public Command { public: void execute(const std::vector<std::string>& args) const override { CommandInterface::getInstance()->getLoggedUser()->a(); // 错误:基类User无a()方法 } }; class BCMD : public Command { public: void execute(const std::vector<std::string>& args) const override { CommandInterface::getInstance()->getLoggedUser()->b(); // 错误:基类User无b()方法 } }; // 补充抽象User类及派生类的定义(原代码缺失) class User { public: virtual ~User() = default; virtual void print() = 0; virtual std::unique_ptr<Command> getCommand(const std::string& cmdName) = 0; }; class User1 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void a() { /* User1特有方法 */ } }; class User2 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void b() { /* User2特有方法 */ } }; class System { public: static System* getInstance(); std::shared_ptr<User> getUser(const std::string& name) { /* 实现逻辑 */ } };
解决方案
方案1:动态类型转换(dynamic_cast)
通过dynamic_cast将基类指针安全转换为派生类指针,转换成功后调用特有方法,适合小范围场景。
修改ACMD和BCMD的execute方法:
class ACMD : public Command { public: void execute(const std::vector<std::string>& args) const override { auto user1 = std::dynamic_pointer_cast<User1>(CommandInterface::getInstance()->getLoggedUser()); if (user1) { user1->a(); } else { throw std::runtime_error("当前用户无法执行该命令"); } } }; class BCMD : public Command { public: void execute(const std::vector<std::string>& args) const override { auto user2 = std::dynamic_pointer_cast<User2>(CommandInterface::getInstance()->getLoggedUser()); if (user2) { user2->b(); } else { throw std::runtime_error("当前用户无法执行该命令"); } } };
- 优点:实现简单,无需修改基类结构;
- 缺点:显式判断类型,违背开闭原则,新增派生类时需修改命令类代码。
方案2:扩展基类多态接口(推荐)
在抽象User类中定义通用的命令执行接口,让派生类实现各自的特有逻辑,符合开闭原则。
修改User基类及派生类:
class User { public: virtual ~User() = default; virtual void print() = 0; virtual std::unique_ptr<Command> getCommand(const std::string& cmdName) = 0; // 新增通用特有命令执行接口 virtual void executeSpecificCommand(const std::string& cmdName, const std::vector<std::string>& args) = 0; }; class User1 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void executeSpecificCommand(const std::string& cmdName, const std::vector<std::string>& args) override { if (cmdName == "a") { a(); } else { throw std::runtime_error("未知命令"); } } void a() { /* User1特有方法 */ } }; class User2 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void executeSpecificCommand(const std::string& cmdName, const std::vector<std::string>& args) override { if (cmdName == "b") { b(); } else { throw std::runtime_error("未知命令"); } } void b() { /* User2特有方法 */ } };
修改命令类:
class ACMD : public Command { public: void execute(const std::vector<std::string>& args) const override { CommandInterface::getInstance()->getLoggedUser()->executeSpecificCommand("a", args); } }; class BCMD : public Command { public: void execute(const std::vector<std::string>& args) const override { CommandInterface::getInstance()->getLoggedUser()->executeSpecificCommand("b", args); } };
- 优点:符合开闭原则,新增派生类或命令时无需修改原有代码;
- 缺点:若命令过多,基类接口会变得臃肿,需合理设计接口粒度。
方案3:访问者模式
将操作与数据结构分离,适合需要对不同派生类执行大量不同操作的场景,避免频繁类型转换。
定义访问者接口及修改User类:
// 访问者接口 class UserVisitor { public: virtual ~UserVisitor() = default; virtual void visitUser1(User1& user) = 0; virtual void visitUser2(User2& user) = 0; }; class User { public: virtual ~User() = default; virtual void print() = 0; virtual std::unique_ptr<Command> getCommand(const std::string& cmdName) = 0; // 接受访问者 virtual void accept(UserVisitor& visitor) = 0; }; class User1 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void accept(UserVisitor& visitor) override { visitor.visitUser1(*this); } void a() { /* User1特有方法 */ } }; class User2 : public User { public: void print() override { /* 实现逻辑 */ } std::unique_ptr<Command> getCommand(const std::string& cmdName) override { /* 实现逻辑 */ } void accept(UserVisitor& visitor) override { visitor.visitUser2(*this); } void b() { /* User2特有方法 */ } };
实现对应命令的访问者:
// 执行a命令的访问者 class ACommandVisitor : public UserVisitor { public: void visitUser1(User1& user) override { user.a(); } void visitUser2(User2& user) override { throw std::runtime_error("User2无法执行a命令"); } }; // 执行b命令的访问者 class BCommandVisitor : public UserVisitor { public: void visitUser1(User1& user) override { throw std::runtime_error("User1无法执行b命令"); } void visitUser2(User2& user) override { user.b(); } };
修改命令类:
class ACMD : public Command { public: void execute(const std::vector<std::string>& args) const override { ACommandVisitor visitor; CommandInterface::getInstance()->getLoggedUser()->accept(visitor); } }; class BCMD : public Command { public: void execute(const std::vector<std::string>& args) const override { BCommandVisitor visitor; CommandInterface::getInstance()->getLoggedUser()->accept(visitor); } };
- 优点:操作与数据结构分离,新增操作时无需修改
User类; - 缺点:新增派生类时需修改访问者接口及所有访问者实现,适合操作相对稳定的场景。
内容的提问来源于stack exchange,提问作者ashamedgap
相关产品推荐
相关产品推荐

