
#include <stddef.h>
//Target: a pointer to a function plays Request()
typedef struct CText {
void (*getText)(const struct CText *self, char *buffer, size_t size);
} CText;
//Adaptee
typedef struct {
const char *rows[10];
size_t count;
} StringList;
void add(StringList *list, const char *value) {
if (list->count < 10) {
list->rows[list->count++] = value;
}
}
//SpecificRequest()
void getString(const StringList *list, char *buffer, size_t size) {
buffer[0] = '\0';
for (size_t i = 0; i < list->count; i++) {
if (i != 0) {
strncat(buffer, "\n", size - strlen(buffer) - 1);
}
strncat(buffer, list->rows[i], size - strlen(buffer) - 1);
}
}
//Adapter: C has no inheritance, both bases are fields;
//the adaptee goes first, so the adapter is a StringList as well
typedef struct {
StringList base;
CText target;
} TextAdapter;
//Request(): the second base finds the whole adapter by its offset
void adapterGetText(const CText *self, char *buffer, size_t size) {
const char *start = (const char *)self - offsetof(TextAdapter, target);
const TextAdapter *adapter = (const TextAdapter *)start;
getString(&adapter->base, buffer, size);
}
//Client
TextAdapter adapter = {{}, {adapterGetText}};
//the adapter goes where a StringList is expected
add((StringList *)&adapter, "line 1");
add((StringList *)&adapter, "line 2");
//and where a CText is expected
CText *target = &adapter.target;
char text[32];
target->getText(target, text, sizeof text);
//text: line 1
// line 2
printf("text:\n%s\n", text);
Преобразует интерфейс класса в другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного паттерна из-за несовместимости интерфейсов.Read moreЗадача. Программа читает текст из любого источника через один метод. Готовый библиотечный класс хранит строки текста и умеет их склеивать, но метод у него называется иначе. Библиотеку изменить нельзя. Переписывать весь код, который вызывает ожидаемый метод, тоже не хочется. Идея. Адаптер подгоняет существующий класс под интерфейс, которого ждёт клиент. Такой класс называют адаптируемым, а нужный клиенту интерфейс - целевым. В этом варианте адаптер наследует адаптируемый класс и вдобавок реализует целевой интерфейс. Его целевой метод просто вызывает унаследованный, иногда с небольшим преобразованием. Поэтому адаптер сам по себе - адаптируемый класс со всеми его методами. Для сравнения: адаптер через композицию хранит адаптируемый объект, а не наследует его класс. Как читать пример. Адаптируемый класс уже делает нужную работу, но метод у него называется иначе. Целевой интерфейс объявляет метод, которого ждёт клиент. Адаптер наследует адаптируемый класс и добавляет целевой метод, который вызывает унаследованный. Объект здесь один: адаптер и есть адаптируемый объект. В большинстве языков клиент наполняет его собственными методами адаптируемого класса, а результат читает через целевой метод. Языки без наследования классов его имитируют: встраивают адаптируемый объект или добавляют целевой метод прямо в тип адаптируемого. Когда применять. Адаптер класса нужен, когда подгоняется один известный класс и заодно нужны его остальные методы. Он не годится, если подгоняемый объект уже существует или может оказаться подклассом адаптируемого. Адаптер класса - это всегда новый объект одного жёстко заданного класса, поэтому такие объекты он подогнать не может. Частая ошибка. Забыть, что адаптер открывает наружу все методы адаптируемого класса. Клиенты начинают вызывать их напрямую, и целевой интерфейс больше не скрывает адаптируемый. |