Я изучаю пример паттерна «Мост» из «Объясненных паттернов».
Пример, на который я смотрю, это Пример 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
быть чисто виртуальным в унаследованном классе, учитывая, что метод фактически реализован в базовом классе?
Представьте, что вы создаете модуль для рисования фигур с использованием различных 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;
};
Конечно, у вас будет много CircleImpl
s — каждая для другой платформы, которую поддерживает ваш модуль (так, CircleImplGDI
, CircleImplTk
, CircleImplOpenGL
так далее.).
В реализации ShapeFactory
вы бы создали конкретный CircleImpl
соответственно, и клиент вашего модуля не должен ничего знать об этом. Этот пример является упрощенной версией той, на которую вы дали ссылку. Обратите внимание, что сейчас, когда один из возможных CircleImpl
s используется как Circle
никакие абстрактные классы не создаются, поэтому это также должно прояснить вашу проблему с абстрактным производным классом.
Основная идея этого шаблона состоит в том, чтобы иметь два уровня абстракции: Shape
это абстрактная геометрическая концепция, Circle
а также Rectangle
более конкретны, чем Shape
но в контексте многих технических возможностей для их рисования они все еще довольно абстрактны. Конкретные представления конкретных фигур существуют, когда вы знаете контекст: например, рисование на растре или использование векторной графики.
Другой уровень абстракции дает вам возможность отложить еще несколько решений относительно вашего кода — сначала мы отложим решение о том, какие формы мы имеем. Затем, имея Circle
а также Rectangle
мы откладываем решение о том, как их нарисовать. А отложенные решения дают нам отсоединенный, гибкий код (как продемонстрировано на примере с «добавленным DPI»).
Чистые виртуальные методы разрешены в любом классе, если вы не пытаетесь создать экземпляр этого класса.