1

我在应用程序架构中有以下问题,并愿意解决它(对不起,很多文字)。

我正在构建一个游戏引擎原型,并且我有基本的抽象类AbstractRenderer (我将使用 C++ 语法,但问题仍然很普遍)。

假设这个渲染器有一些派生的实现,比如说DirectxRendererand OpenglRenderer

现在,假设这些渲染器中只有一个(让我们坚持基于 DirectX)有一个名为“IDirect3D9Device* m_device;显然此时一切都很好”的成员 -m_device在内部使用DirectxRenderer并且不应该在抽象AbstractRenderer超类中公开。

我还添加了一些抽象的渲染接口,例如IRenderable. 这仅意味着一种纯虚方法virtual void Render(AbstractRenderer* renderer) const = 0;


这就是一些问题开始的地方。假设我正在为某个场景建模,那么这个场景中可能会有一些几何对象。

我创建抽象超类AbstractGeometricalObject和派生的基于 DirectX 的实现DirectxGeometricalObject。第二个负责存储指向 DirectX 特定的顶点和索引缓冲区的指针。

现在 - 问题。

AbstractGeometricalObject显然应该派生IRenderable接口,因为它在逻辑上是可渲染的。

如果我派生出我DirectxGeometricalObject的 from AbstractGeometricalObject,第一个应该有virtual void Render(AbstractRenderer* renderer) const { ... }方法,那Abstract...东西会带来一些麻烦。

请参阅代码以获得更好的解释:

现在我的课程看起来如下:

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(...);
}

我知道我可能担心太多了,但是在如此简单的情况下,这种沮丧意味着如果我的应用程序增长,我可能会被迫做出很多沮丧。

当然,我想避免这种情况,所以请你在设计方面给我一些更好的建议/指出我的错误。

谢谢

4

6 回答 6

3

这可能是模板模式(不要与 C++ 模板混淆)派上用场的情况。抽象类中的 publicRender应该是非虚拟的,但是让它调用一个私有的虚拟函数(例如 DoRender)。然后在派生类中,您改为覆盖 DoRender。

这是一篇深入描述模板模式与私有虚函数的使用的文章。

编辑:

我开始整理一个例子来说明我的意思,看起来架构中实际上存在一个更广泛的缺陷。您对 AbstractRenderer 的使用有点轻率,因为您强制每个几何对象密切了解特定的渲染器类型。

渲染器应该能够处理 Renderables 的公共方法,或者 Renderables 应该能够处理 Renderer 的公共方法。或者,如果确实需要如此紧密的连接,也许您可​​以给具体的渲染器一个 Renderable 工厂。我相信还有一些其他的模式也很适合。

于 2010-07-14T20:34:39.763 回答
2

我看不到您的代码想要实现什么。您将 Renderable 对象派生到 DirectXRenderables 和 OpenGLRenderables,然后在从 Renderer 派生的东西中提供 OpenGL 或 DirectX 功能。可以说,一个特定的事物使用另一个特定的事物。pure virtual识别通用渲染函数,使它们成为抽象渲染器的成员并在DirectXRenderer和中实现它们似乎更合理OpenGLRenderer。然后 aIRenderable会有一个成员函数 draw 大致如下:

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
  }
} 
于 2010-07-14T20:52:53.623 回答
1

使用模板,您可以将 IRendable 拆分为两个类,一个用于两种渲染器类型。这可能不是最好的答案,但它确实避免了动态转换的需要:

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)
}
于 2010-07-14T23:18:44.503 回答
0

让我们远离编译器并考虑理论。如果DirectxGeometricalObject::Render期望DirectxRenderer作为参数而不是任何AbstractRenderer,那么其他一些OtherGeometricalObject::Render人可能会期望OtherRenderer对象作为参数。

因此,不同的实现AbstractGeometricalObject具有不同的方法签名Render。如果它们不同,那么定义 virtual 就没有任何意义AbstractGeometricalObject::Render

如果您声明AbstractGeometricalObject::Render(AbstractRenderer*),那么您应该能够将任何渲染器传递给任何几何对象。在你的情况下,你不能因为dynamic_cast会失败。

于 2010-07-14T23:58:14.987 回答
0

使用 setter 设置渲染器 var 并将其转换为该位置的正确类型。

于 2010-07-14T20:35:12.243 回答
0

看看Bridge设计模式是否对您有帮助:“将抽象与其实现分离,以便两者可以独立变化。” 在您的示例中, AbstractGeometricalObject 将指向一个实现,一个具有特定于平台的子类的纯虚拟接口。棘手的部分是花时间去发现那个接口。

于 2010-07-14T21:37:36.370 回答