Вопрос о наследовании C ++ - PullRequest
1 голос
/ 15 июля 2010

У меня есть следующая проблема в архитектуре приложения, и я хочу ее решить (извините за большой объем текста).

Я создаю прототип игрового движка, и у меня есть базовая аннотацияclass AbstractRenderer (я буду использовать синтаксис C ++, но проблема по-прежнему общая).

Предположим, что есть некоторые производные реализации этого рендерера, скажем, DirectxRenderer иOpenglRenderer.

Теперь предположим, что только один из этих средств визуализации (давайте придерживаться DirectX) имеет член с именем IDirect3D9Device* m_device; Очевидно, что в этот момент все в порядке - m_deviceиспользуется внутри DirectxRenderer и не должен быть представлен в абстрактном суперклассе AbstractRenderer.

Я также добавляю некоторый абстрактный интерфейс рендеринга, например IRenderable.Это означает просто один чисто виртуальный метод virtual void Render(AbstractRenderer* renderer) const = 0;


И это то место, где начинаются некоторые проблемы.Предположим, я моделирую какую-то сцену, поэтому в этой сцене, вероятно, будут некоторые геометрические объекты.

Я создаю абстрактный суперкласс AbstractGeometricalObject и производную реализацию на основе DirectX DirectxGeometricalObject.Второй будет отвечать за хранение указателей на специфичные для DirectX буферы вершин и индексов.

Теперь - проблема.

AbstractGeometricalObject должно, очевидно, получить IRenderable интерфейс, потому что это рендеринг в логическом смысле.

Если я получу DirectxGeometricalObject из 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(...);
}

Я знаю, что, вероятно, слишком беспокоюсь, но это удручаетв таком простом случае означает, что меня могут заставить делать много откликов в случае роста моего приложения.

Определенно, я бы хотел избежать этого, поэтому, пожалуйста, не могли бы выпосоветуйте мне что-нибудь лучше с точки зрения дизайна / укажите мои ошибки.

Спасибо

Ответы [ 6 ]

3 голосов
/ 15 июля 2010

Это может быть ситуация, когда шаблонный шаблон (не путать с шаблонами C ++) пригодится. Публичный Render в абстрактном классе должен быть не виртуальным, но должен вызывать приватную виртуальную функцию (например, DoRender). Затем в производных классах вы переопределяете DoRender.

Вот статья, которая углубленно описывает использование шаблона шаблона с частными виртуальными функциями .

Edit:

Я начал собирать пример того, что имел в виду, и кажется, что на самом деле в архитектуре есть более широкий недостаток. Ваше использование AbstractRenderer несколько легкомысленно, поскольку вы заставляете каждый геометрический объект внимательно знать о конкретном типе рендерера.

Либо средство визуализации должно быть в состоянии отработать общедоступные методы Renderables, либо Renderables должно быть в состоянии отработать общедоступные методы Renderer. Или, может быть, вы можете дать бетононосителям фабрику Renderable, если действительно требуется такая тесная связь. Я уверен, что есть и другие шаблоны, которые тоже подойдут.

2 голосов
/ 15 июля 2010

Я не вижу, чего хочет ваш код. Вы выводите объекты Renderable в DirectXRenderables и OpenGLRenderables, а затем предоставляете функции OpenGL или DirectX в чем-то, производном от Renderer. Определенная вещь использует, так сказать, другую конкретную вещь. Казалось бы, гораздо разумнее определить общие функции рендеринга, сделать их pure virtual членами вашего абстрактного рендерера и реализовать их в DirectXRenderer и OpenGLRenderer. Тогда IRenderable будет иметь функцию-член, нарисованную примерно так:

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
  }
} 
1 голос
/ 15 июля 2010

Используя шаблоны, вы можете разделить 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)
}
0 голосов
/ 15 июля 2010

Давайте отойдем от компиляторов и рассмотрим теорию.Если DirectxGeometricalObject::Render ожидает DirectxRenderer в качестве параметра, а не AbstractRenderer, то некоторые другие OtherGeometricalObject::Render, вероятно, ожидают OtherRenderer объект в качестве параметра.

Таким образом, разные реализации AbstractGeometricalObject имеют разные сигнатурыих Render методов.Если они отличаются, то нет смысла определять виртуальный AbstractGeometricalObject::Render.

. Если вы объявляете AbstractGeometricalObject::Render(AbstractRenderer*), тогда вы сможете передавать любой рендерер любому геометрическому объекту.В вашем случае вы не можете, потому что dynamic_cast потерпит неудачу.

0 голосов
/ 15 июля 2010

Посмотрите, поможет ли вам шаблон проектирования Bridge : «Отделите абстракцию от его реализации, чтобы они могли варьироваться независимо».В вашем примере AbstractGeometricalObject будет указывать на реализацию, чистый виртуальный интерфейс с подклассами, специфичными для платформы.Сложная часть занимает время, чтобы обнаружить этот интерфейс.

0 голосов
/ 15 июля 2010

Используйте сеттер для установки рендера var и приведите его к нужному типу в этом одном месте.

...