|
© 2026
Александр Легалов
Содержание
Адаптер (Adapter)
Адаптер преобразует интерфейс одного класса в другой интерфейс, на который рассчитаны клиенты. Он обеспечивает совместную работу классов с несовместимыми интерфейсами, которая без него была бы невозможна. Применяется когда:
- нужно использовать существующий класс, но его интерфейс не соответствует желаемым потребностям;
- требуется создать повторно используемый класс, который должен взаимодействовать с заранее неизвестными или не связанными с ним классами, имеющими несовместимые интерфейсы;
- (только для адаптера объектов!) нужно использовать несколько существующих подклассов, но непрактично адаптировать их интерфейсы путем порождения новых подклассов от каждого. В этом случае адаптер объектов может приспосабливать интерфейс их общего родительского класса.
В ОО подходе Адаптер может быть реализован с использованием множественного наследования.
Структура паттерна Адаптер с использованием множественного наследования
Можно также обойтись и без множественного наследования, подменив его композицией.
Структура паттерна Адаптер с использованием множественного наследования
Объектно-ориентированная реализация Адаптера
В качестве демонстрационного примера вместо животных хотелось бы опять возвратиться к геометрическим фигурам, на которых рассматривались вопросы эволюционного расширения программ с применением различных парадигм программирования. Разбор полетов, как мне кажется, удобнее рассмотреть на основе второго шага, когда введены три альтернативных фигуры. Это позволяет сопоставить использование обычного наследования (на примере прямоугольника), а также адаптера на основе множественного наследования (на примере треугольника) и адаптера на основе композиции (на примере круга).
Исходя из этих допущений будем считать, что клиентский код взаимодействует с обобщенной фигурой, определяющий интерфейс для взаимодействия. Фигура, как и ранее, является абстрактным классом, задающим интерфейс для потомков.
//------------------------------------------------------------------------------
// Класс, обобщающает все имеющиеся фигуры.
// Является абстрактным, обеспечивая, тем самым, проверку интерфейса
class Figure {
public:
// идентификация, порождение и ввод фигуры из потока
static Figure* In(std::ifstream &ifst);
virtual void InData(std::ifstream &ifst) = 0; // ввод данных из потока
virtual void Out(std::ofstream &ofst) = 0; // вывод данных в стандартный поток
};
Контейнер, являющийся клиентом, использует взаимодействие с фигурами в выполняемых операциях ввода и вывода.
//------------------------------------------------------------------------------
// Простейший контейнер на основе одномерного массива
//------------------------------------------------------------------------------
class Container {
enum {max_len = 100}; // максимальная длина
int len; // текущая длина
Figure *cont[max_len];
public:
void In(std::ifstream &ifst); // ввод фигур в котнейнер из входного потока
void Out(std::ofstream &ofst); // вывод фигур в выходного потока
void Clear(); // очистка контейнера от фигур
Container(); // инициализация контейнера
~Container() {Clear();} // утилизация контейнера перед уничтожением
};
//------------------------------------------------------------------------------
// Ввод содержимого контейнера
void Container::In(std::ifstream &ifst) {
while(!ifst.eof()) {
if((cont[len] = Figure::In(ifst)) != 0) {
len++;
}
}
}
//------------------------------------------------------------------------------
// Вывод содержимого контейнера
void Container::Out(std::ofstream &ofst) {
ofst << "Container contents " << len << " elements.\n";
for(int i = 0; i < len; i++) {
ofst << i << ": ";
cont[i]->Out(ofst);
}
}
Так как мы считаем, что прямоугольник разрабатывается с нуля, то реализуем его по обычной схеме с использованием наследования от фигуры.
//------------------------------------------------------------------------------
// Прямоугольник. Является подклассом фигуры по традиционной схеме
class Rectangle: public Figure {
int x, y; // ширина, высота
public:
// переопределяем интерфейс класса
virtual void InData(std::ifstream &ifst); // ввод данных из потока
virtual void Out(std::ofstream &ofst); // вывод данных в стандартный поток
Rectangle(): x{0}, y{0} {} // создание без инициализации.
};
//------------------------------------------------------------------------------
// Ввод параметров прямоугольника
void Rectangle::InData(std::ifstream &ifst) {
ifst >> x >> y;
}
//------------------------------------------------------------------------------
// Вывод параметров прямоугольника
void Rectangle::Out(std::ofstream &ofst) {
ofst << "It is Rectangle: x = " << x << ", y = " << y << "\n";
}
Пусть треугольник и круг ранее уже разработаны и не связаны с фигурой, а их методы выполняют необходимые действия, но реализуют иной интерфейс. Исходя из этого данные фигуры необходимо адаптировать к общему интерфейсу, используемому клиенту. Предположим, что треугольник полученный из вне, имеет следующую реализацию.
//------------------------------------------------------------------------------
// Треугольник. Реализован вне системы наследования.
// Необходимо подключить через адаптер.
// Методы специально переименованы для большей наглядности
class ExtTriangle {
int a, b, c; // стороны
public:
// определяем интерфейс класса
void InTriangle(std::ifstream &ifst); // ввод данных из потока
void OutTriangle(std::ofstream &ofst); // вывод данных в стандартный поток
ExtTriangle(): a{0}, b{0}, c{0} {} // создание без инициализации.
};
//------------------------------------------------------------------------------
// Ввод параметров независимого треугольника
void ExtTriangle::InTriangle(std::ifstream &ifst) {
ifst >> a >> b >> c;
}
//------------------------------------------------------------------------------
// Вывод параметров независимого треугольника
void ExtTriangle::OutTriangle(std::ofstream &ofst) {
ofst << "It is Triangle: a = "
<< a << ", b = " << b
<< ", c = " << c << "\n";
}
Пусть треугольник-адаптер, формируемый над взятым из вне треугольником будет реализован с использованием множественного наследования, реализуя обертки и над методами.
//------------------------------------------------------------------------------
// Треугольник, сформированный из внешнего описания с использованием адаптера
// Используется множественное наследование
class Triangle: public Figure, public ExtTriangle {
public:
// переопределяем интерфейс класса
void InData(std::ifstream &ifst); // ввод данных из потока
void Out(std::ofstream &ofst); // вывод данных в стандартный поток
Triangle(): ExtTriangle() {} // создание без инициализации.
};
//------------------------------------------------------------------------------
// Ввод параметров треугольника
void Triangle::InData(std::ifstream &ifst) {
InTriangle(ifst);
}
//------------------------------------------------------------------------------
// Вывод параметров треугольника
void Triangle::Out(std::ofstream &ofst) {
OutTriangle(ofst);
}
Аналогичным образом будем использовать и ранее разработанное внешнее представление круга, реализованного по той же схеме, что и внешний треугольник.
//------------------------------------------------------------------------------
// Круг. Реализован вне системы наследования.
// Необходимо подключить через адаптер
// Методы специально переименованы для большей наглядности.
class ExtCircle {
int r; // радиус
public:
// определяем интерфейс класса
void InCircle(std::ifstream &ifst); // ввод данных из потока
void OutCircle(std::ofstream &ofst); // вывод данных в стандартный поток
ExtCircle(): r{0} {} // создание без инициализации.
};
//------------------------------------------------------------------------------
// Ввод параметров независимого круга
void ExtCircle::InCircle(std::ifstream &ifst) {
ifst >> r;
}
//------------------------------------------------------------------------------
// Вывод параметров независимого круга
void ExtCircle::OutCircle(std::ofstream &ofst) {
ofst << "It is Circle: r = " << r << "\n";
}
Для приведения внешнего круга к требуемому интерфейсу вместо наследования будем использовать композицию, что является вторым вариантом реализации паттерна Адаптер.
//------------------------------------------------------------------------------
// круг - адаптирующий внешнюю реализацию
class Circle: public Figure {
ExtCircle c; // Внешний круг
public:
// переопределяем интерфейс класса
void InData(std::ifstream &ifst); // ввод данных из потока
void Out(std::ofstream &ofst); // вывод данных в стандартный поток
Circle() {} // создание без инициализации.
};
//------------------------------------------------------------------------------
// Ввод параметров круга
void Circle::InData(std::ifstream &ifst) {
c.InCircle(ifst);
}
//------------------------------------------------------------------------------
// Вывод параметров круга
void Circle::Out(std::ofstream &ofst) {
c.OutCircle(ofst);
}
Полностью программа представлена в прилагаемых архиве и репозитории на гитхабе. Ее остальные фрагменты практически ничем не отличаются от ОО кода, представленного при рассмотрении эволюционного расширения.
Адаптер с процедурно-параметрических позиций
Имитация Адаптера с применением 4П осуществляется банально. Почему? Да просто потому, что обобщение само по сути является адаптером над различными основами специализации. Это можно увидеть и на рисунке, иллюстрирующем особенности реализации процедурно-параметрического обобщения в PPC.
Структура ПП обобщения в ППС
Подключаемые к обобщению основы специализаций по сути являются внешними решениями, имеющими функции их обработки со своей специфической сигнатурой. Поэтому формируемый на основе обобщенной функции, задающей клиентский интерфейс, обработчик специализации использует функцию основы специализации, что определяет непосредственную адаптацию. Даже если у основы нет нужной функции, ее отсутствие можно считать пустой функцией и реализовать необходимые действия непосредственно в обработчике специализации. Подключение через указатели позволяет использовать дополнительную функциональность. Например, реализовать в качестве специализаций подключение обобщения, расширяемого своими специализациями.
Отдельно стоит отметить подключение неименованных структур. Отсутствие внешнего определения и, следовательно функций, осуществляющих обработку в целом является непринципиальным и аналогично подключению именованных основ специализации, не содержащих функций, для которых вычисления проводятся в обработчиках специализаций.
Примечание. Можно отметить, что пока использование неименованных структуро в качестве специализаций не реализовано. Но будет сделано...
Процедурно-параметрическая "имитация" Адаптера
Как и в случае ОО реализации, за основу взята ПП программа из второго шага эссе об эволюцинном расширении. Вообще-то, можно было бы даже и не менять используемую там программу, раз ПП обобщение и его обработчики по сути являются адаптерами. Но хотелось бы рассмотреть особенности ряда из вариантов использования представленных выше на рисунке. Поэтому для трех геометрических фигур будут реализованы следующие решения.
- Прямоугольник и его обработка остаются без изменений, демонстрируя классическую адаптацию.
- У основы специализации, представляющей треугольник, "отберем" внешние функции, описав необходимые действия непосредственно в обработчике специализации. Кстати, можно отметить, что этот перенос ускоряет вычисления за счет того, что убираются лишние вызовы функций. Возможно в ряде ситуаций будет полезно.
- Круг подключим через обобщение, что повысит гибкость по смене одного круга на другой. По этой же схеме можно подключать и обобщения других данных. Но это рассматривать не будем.
Исходя из этого описание обобщения, основ специализаций и самих специализаций будет представлено следующим образом.
//------------------------------------------------------------------------------
// структура, обобщающая фигуры
typedef struct Figure {} <> Figure;
//------------------------------------------------------------------------------
// прямоугольник
typedef struct Rectangle {
int x, y; // ширина, высота
} Rectangle;
//------------------------------------------------------------------------------
// Фигура - прямоугольник
Figure + < rect: Rectangle; >;
//------------------------------------------------------------------------------
// треугольник
typedef struct Triangle {
int a, b, c; // стороны треугольника
} Triangle;
//------------------------------------------------------------------------------
// Фигура - треугольник
Figure + < trian: Triangle; >;
// круг
typedef struct Circle {
int r; // радиус круга
} Circle;
//------------------------------------------------------------------------------
// Фигура - круг
Figure + < circ: Circle*; >;
В данной ситуации изменения относительно программы, используемой в качестве основы, затронули только круг, подключенный через указатель.
Реализация функций, осуществляющих обработку адаптированного прямоугольник, остается прежней, так как и он сам не меняется. Отсутствие функций у основы, определяющей треугольник, можно компенсировать их отдельным созданием с последующим вызовом (как и в прямоугольнике). Но в данном случае, наплевав на инкапсуляцию, реализуем необходимые действия непосредственно в обработчике специализаций. Изменения, затрагивающие обработку круга связаны только с тем, что необходимо к основе специализации обратиться через указатель. Ниже все функции, осуществляющие использование фигур (а это только их ввод и вывод), собраны воедино.
//==============================================================================
// Функции, осуществляющие ввод фигур из потока
//==============================================================================
//------------------------------------------------------------------------------
// Обобщающая функция для ввода параметров фигуры
void FigureIn<Figure *f>(FILE* file) = 0;
//------------------------------------------------------------------------------
// Ввод параметров прямоугольника из потока
void RectangleIn(Rectangle *r, FILE* ifst) {
fscanf(ifst, "%d", &(r->x));
fscanf(ifst, "%d", &(r->y));
}
//------------------------------------------------------------------------------
// Ввод прямоугольника как фигуры
void FigureIn<Figure.rect *f>(FILE* ifst) {
RectangleIn(&(f->@), ifst);
}
//------------------------------------------------------------------------------
// Ввод треугольника как фигуры
void FigureIn<Figure.trian *f>(FILE* ifst) {
fscanf(ifst, "%d", &(f->@a));
fscanf(ifst, "%d", &(f->@b));
fscanf(ifst, "%d", &(f->@c));
}
//------------------------------------------------------------------------------
// Ввод параметров круга из потока
void CircleIn(Circle *c, FILE* ifst) {
fscanf(ifst, "%d", &(c->r));
}
//------------------------------------------------------------------------------
// Ввод круга как фигуры
void FigureIn<Figure.circ *f>(FILE* ifst) {
Circle* c = f->@;
CircleIn(c, ifst);
}
//==============================================================================
// Функции, осуществляющие вывод фигур в поток
//==============================================================================
//------------------------------------------------------------------------------
// Обобщающая функция для вывода параметров фигуры
void FigureOut<Figure *f>(FILE* file) = 0;
//------------------------------------------------------------------------------
// Вывод параметров прямоугольника в форматируемый поток
void RectangleOut(Rectangle *r, FILE* ofst) {
fprintf(ofst, "It is Rectangle: x = %d, y = %d\n", r->x, r->y);
}
//------------------------------------------------------------------------------
// Вывод прямоугольника как фигуры
void FigureOut<Figure.rect *f>(FILE* ofst) {
RectangleOut(&(f->@), ofst);
}
//------------------------------------------------------------------------------
// Вывод треугольника как фигуры
void FigureOut<Figure.trian *f>(FILE* ofst) {
fprintf(ofst, "It is Triangle: a = %d, b = %d, c = %d\n", f->@a, f->@b, f->@c);
}
//------------------------------------------------------------------------------
// Вывод параметров круга в форматируемый поток
void CircleOut(Circle *c, FILE *ofst) {
fprintf(ofst, "It is Circle: r = %d\n", c->r);
}
//------------------------------------------------------------------------------
// Вывод круга как фигуры
void FigureOut<Figure.circ *f>(FILE* ofst) {
Circle* c = f->@;
CircleOut(c, ofst);
}
Использование непосредственного подключения основ специализаций для одних данных и подключения через указатели для других ведет к неоднородности при формировании специализаций обобщения. Это отражается при динамическом создании фигур во время ввода. Используемый в примере простой не эволюционный ввод фигур требует модификации соответствующей функции из-за раздельного создания обобщения для круга и самого круга, подключаемого к этому обобщению (но в данном случае речь не идет об эволюции, чтобы упираться).
//------------------------------------------------------------------------------
// Ввод параметров одной из фигур из файла
Figure* FigureCreateAndIn(FILE* ifst) {
Figure *sp;
Figure.circ * cp;
int k = 0;
fscanf(ifst, "%d", &k);
switch(k) {
case 1:
sp = create_spec(Figure.rect);
break;
case 2:
sp = create_spec(Figure.trian);
break;
case 3:
cp = create_spec(Figure.circ);
// Для круга формируется основа, подключаемая через указатель
cp->@ = malloc(sizeof(Circle));
sp = cp;
break;
default:
return 0;
}
FigureIn<sp>(ifst);
return sp;
}
Неоднородность фигур также сказывается и на изменение простого варианта удаления из из массива. Он заменяется на использование обобщенного полиморфного удаления, когда удаление прямоугольника и треугольника осуществляется обработчиком по умолчанию, а данные круга удаляются в два приема его обработчиком специализации. Контейнер при очистке массива использует сформированный обобщенный обработчик, обеспечивающий удаления фигур неоднородной структуры.
//==============================================================================
// Полиморфная функция удаления фигур, созданных динамически.
// Необходимо из-за того, что подключение основ специализаций в разных
// специализациях осуществляется по разному.
//==============================================================================
//------------------------------------------------------------------------------
// Обобщенная функция перекрывает все ситуации, когда фигура состоит только
// из головной части
void FigureDestroy<Figure* f>() {
free(f);
}
//------------------------------------------------------------------------------
// Уникальным является удаление спеиализации круга, состоящего из обобщения и
// динамически подключаемой основы.
void FigureDestroy<Figure.circ* f>() {
free(f->@);
free(f);
}
//------------------------------------------------------------------------------
// Инициализация контейнера
void ContainerInit(Container *c) {
c->len = 0;
}
//------------------------------------------------------------------------------
// Очистка контейнера от элементов (освобождение памяти)
void ContainerClear(Container *c) {
for(int i = 0; i < c->len; i++) {
// Вместо прямой очистки памяти для однородных обобщений
// используется полиморфное удаление неоднородных фигур
FigureDestroy<c->cont[i]>();
}
ContainerInit(c);
}
Подводя итоги, можно отметить лишь то, что обобщение и подключаемая к нему основа специализации по сути и образуют Адаптер.
Содержание
|