C ++: сомнения в структуре посетителей - PullRequest
9 голосов
/ 14 ноября 2010

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


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

Часто мне нужно добавлять функции в некоторые классы, но без добавления этих новых функций в библиотеку. Позвольте мне привести реальный пример:

В этой библиотеке у меня есть класс Shape, унаследованный от CircleShape, PolygonShape и CompositeShape.

Сейчас я разрабатываю графическое приложение, в котором мне нужно визуализировать эти Shape, но я не хочу помещать виртуальную функцию render в базовый класс Shape, так как некоторые из моих проектов, которые используют Shape не выполняет никакого рендеринга, и другие графические проекты могут использовать другие механизмы рендеринга (я использую Qt для этого проекта, но для игры я бы использовал OpenGL, поэтому функция render потребует других реализаций).

Самый известный способ сделать это, конечно, использовать Visitor Pattern, но это вызывает у меня несколько сомнений:

Любой класс любой библиотеки может быть расширен как мой Shape. Большинство публичных библиотек (почти все) не предоставляют никакой поддержки Шаблон посетителя; Зачем? почему я должен?

Шаблон посетителя - это способ симуляции двойной диспетчеризации в C ++. Он не является родным в C ++ и требует явной реализации, что делает интерфейс класса более сложным: я не думаю, что функция applyVisitor должна быть на том же уровне, что и функции моего класса, я вижу это как нарушение абстракции.

Явное повышение качества Shape с dynamic_cast дороже, но для меня это выглядит как более чистое решение.


Итак, что мне делать? Реализация двойной диспетчеризации во всех моих библиотечных классах? Что если библиотека, предоставляющая Shape, не моя, а какая-то библиотека GPL, найденная в Интернете?

Ответы [ 5 ]

14 голосов
/ 14 ноября 2010

Первое: "Шаблон посетителя - это способ имитировать двойную диспетчеризацию в C ++." Это не совсем верно. На самом деле, двойная диспетчеризация - это одна из форм множественной диспетчеризации, которая является способом моделирования (отсутствующих) мульти-методов в C ++.


Должны ли операции с иерархией классов выполняться путем добавления виртуальных функций или добавления посетителей , определяется вероятностями добавление классов против операций добавления:

  • Если число классов продолжает меняться быстрее, чем количество операций, использует виртуальные функции . Это потому, что добавление класса требует изменения всех посетителей.
  • Если количество классов относительно стабильно по сравнению с количеством операций, использует посетителей . Это связано с тем, что добавление виртуальной функции требует изменения всех классов в иерархии.

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

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

0 голосов
/ 30 января 2011

Я абсолютно понимаю, что вы сказали, и я разделяю те же проблемы.Проблема в том, что шаблон посетителя не очень четко определен, и оригинальное решение для него вводит в заблуждение, ИМХО.Вот почему существует так много вариантов этого паттерна.

В частности, я считаю, что правильная реализация должна поддерживать устаревший код, я имею в виду: бинарный файл, который вы вообще потеряли исходный код, не так лиЭто?Это то, что говорится в определении: вам никогда не приходилось менять исходные структуры данных.

Мне не нравятся реализации с visitA, visitB, visitWh what, acceptA, acceptB, acceptWhothing.Это абсолютно неправильно, ИМХО.

Если у вас есть шанс, посмотрите статью, которую я написал об этом .

Это Java, но выможно легко портировать на C ++, если вы найдете это полезным для ваших целей.

Надеюсь, это поможет

Приветствия

0 голосов
/ 14 ноября 2010

Итак, есть класс xxxShape, который каким-то образом содержит информацию, которая «управляет» рендерингом.Для кругов это может быть центр, радиус, для квадратов некоторые угловые координаты или что-то подобное.Может быть, некоторые другие вещи о заливках и цветах.

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

Но, по-видимому, у вас есть достаточно общедоступных методов доступа к классам, чтобы вы могли получить информацию о «вождении», иначе вы обречены.

В таком случае, почему вы не можете простооберните эти предметы:

 CircleRenderer hasA Cicle, knows how to render Circles

и так далее.Теперь используйте шаблон Visitor для классов Renderer.

0 голосов
/ 14 ноября 2010

Существует много возможных решений, но вы можете сделать это, например: Начать новую иерархию, которая отображает Shapes в определенном Context:

// contracts:

class RenderingContext {
public: virtual void DrawLine(const Point&, const Point&) = 0; 
    // and so on...
};

class ShapeRenderer {
public: virtual void Render(RenderingContext&) = 0;
};

// implementations:

class RectangleRenderer : public ShapeRenderer {
 Rectangle& mR;

public: 
 virtual void Render(RenderingContext& pContext) {
   pContext.DrawLine(mR.GetLeftLower(), mR.GetRightLower());
   // and so on...
 }

 RectangleRenderer(Rectangle& pR) : mR(pR) {}
};
0 голосов
/ 14 ноября 2010

Это не похоже на случай паттерна Visitor для меня.

Я хотел бы предложить вам класс RenderableShape, который агрегирует объект Shape, а затем создать подклассы для каждой фигуры. RenderableShape будет иметь виртуальный render метод.

Если вы хотите поддерживать несколько механизмов рендеринга, вы можете иметь базовый класс RenderContext, который абстрагирует операции рисования, с подклассами для каждого механизма рендеринга, причем каждый подкласс реализует операции рисования в терминах своего механизма рендеринга. Затем вы RenderableShape::render берете RenderContext в качестве аргумента и рисуете его, используя его абстрагированный API.

...