在GUI设计中避免dynamic_cast

Avoiding dynamic_cast in GUI design

本文关键字:dynamic cast GUI      更新时间:2023-10-16

我正在设计一个GUI,但我遇到了一个问题,因为我无法避免dynamic_casting。

我的课程:

  class Widget; //base class for all widgets
  class Container //contains widgets
  {
     std::map<std::string, Widget*> m_widgets;
     public:
     template <class T> T* get(const std::string &name)
     {
         return dynamic_cast<T*>(m_widgets.at(name)); //I need casting here
     }
   }

如何避免动态铸造?我不能为每种小部件类型都提供容器,因为我的GUI必须使用用户定义的小部件。此外,我必须为每个小部件都有一个容器,这样用户就不必自己存储小部件了。

为什么我需要选角?

 class TextBox : public Widget
 { 
      public:
      std::string getText(); //I can't have it in Widget class, because it's object-specific
      //also, my gui must work with user-defined widgets so I can't provide 
      //empty virtual functions for everything in Widget
 }

我认为您不需要一个容纳所有小部件的容器。这些类可以保存一个指针,指向它们需要使用的小部件的具体实例。您可以将指针作为构造函数上的参数进行传递;另一种选择是使用依赖注入(例如wallaroo可以用于您的问题)。

要扩展我的评论…

根据您的代码,我想您打算让我为添加到Container的每个子小部件指定一个字符串名称,然后按名称访问该子小部件。所以你希望我写这样的东西:

class LoginController {
    Container *container;
    static const char *kUsernameKey = "username";
    static const char *kPasswordKey = "password";
public:
    LoginController() :
        container(new Container())
    {
        container->addChild(kUsernameKey, new TextBox());
        container->addChild(kPasswordKey, new TextBox());
        container->addChild("button", new Button("Log In"));
        container->get<Button>("button")->setAction([](){
            this->login();
        })
    }
    void login() {
        string username = container->get<TextBox>(kUsernameKey)->getText();
        string password = container->get<TextBox>(kPasswordKey)->getText();
        sendLoginRequest(username, password);
    }
};

以这种方式设计Container在运行时进行查找和类型检查,但这些查找和类型检测可以在编译时进行。

相反,设计API,这样我就可以在自己的变量中保留自己的、特定类型的引用。查找子项就变成了使用变量,不需要进行强制转换。查找和类型检查是在编译时完成的。代码如下:

class LoginController {
    Container *container;
    TextBox *usernameBox;
    TextBox *passwordBox;
public:
    LoginController() :
        container(new Container()),
        usernameBox(new TextBox()),
        passwordBox(new TextBox())
    {
        container->addChild(username);
        container->addChild(password);
        Button *button = new Button("Log In");
        container->addChild(button);
        button->setAction([](){
            this->login();
        })
    }
    void login() {
        string username = usernameBox->getText();
        string password = passwordBox->getText();
        sendLoginRequest(username, password);
    }
};

我到处都读到RTTI不好,应该避免

我怀疑你在任何地方都读过,因为那纯粹是胡说八道。

RTTI是一个非常好的功能。尽管这肯定应该避免,因为有更好的工具。一个好的层次结构被设计为可以通过虚拟函数访问基类接口。在这种情况下,您不需要任何强制转换,只需调用虚拟函数,它就会做正确的事情。

即使您的GetText也可能是一个不错的候选者,默认实现返回一个空字符串。也许是在一个功能查询工具的公司,报告实际文本的存在。因此,大多数客户端可以直接调用并对空字符串感到满意,而其他客户端可能会进行检查。

那些对一些罕见的接口感兴趣的人,只需要一个具体的类,就可以永远调用dynamic_cast。收藏最好保持简单,仅限于收藏。也许可以为真正常见的Widget族添加一些特殊的窗体。

这里的问题是您的设计是向后的。您想要渲染一个Widget,但该Widget没有具体的可渲染功能。您必须要求Widget呈现自己,在那里它可以呈现文本/图像/任何它想要的东西。