Вопрос по дизайну (наследование, полиморфизм)

У меня есть вопрос о проблеме, с которой я борюсь. Надеюсь, вы можете терпеть меня.

Представьте, что у меня есть класс Object, представляющий базовый класс иерархии физических объектов. Позже я наследую от него, чтобы создать классы Object1D, Object2D и Object3D. Каждый из этих производных классов будет иметь определенные методы и атрибуты. Например, 3D-объект может иметь функцию загрузки 3D-модели, которая будет использоваться средством визуализации.

Итак, у меня было бы что-то вроде этого:

class Object {};
class Object1D : public Object { Point mPos; };
class Object2D : public Object { ... };
class Object3D : public Object { Model mModel; };

Теперь у меня был бы отдельный класс с именем Renderer, который просто принимает объект в качестве аргумента и, ну, рендерит его :-) Подобным образом я хотел бы поддерживать разные виды рендереров. Например, я мог бы иметь стандартную версию, на которую мог бы полагаться каждый объект, а затем предоставить другие специальные средства визуализации для некоторых объектов:

class Renderer {};  // Default one
class Renderer3D : public Renderer {};

И вот моя проблема. Класс рендерера должен получить объект в качестве аргумента, например, в конструкторе, чтобы получить любые данные, необходимые для рендеринга объекта.

Все идет нормально. Но Renderer3D должен получить аргумент Object3D, чтобы получить не только основные атрибуты, но и конкретные атрибуты 3D-объекта.

Конструкторы будут выглядеть так:

CRenderer(Object& object);
CRenderer3D(Object3D& object);

Теперь, как мне указать это в общем виде? Или еще лучше, есть ли лучший способ спроектировать это?

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

Заранее спасибо!


person Dan    schedule 09.04.2010    source источник
comment
Ребят, большое спасибо за помощь. Это действительно очень ценно, и я провел последние несколько часов, переосмысливая это. Пожалуйста, смотрите мои комментарии ниже к каждому ответу.   -  person Dan    schedule 10.04.2010
comment
вы можете использовать имя, отличное от Object.   -  person John Saunders    schedule 10.04.2010


Ответы (5)


Один из подходов заключается в том, чтобы позволить Объектам создавать свои собственные средства визуализации с помощью метода getRenderer(), который возвращает средство визуализации соответствующего типа. Это пример шаблона Factory Method. В зависимости от особенностей вашего языка реализации метод getRenderer может быть абстрактным методом в вашем родительском классе Object с возвратом экземпляра родительского класса/интерфейса Renderer, который конкретный Renderer расширяет/реализует.

person Jason Jenkins    schedule 09.04.2010

Один из способов справиться с этой связью — использовать фабрики. Универсальная фабрика создает объекты и визуализаторы. 2D-фабрика создает 2D-объекты и совместимые 2D-рендереры, 3D-фабрика создает 3D-объекты и совместимые 3D-рендереры.

abstract class Factory {
  abstract Object createObject();
  abstract Renderer createRenderer();
}

class 2DFactory {
  Object createObject() { return new 2DObject(); }
  Renderer createRenderer() { return new 2DRenderer(); }
}

// similarly for 3D

class Client {
  Client(Factory factory) { ... }
  void render() {
    factory.createRenderer().render(factory.createObject());
  }
}

Таким образом, если вы не смешиваете продукты с разных фабрик, вы можете сохранить чистоту своего клиентского кода. Если клиентский код имеет дело только с одним семейством объектов и средств визуализации одновременно, тривиально передать ему соответствующую фабрику, а затем позволить ему использовать объекты и средства визуализации, не зная их конкретного типа. Вариантом этого является создание объектом совместимого средства визуализации с помощью фабричного метода, как >предложено Джейсоном.

Другой возможностью было бы использование шаблонов/дженериков, но это зависит от языка. Ваш язык реализации похож на C++, поэтому я рискну в этом направлении. Вы можете использовать специализацию шаблона:

template<class T> class Renderer {
  void render(T object) { ... }
};

template<> class Renderer<Object3D> {
  void render(Object3D object) {
    // render the 3D way
  }
};

// similarly for 2D
person Péter Török    schedule 09.04.2010

Это выполнимо?

template<typename T> class Renderer {};
class Renderer3D : public Renderer<Object3D> {};
person leedm777    schedule 09.04.2010

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

Dispatch(renderer)

Затем каждый подкласс Object переопределяет Dispatch и вызывает соответствующий метод предоставленного средства визуализации для своего типа:

Object2D.Dispatch(renderer)
{
   renderer.Render2D(self)
}

Вы также можете использовать интерфейсы, чтобы сделать это более общим. Создайте интерфейс для рендеринга каждого типа объекта и проверьте, поддерживается ли правильный интерфейс вашим методом отправки:

Object2D.Dispatch(renderer)
{
    render2d = render as IRender2D
    if render2d found
        render2d.Render(self)
}
person Peter Ruderman    schedule 09.04.2010
comment
Правильно, поэтому в этом случае каждый Renderer будет иметь другую функцию. Я не мог использовать общий базовый класс, но, похоже, другого пути нет. - person Dan; 10.04.2010

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

Render(){
    myRenderer.Render(this)
}
person derdo    schedule 09.04.2010