Вопрос о дизайне (наследование, полиморфизм) - PullRequest
4 голосов
/ 10 апреля 2010

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

Представьте, что у меня есть класс 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, который просто принимает Object в качестве аргумента и, хорошо, отрисовывает его :-) Аналогичным образом, я бы хотел поддерживать разные виды рендеров. Например, у меня может быть по умолчанию тот, на который может положиться каждый объект, и затем предоставить другие конкретные средства визуализации для каких-либо объектов:

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

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

Пока все хорошо. Но Renderer3D должен получить аргумент Object3D, чтобы получить не только базовые атрибуты, но и конкретные атрибуты трехмерного объекта.

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

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

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

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

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

Ответы [ 5 ]

4 голосов
/ 10 апреля 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
3 голосов
/ 10 апреля 2010

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

1 голос
/ 10 апреля 2010

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

Dispatch(renderer)

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

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

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

Object2D.Dispatch(renderer)
{
    render2d = render as IRender2D
    if render2d found
        render2d.Render(self)
}
1 голос
/ 10 апреля 2010

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

template<typename T> class Renderer {};
class Renderer3D : public Renderer<Object3D> {};
0 голосов
/ 10 апреля 2010

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

Render(){
    myRenderer.Render(this)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...