Пример паттерна «Мост» из «Объясненных паттернов»

Я изучаю пример паттерна «Мост» из «Объясненных паттернов».
Пример, на который я смотрю, это Пример 10.3, который можно найти на

http://www.netobjectives.com/resources/books/design-patterns-explained/cpp-code-examples/chapter10#10-3

Конкретная путаница, которую я имею, связана с классом Shape и его производными классами.

#pragma once
#include "Drawing.h"
class Shape
{
public:
Shape(Drawing *aDrawing);
virtual void draw()= 0;

protected:
Drawing *myDrawing;
void drawLine( double, double, double, double);
void drawCircle( double, double, double);

public:
~Shape(void);
};

В классе Круга у нас есть

#pragma once
#include "Shape.h"
class Circle : public Shape
{
public:
Circle(Drawing*, double, double, double);
virtual void draw();
virtual void drawCircle(double, double, double)=0;

public:
~Circle(void);
protected:
double _x, _y, _r;
};

Итак, у меня есть вопрос:
почему может drawCircle быть чисто виртуальным в унаследованном классе, учитывая, что метод фактически реализован в базовом классе?

1

Решение

Представьте, что вы создаете модуль для рисования фигур с использованием различных API (Windows GDI, немного API для смартфонов, OpenGL, что угодно). Имея типичную иерархию abstract Shape <--- concrete Circle а также abstract Shape <--- concrete Rectangle вам придется перекомпилировать и заново развернуть Circle а также Rectangle каждый раз, когда вы добавляете новый фреймворк, и каждый раз что-то меняется в существующем фреймворке. Такие изменения могут даже включать модификацию конструкторов этих классов, поэтому пользователям вашего модуля также придется изменить свой код.

Пример: у вас есть рабочая первая версия вашего модуля со следующим интерфейсом для Circle:

class Circle : public Shape
{
public:
Circle(int x, int y, int radius);

void draw(...);
};

Затем случается, что причины оптимизации одной из платформ заставляют вас знать DPI разрешение текущей платформы заранее (до фактического рисования круга). Итак, вам придется изменить конструктор:

class Circle : public Shape
{
public:
Circle(int x, int y, int radius, int dpi);

void draw(...);
};

и клиенты вашего кода должны будут перекомпилировать свои приложения. Конечно, можно было бы избежать некоторых попыток избежать этого (например, ввести CircleWithDpi), но они приведут к высокосвязанному и трудно поддерживаемому коду. Если вы используете шаблон моста, вы можете оставить свой четкий дизайн без изменений и по-прежнему выражать свой домен (в общем, концепция «круга» не должна ничего знать о вещи, называемой «разрешением dpi»).

Итак, имея:

class Circle : public Shape
{
public:
Circle(int x, int y, int radius);

virtual void draw(...) = 0;
};

а также

class CircleImpl : public Circle
{
public:
CircleImpl(int x, int y, int radius, int dpi);
//perform some calculations before drawing for optimization

void draw(...);
//draw using appropriate API
};

а также

class ShapeFactory
{
public:
virtual Circle* CreateCircle(int x, int y, int radius) = 0;
};

Конечно, у вас будет много CircleImpls — каждая для другой платформы, которую поддерживает ваш модуль (так, CircleImplGDI, CircleImplTk, CircleImplOpenGL так далее.).

В реализации ShapeFactory вы бы создали конкретный CircleImpl соответственно, и клиент вашего модуля не должен ничего знать об этом. Этот пример является упрощенной версией той, на которую вы дали ссылку. Обратите внимание, что сейчас, когда один из возможных CircleImpls используется как Circle никакие абстрактные классы не создаются, поэтому это также должно прояснить вашу проблему с абстрактным производным классом.

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

Другой уровень абстракции дает вам возможность отложить еще несколько решений относительно вашего кода — сначала мы отложим решение о том, какие формы мы имеем. Затем, имея Circle а также Rectangle мы откладываем решение о том, как их нарисовать. А отложенные решения дают нам отсоединенный, гибкий код (как продемонстрировано на примере с «добавленным DPI»).

4

Другие решения

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

1