I've often found it difficult to avoid dynamic casts in my designs, and I was wondering if we could brainstorm what strategies one could use to minimize their use. I would find that tremendously productive.
One instance that comes up a lot is with the Abstract Factory pattern and its variants. An often-used illustration of this pattern is found on Wikipedia:
http://en.wikipedia....act_factory.svg
Now, I would make this a little more complex by saying that the GUIFactory interface would also have methods for creating other controls, like createComboBox and createList. It may also have something like createWindow. And the Window interface might have an addControl method that can be used to add buttons/comboboxes/lists to the window.
So, the trickery comes when you're writing your concrete implementations. Let's imagine an imaginary GUI framework called Foo32 that you want to target. So you write FooFactory and concrete product classes like FooWindow, FooButton, FooComboBox, FooList, etc.
So, the class hierarchy would look something like this:
And the factory would look something like this:
[source lang="cpp"]class FooFactory : public GUIFactory{public: FooWindow* createWindow(); FooButton* createButton(); FooList* createList(); FooComboBox* createComboBox();};[/source]
And here are some possible class definitions for FooButton and FooWindow:
[source lang="cpp"]class FooButton : public Button{ Foo::Button *m_button; // The actual button object.public: FooButton(); // Button interface void paint();};class FooWindow: public Window{ Foo::Window m_window; // The actual button object.public: FooButton(); // Window interface void paint(); void addControl(Control *ctrl) { m_window.addControl(ctrl); // Error! Control is not a Foo::Control }};[/source]
So the question is, how to solve that error. The naive approach would be to just test and see what concrete class we're dealing with.
[source lang="cpp"] void addControl(Control *ctrl) { FooButton *fooButton = dynamic_cast<FooButton*>(ctrl); if(fooButton != NULL) { m_window.addControl(fooButton->getFooButton()); // getFooButton returns the Foo::Button* } FooComboBox *fooCB = dynamic_cast<FooComboBox*>(ctrl); if(fooCB != NULL) { m_window.addControl(fooCB->getFooComboBox()); // getFooButton returns the Foo::ComboBox* } /* and so on... */ }[/source]
Ouch, though. I suppose I could use multiple inheritance, i.e. FooButton would inherit from both Button and Foo::Button. Since Foo::Button is a Foo::Control, then the method implementation is simplified:
[source lang="cpp"] void addControl(Control *ctrl) { Foo::Control *fooCtrl = dynamic_cast<Foo::Control*>(ctrl); if(fooCtrl!= NULL) { m_window.addControl(fooCtrl); } }[/source]
Problem is, this uses multiple inheritance and we still have a dynamic_cast. I've spent enough time debugging dynamic_cast to know that it is not a quick or trivial thing (with MSVC, it does multiple strcmps). Perhaps that's not a problem for setting up a GUI (most of this is done during program initialization and GUI's typically spend most of their time waiting for user input anyway). But what if the above pattern is needed for something other than a GUI -- something for a real-time video game that requires dozens or hundreds of Control-like objects to be added and removed from a Window-like object each frame?
I am currently wrestling with a situation just like that -- a have a "View" (analogous to Window) that needs to have "Scene Objects" (analogous to Control) added to it. Examples of Scene Object types are Model, Particle System, Hud Element, etc. I feel that it is necessary to use something like an Abstract Factory because of a high possibility that I may want to change what graphics framework I'm using someday (I'm currently using OpenSceneGraph).
It's often said that, if you are using dynamic_cast a lot, you're probably doing something wrong. This makes me feel uneasy about my design.
It's also often said that your classes should be open for extension and closed for modification. I find it really difficult to close classes like GUIFactory, because one is always thinking of new types of controls that need to be created. Maybe a createTextBox method needs to be added at some point -- is it really desirable to subclass GUIFactory just because you want to call it 'closed'?
I'm getting better and better at OO design, but I'm still fledgling in a lot of ways, and as you can see, I could use some advice. From where I sit, there are two basic possibilities:
1) Don't try to abstract the graphics framework in something like a game, where performance is critical.
2) Minimize the amount of time that the code sequence crosses your abstraction layer, i.e...
[source lang="cpp"]// This code can't know we're using Foo32. It just knows about the abstract interfaces.// The typical way to do it...Window w = guiFactory->createWindow(); // returns a FooWindow, but this code doesn't know or careButton b = guiFactory->createButton(); // returns a FooButton, but this code doesn't know or carew->addControl(b); // this will do a dynamic_cast (slow)// Here is an alternate possibility and gets around the above issues...int windowId = guiFactory->createWindow(); // returns the id of the created window, but the factory keeps the window for itselfint buttonId = guiFactory->createButton(); // as above, only the id of the button is returnedguiFactory->addControlToWindow(windowId, buttonId);[/source]
Since FooFactory is owning the objects it creates in that second solution, it has concrete references to them, and thus no casting is needed. This has a very messy, non-OOP feel to it, though. Almost as if I'm working with OpenGL.







