OO Design and avoiding dynamic casts

Started by
22 comments, last by Xai 11 years, 7 months ago

Essentially, it would be nice to have a system where you can change the effect without changing the material, and everything still works as long as the material provides the necessary inputs to the effect.

As you demonstrated in your first example a material has no behaviour and in particular no polymorphic behaviour, this is why you gave it an empty interface. If something has no behaviour then it's just data. The problem then becomes how to model that data so as to meet your goals.

If we recognise that there are only a few different kinds of thing that any effect could possibly ever want from a material then all we have to do is design a Material class that is able to provide any and all of those different things to an effect:


class Material
{
public:
const vector3 & getVector(const std::string & propertyName) const;
const matrix44 & getMatrices(const std::string & propertyName) const;
float getScalars(const std::string & propertyName) const;
// etc
};


Now you're in a position where you model different types of material in a data-driven fashion; a Material is a "ColorMaterial" not because you subclassed it but because you populated it with ambient and diffuse properties.

Edit: Annoying that the Code tags don't format indent properly, I always have to fight with it to get things aligned.
Advertisement
Yes, I have considered this approach as well (but I didn't want to make my post any longer). It certainly has some good arguments going for it (and the empty material base class felt wrong from the beginning). However, the nice thing about using inheritance is that I can use a cast to make sure a certain set of properties will always be present. Once the dynamic cast succeeds, I can access them very efficiently, whereas - if I understand correctly - in your data driven approach, I would have look them up in a map. But I agree, this is kind of an "abuse" of inheritance without any real polymorphic behaviour and it brings some weirdness with it.

An OpenGL window should not be running around casting OpenGL controls to a long list of all possible children types, that is just madness. That is an unmanageable, unextendable non-oo design. However each OpenGL control can and likely will cast the IWindow to an IOpenGL window (fully expecting it to be one, since the whole assumption is the creation of everything from exactly 1 factory instance, so that a mismatch would be impossible. Also, an OpenGL window would keep a list of IOpenGL controls, and cast each control on add from IControl to IOpenGL control ... because the open gl family is going to be implemented internally to receive, associate with and use only other open gl family controls. The base interfaces are just used as the lesser API for externally facing interfaces.


Keep in mind that my casts "to a list of all possible children types" was an illustration of what not to do. Then, I offered another solution that is very similar to what you proposed:


[source lang="cpp"]void addControl(Control *ctrl)
{
Foo::Control *fooCtrl = dynamic_cast(ctrl);
if(fooCtrl!= NULL)
{
m_window.addControl(fooCtrl);
}
}[/source]


There is a problem with this, though. It works, but there is a bit of ugliness which I can't seem to get around, which is that it almost inevitably requires multiple inheritance. Here is one possible hierarchy (this time using OpenGL as an example, as you did).

rE4wX.gif

OpenGLControl could also derive from IControl, but then you'd have diamond inheritance. You could use virtual inheritance to solve that; in fact, it's probably a good idea to do that anyway, because if your hierarchy gets any deeper (think ISwitchButton and IRadioButton), you're going to end up with diamond inheritance anyway. Once you have diamond inheritance, virtual inheritance becomes and issue, and once you have virtual base classes, you can no longer downcast them using static_cast, which means that SiCrane's checked cast solution is out of the question.

So, let's suppose that you keep your hierarchy clean enough that there are no diamonds, and thus virtual inheritance is unnecessary. Well, you still have multiple inheritance, which lends overhead to dynamic casting (and static casting too, if I understand it correctly) because now you have multiple vtables in your object, and a cast involves offsetting the pointer.

These are the sorts of ugly sins that I've been dealing with with this design.

I am warming up to Telastyn YAGNI remark. As jwezorek said, it may be impractical to think that I can wrap everything that I need to, even future frameworks that I haven't thought of yet.

dmatter, thank you very much for your reply. It seems to be clearest way to put into words what I'm doing wrong. The duck typing idea sounds intriguing, but I think my project is a bit too complicated for that. Your idea for having the window produce controls is also intriguing. It seems like it would be difficult to ever close the Window class to modification, though, because every time you come up with a new control type, you have to add a new method. Still, that may be a lesser sin than violating LSP.
You missed exactly 1 thing in my example CDProp. The OpenGL Window does NOT have a list of IControls, it has a list of IOpenGLControls. So your original example (with multiple inheritance and/or dynamic_casting still needed is OK, even for a 500 object inner loop of a game engine, if and only if the dynamic_casts can be amortized by being NOT in the read / render, but instead in the add ... this is just like having indexes on a db that hurt insert / update but speed up search. But if every frame you are rebuilding or completely reorganizing your objects - then it would not perform well. But the goal of this design is that the casts are AT ASSOCIATION TIME, not AT USAGE TIME.

This design is simply does of course require the key interfaced based programming techniques. So as you mention the diamond problem, multiple inheritance, virtual inheritance, etc. all come along for the ride - stealing a little cost here and there, but hopefully applied judiciously they benefit will outweigh the cost.

This topic is closed to new replies.

Advertisement