I am using an abstract factory to create user interface components such as dialogs. The abstract factory used is returned from a currently selected generic "INode" which is the base class for several different types of node. So for instance, if I want to add a new node of the same type as the selected node, the scenario goes something like this:
(please note this is semi-pseudo code)
User clicks node and the node gets stored for later use:
void onTreeNodeSelected(INode *node)
{
selectedNode = node;
}
User clicks "add" on the user interface:
void onAddClicked()
{
IFactory *factory = selectedNode->getFactory();
Dialog *dialog = factory->createAddDialog(parentWidget);
dialog->show();
}
Which all seems fine. The problem comes when I want to edit the selected node:
void onEditClicked()
{
IFactory *factory = selectedNode->getFactory();
Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget);
dialog->show();
}
Oh dear.. I'm passing in an INode object. At some point I'm going to have to downcast that to the correct node type so the dialog can use it properly.
I've studied the "PostgreSQL Admin 3" source code, and they do something similar to this. They get round it by doing something like this:
FooObjectFactoryClass::createDialog(IObject *object)
{
FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object);
}
Yeck.. cast!
The only way I can think around it and still able to use my factories is to inject the node itself into the factory before it is returned:
FooNode : INode
{
FooNodeFactory* FooNode::getFactory()
{
fooNodeFactory->setFooNode(this);
return fooNodeFactory;
}
}
So then my edit event can do this:
void onEditClicked()
{
IFactory *factory = selectedNode->getFactory();
Dialog *dialog = factory->createEditDialog(parentWidget);
dialog->show();
}
And it will use the injected node for context.
I suppose if there is no injected code, the createEditDialog could assert false or something.
Any thoughts?
Thanks!
Use of this pattern makes it possible to interchange the concrete classes without changing the client code even at runtime. Drawback: One of the main drawbacks is the extra complexity and writing the code during the initial stages.
The Abstract Factory design pattern solves problems like: How can an application be independent of how its objects are created? How can a class be independent of how the objects it requires are created? How can families of related or dependent objects be created?
Abstract-Factory Pattern { consequence: The Abstract Factory pattern has the following beneets and liabilities: 1. It isolates concrete classes. 2. It makes exchanging product families easy.
A potential disadvantage of Factory methods is that clients might have to sub-class the creator class just to create a particular concrete product object. Subclassing is fine when the client has to subclass the creator class anyway, but otherwise, the client now must deal with another point of evolution.
In my opinion, using C-style casts (although C++ style would be preferred) is perfectly acceptable as long as your code is properly commented.
I'm not a big fan of DI (dependency injection) because it makes some code hard to follow and, in your case, I would rather look at a dynamic_cast<>()
or something than try to follow injected code over multiple source files.
A common solution is "double-dispatch", where you call a virtual function on one object, which in turn calls a virtual function on the other, passing this
, which now has the correct static type. So, in your case, the factory can contain "create" functions for the various types of dialogues:
class IFactory
{
public:
....
virtual Dialog* createEditDialog(ThisNode*, IWidget*);
virtual Dialog* createEditDialog(ThatNode*, IWidget*);
virtual Dialog* createEditDialog(TheOtherNode*, IWidget*);
....
};
then each type of node has a virtual createEditDialog
that dispatches to the correct factory function:
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);
}
....
};
Then you can create the correct dialogue as
void onEditClicked()
{
Dialog *dialog = selectedNode->createEditDialog(parentWidget);
dialog->show();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With