【问题标题】:Problem using abstract factory使用抽象工厂的问题
【发布时间】:2010-08-02 17:20:55
【问题描述】:

我正在使用抽象工厂来创建用户界面组件,例如对话框。使用的抽象工厂是从当前选择的通用“INode”返回的,它是几种不同类型节点的基类。因此,例如,如果我想添加与所选节点相同类型的新节点,则场景如下所示:

(请注意这是半伪代码)

用户点击节点,节点被存储以备后用:

void onTreeNodeSelected(INode *node)
{
    selectedNode = node;
}

用户在用户界面点击“添加”:

void onAddClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createAddDialog(parentWidget);
    dialog->show();
}

这一切看起来都很好。当我想编辑选定的节点时,问题就来了:

void onEditClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget);
    dialog->show();
}

哦,亲爱的.. 我正在传递一个 INode 对象。在某些时候,我将不得不将其向下转换为正确的节点类型,以便对话框可以正确使用它。

我研究了“PostgreSQL Admin 3”源代码,他们做了类似的事情。他们通过这样做来绕过它:

FooObjectFactoryClass::createDialog(IObject *object)
{
    FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object);
}

是的..演员!

我可以考虑它并且仍然能够使用我的工厂的唯一方法是在返回之前将节点本身注入工厂:

FooNode : INode
{
    FooNodeFactory* FooNode::getFactory()
    {
        fooNodeFactory->setFooNode(this);
        return fooNodeFactory;
    }
}

那么我的编辑事件可以这样做:

void onEditClicked()
{
    IFactory *factory = selectedNode->getFactory();
    Dialog *dialog = factory->createEditDialog(parentWidget);
    dialog->show();
}

并且它将使用注入的节点作为上下文。

我想如果没有注入代码,createEditDialog 可以断言 false 或其他东西。

有什么想法吗?

谢谢!

【问题讨论】:

    标签: c++ design-patterns oop


    【解决方案1】:

    一个常见的解决方案是“双重调度”,您在一个对象上调用一个虚函数,而后者又在另一个对象上调用一个虚函数,传递this,它现在具有正确的静态类型。因此,在您的情况下,工厂可以包含各种类型对话的“创建”功能:

    class IFactory
    {
    public:
        ....
        virtual Dialog* createEditDialog(ThisNode*, IWidget*);
        virtual Dialog* createEditDialog(ThatNode*, IWidget*);
        virtual Dialog* createEditDialog(TheOtherNode*, IWidget*);
        ....
    };
    

    那么每种类型的节点都有一个虚拟的createEditDialog,它分派到正确的工厂函数:

    class INode
    {
    public:
        ....
        virtual Dialog* createEditDialog(IWidget* parent) = 0;
        ....
    };
    
    class ThisNode : public INode
    {
    public:
        ....
        virtual Dialog* ThisNode::createEditDialog(IWidget* parent)
        {
            return getFactory()->createEditDialog(this, parent);
        }
        ....
    };
    

    然后你就可以创建正确的对话了

    void onEditClicked()
    {
        Dialog *dialog = selectedNode->createEditDialog(parentWidget);
        dialog->show();
    }
    

    【讨论】:

      【解决方案2】:

      在我看来,只要你的代码被正确注释,使用 C 风格的强制转换(尽管 C++ 风格更受欢迎)是完全可以接受的。

      我不是 DI (dependency injection) 的忠实拥护者,因为它使一些代码难以遵循,在你的情况下,我宁愿查看 dynamic_cast<>() 或其他东西,也不愿尝试跟踪多个注入的代码源文件。

      【讨论】:

        【解决方案3】:

        我建议两件事。

        首先:选角没有错。如果你想安全,你可以使用 RTTI(type_id 的东西)或 INode 类中的一些虚函数,它们可以返回一些信息,让你知道它是否安全。

        第二:您可以检查 createEditDialog 函数需要什么,然后将它们放在 INode 中的虚函数中,或者是 createDialog 期望的类型的继承类。

        总的来说,我认为您描述的问题并没有什么问题,而且如果没有看到整个代码,很难给出更多建议,我认为这是不可行的。

        【讨论】:

          【解决方案4】:

          您将节点注入工厂的方法通常是一种我发现很有用的模式,但是当您像这里一样创建工厂时,经常没有对目标对象的引用.因此,在这种情况下,这可能对您有用,并且比在一般情况下处理此类问题更简单。

          对于更一般的情况,您需要使用接口的概念并建立一种机制,您的INode 对象可以通过该机制发布它支持的接口并为客户端提供对这些接口的访问。完全动态地执行此操作会导致需要动态注册和强制转换的类似 COM 的方法。但是,如果您想要公开一组相对稳定的接口,并且在需要添加新的组件接口时能够编辑INode 接口,您也可以以静态类型的方式执行此操作。

          因此,这将是一个如何执行简单静态类型方法的示例:

          struct INode
          {
               virtual INodeSize* getNodeSizeInterface() = 0;
          
               virtual INodeProperties* getNodePropertiesInterface() = 0;
          
               virtual INodeColor* getNodeColorInterface() = 0;
          
               ... // etc
          }
          

          现在每个INode 实现都可以返回部分或全部这些组件接口(如果没有实现它们,它只会返回NULL)。然后,您的对话框在组件接口上运行以完成它们的工作,而不是试图找出传入的 INode 的实际实现。这将使对话框和节点实现之间的映射更加灵活。对话框可以通过验证它是否为对话框感兴趣的每个接口返回一个有效对象来快速确定它是否具有“兼容”INode 对象。

          【讨论】:

            【解决方案5】:

            我认为在这种情况下,createEditDialog 内部的强制转换并不是一件坏事,即使您放弃了编译时检查。如果节点的类型在运行时没有改变,你可以使用模板而不是抽象的INode-class。

            否则,您提出的解决方案也是我会想到的解决方案。但是,我会将方法重命名为“getSelectedNodeDialogFactory”(我知道,长名称),以便清楚返回的工厂特定于该节点。是否还有其他对话框需要知道INode 对象的具体类型? createAddDialog 是否需要父节点或前驱节点,也许?这些都可以放在 factory-with-selected-node 类中。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-01-05
              • 1970-01-01
              • 1970-01-01
              • 2014-01-14
              • 1970-01-01
              相关资源
              最近更新 更多