Reputation: 22824
I have the following problem in application architecture and am willing to solve it (sorry for a lot of text).
I am building a game engine prototype and I have base abstract class AbstractRenderer
(I will use C++ syntax, but still the problem is general).
Assume there are some derived implementations of this renderer, let's say DirectxRenderer
and OpenglRenderer
.
Now, let's say that only one of these renderers (let's stick to DirectX-based) has a member called IDirect3D9Device* m_device;
Obviously at this point everything is fine - m_device
is used internally in DirectxRenderer
and shouldn't be exposed in the abstract AbstractRenderer
superclass.
I also add some abstract rendering interface, for instance IRenderable
. It means simply one pure virtual method virtual void Render(AbstractRenderer* renderer) const = 0;
And this is the place where some problems start. Assume I am modelling some scene, so, this scene will probably have some geometrical objects in it.
I create abstract superclass AbstractGeometricalObject
and derived DirectX-based implementation DirectxGeometricalObject
. The second one would be responsible for storing pointers to DirectX-specific vertex & index buffers.
Now - the problem.
AbstractGeometricalObject
should obviously derive the IRenderable
interface, because it's renderable in logical terms.
If I derive my DirectxGeometricalObject
from AbstractGeometricalObject
, the first one should have virtual void Render(AbstractRenderer* renderer) const { ... }
method in it, and that Abstract...
stuff brings some troubles.
See the code for better explanation:
And for now my classes look the following way:
class AbstractGeometricalObject : public IRenderable {
virtual void Render(AbstractRenderer* renderer) const { ... }
};
class DirectxGeometricalObject : public AbstractGeometricalObject {
virtual void Render(AbstractRenderer* renderer) const {
// I think it's ok to assume that in 99 / 100 cases the renderer
// would be a valid DirectxRenderer object
// Assume that rendering a DirectxGeometricalObject requires
// the renderer to be a DirectxRenderer, but not an AbstractRenderer
// (it could utilize some DX-specific settings, class members, etc
// This means that I would have to ***downcast*** here and this seems really
// bad to me, because it means that this architecture sucks
renderer = dynamic_cast<DirectxRenderer*>(renderer);
// Use the DirectX capabilities, that's can't be taken out
// to the AbstractRenderer superclass
renderer.DirectxSpecificFoo(...);
}
I know I'm probably worrying too much, but this downcast in such a simple case means that I could be forced to make lots of downcasts if my application grows.
Definitely, I would like to avoid this, so please, could you advice me something better in design terms / point out my errors.
Thank you
Upvotes: 1
Views: 465
Reputation: 19362
Let's distance from compilers and consider theory. If DirectxGeometricalObject::Render
expects DirectxRenderer
as parameter and not any AbstractRenderer
, then some other OtherGeometricalObject::Render
will probably expect OtherRenderer
object as parameter.
So, different implementations of AbstractGeometricalObject
have different signatures of their Render
methods. If they are different, then there is no purpose in defining the virtual AbstractGeometricalObject::Render
.
If you declare AbstractGeometricalObject::Render(AbstractRenderer*)
, then you should be able to pass any renderer to any geometrical object. In your case, you can't because dynamic_cast
would fail.
Upvotes: 0
Reputation: 27143
Using templates, you could split the IRendable into two classes, one for each of the two renderer types. This is probably not the best answer, but it does avoid the need for the dynamic cast:
template <typename RendererType>
struct IRenderable {
virtual void Render(RendererType* renderer) const = 0;
}
template <typename RendererType>
class AbstractGeometricalObject : public IRenderable<RendererType> {
virtual void Render(RendererType* renderer) const { ... }
};
class DirectxGeometricalObject : public AbstractGeometricalObject<DirectxRenderer> {
// this class will now require a void Render(DirectxRenderer* renderer)
}
Upvotes: 1
Reputation: 20980
See if the Bridge design pattern helps you: "Decouple an abstraction from its implementation so that the two can vary independently." In your example, AbstractGeometricalObject would point to an implementation, a pure virtual interface with platform-specific subclasses. The tricky part is taking the time to discover that interface.
Upvotes: 0
Reputation: 23227
This might be a situation where the template pattern (not to be confused with C++ templates) comes in handy. The public Render
in the abstract class should be non-virtual, but have it call a private virtual function (e.g. DoRender). Then in the derived classes, you override DoRender instead.
Here's an article that goes into great depth describing the use of template pattern with private virtual functions.
Edit:
I started to put together an example of what I meant, and it seems like there's actually a broader flaw in the architecture. Your use of AbstractRenderer is somewhat frivolous since you're forcing each geometricalobject to be intimately aware of a particular renderer type.
Either the renderer should be able to work off the public methods of Renderables, or Renderables should be able to work off the public methods of the Renderer. Or perhaps you can give the concrete renderers a Renderable factory if there really needs to be such an intimate connection. I'm sure there are some other patterns that would fit well, too.
Upvotes: 3
Reputation: 59831
I don't see what your code wants to achieve. You derive Renderable objects to DirectXRenderables and OpenGLRenderables and then provide OpenGL or DirectX functionality in something derived from Renderer. A specific thing uses another specific thing so to speak.
It would seem much more reasonable to identify general rendering functions, make them pure virtual
members of your abstract renderer and implement them in DirectXRenderer
and OpenGLRenderer
. Then a IRenderable
would have a member function draw roughly like this:
void draw(const AbstractRenderer& r) {
//general stuff
r.drawLine(...);
//only possible on directX
if(DirectxRenderer rx = dynamic_cast<DirectxRenderer*>(r)) {
//...
} else {
//throw exception or do fallback rendering in case we use something else
}
}
Upvotes: 2
Reputation: 5676
Use a setter to set the renderer var and cast it to the proper type in that one place.
Upvotes: 0