2010-11-14 3 views
8

Я знаю, какой шаблон посетителя и как его использовать; этот вопрос не является дубликатом этого one.C++: сомнения относительно шаблона посетителя


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

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

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

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

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

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

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

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


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

+2

Паттерн посетитель не способ получить множественную диспетчеризацию. Это требует многократной отправки, и поэтому метод эмулирования множественной отправки обычно преподается программистам на С ++ как часть шаблона посетителя. – AProgrammer

+0

Вам нужно несколько отправки? Возможно, образец NVI соответствует вашим потребностям. Похоже, что дизайн NVI может помочь, но для этого требуются не виртуальные визуальные и частные методы типа doRender. Так что, возможно, абстрактный класс между формой и шейпингом называется shaperenderengine. ShapeCircle может затем выводиться из формы или из shaperenderengine ..... – Eric

ответ

13

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


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

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

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

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

+0

Итак, вы думаете, что я должен добавить поддержку для посетителей в моей библиотеке? Не могли бы вы рассказать мне о какой-либо библиотеке, которая использует шаблон посетителя, чтобы я мог вдохнуть вдохновение? Единственный, о котором я знаю, это Boost :: Variant, но этот случай сильно отличается от «обычного» использования, подобного моему. – peoro

+0

@peoro: Я бы не знал, так как я не знаю вашу либу. Честно говоря, я не могу представить ни одного C++-библиотеки, которая поставляется с посетителями.Я согласен с тем, что 'boost :: variant' не является вашим обычным посетителем. Мне жаль, что я так мало помог. – sbi

0

Это не похоже на случай для шаблона посещения для меня.

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

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

+0

Некоторые функции в моей библиотеке возвращают указатели/ссылки на 'Shape'. Например, «CompositeShape», о котором я говорил, имеет набор «Shape *», который может быть любого типа. Таким образом, даже если я держу в классе-оболочке («RenderableShape») «Shape», выделенном в коде приложения, я бы не смог обработать «Shape *», возвращенный библиотечными функциями. – peoro

+0

Разве вы не можете просто обернуть возвращенную форму? –

+0

Да, но обертывание его во что? Я не могу знать, является ли это CircleShape или PolygonShape ... Нужна ли какая-то переброска, но тогда зачем беспокоиться об обертке? Я всегда мог его использовать. – peoro

0

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

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

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

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

CircleRenderer hasA Cicle, knows how to render Circles 

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

+0

Некоторые функции в моей библиотеке возвращают указатели/ссылки на 'Shape'. Например, «CompositeShape», о котором я говорил, имеет набор «Shape *», который может быть любого типа. Таким образом, даже если я держу в классе-оболочке 'Shape', выделенный в коде приложения, я бы не смог обработать' Shape * ', возвращенный библиотечными функциями. – peoro

0

Там через множество возможных решений, но вы можете сделать это, например: Начать новую иерархию, которая делает 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

Некоторые функции в моей библиотеке возвращают указатели/ссылки на 'Shape'. Например, «CompositeShape», о котором я говорил, имеет набор «Shape *», который может быть любого типа. Таким образом, даже если я держу в классе-оболочке 'Shape', выделенный в коде приложения, я бы не смог обработать' Shape * ', возвращенный библиотечными функциями. – peoro

0

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

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

Мне не нравятся реализации с визитом, посещение, посещениеВам, acceptA, acceptB, acceptWhatever. Это абсолютно неправильно, ИМХО.

Если у вас есть шанс, пожалуйста, взгляните на an article I've written about this.

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

Я надеюсь, что это помогает

Приветствия

Смежные вопросы