我正在使用抽象工厂来创建用户界面组件,例如对话框。使用的抽象工厂从当前选择的通用“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);
}
Yeck ..演员!
我唯一可以考虑并且仍能使用我的工厂的方法是在返回之前将节点本身注入工厂:
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或其他东西。
有什么想法吗?
谢谢!
答案 0 :(得分:3)
一个常见的解决方案是“双调度”,您可以在一个对象上调用虚函数,然后在另一个对象上调用虚函数,传递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();
}
答案 1 :(得分:1)
在我看来,只要您的代码被正确评论,使用C风格的演员表(虽然C ++风格会更受欢迎)是完全可以接受的。
我不是DI的忠实粉丝(dependency injection),因为它会让一些代码难以理解,在您的情况下,我宁愿看一下dynamic_cast<>()
或其他东西,而不是试图关注在多个源文件上注入代码。
答案 2 :(得分:0)
我会说两件事。
首先:铸造没有问题。如果你想要安全,你可以在INode类中使用RTTI(type_id stuff)或一些虚函数,它们可以返回一些信息,让你知道它是否可以安全投射。
第二:你可以检查一下createEditDialog函数需要什么,并将它们放在虚函数中的任何一个INode中,或者是一个继承的类,它将是createDialog所期望的类型。
一般来说,我认为你所描述的问题并没有什么问题,如果没有看到整个代码就很难给出更多建议,我认为这是不可行的。
答案 3 :(得分:0)
将节点注入工厂的方法通常是我发现有用的模式,但有时候当你在这里创建工厂时没有对目标对象的引用。因此,在这种情况下,这可能对您有用,并且比在一般情况下处理此类问题更简单。
对于更一般的情况,您需要使用接口的概念并建立一种机制,通过该机制,INode
对象可以发布它支持的接口,并为客户端提供对这些接口的访问。完全动态地执行此操作会导致类似COM的方法,需要动态注册和转换。但是,如果您想要公开一组相对稳定的接口,并且需要在需要添加新组件接口时编辑INode
接口,则也可以以静态类型的方式执行此操作。
所以这将是如何进行简单的静态类型方法的一个例子:
struct INode
{
virtual INodeSize* getNodeSizeInterface() = 0;
virtual INodeProperties* getNodePropertiesInterface() = 0;
virtual INodeColor* getNodeColorInterface() = 0;
... // etc
}
现在每个INode
实现都可以返回部分或全部这些组件接口(如果没有实现它们,它将返回NULL)。然后你的对话框在组件接口上运行,而不是试图弄清楚传递了INode
的实际实现。这将使对话框和节点实现之间的映射更加灵活。通过验证对象是否为对话框感兴趣的每个接口返回有效对象,对话框可以快速确定它是否具有“兼容”INode
对象。
答案 4 :(得分:0)
我认为createEditDialog
内的强制转换在这种情况下并不是坏事,即使你放弃了编译时间检查。如果节点的类型在运行时没有改变,则可以使用模板而不是抽象的INode
- 类。
否则,您提出的解决方案也是我想到的解决方案。但是,我会将方法重命名为“getSelectedNodeDialogFactory
”(我知道,长名称),以便很明显返回的工厂特定于该节点。是否有其他对话需要知道INode
对象的具体类型? createAddDialog
是否需要父节点或前导节点?那些都可以进入工厂选择节点类。