Адаптер (композиция)

Паттерны проектирования / Структурные паттерны
image

//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: the target is the first field, the adaptee is
//an object it points to
typedef struct {
    CText base;
    const StringList *rowList;
} TextAdapter;

//Request() passes the work to SpecificRequest()
void adapterGetText(const CText *self, char *buffer, size_t size) {
    const TextAdapter *adapter = (const TextAdapter *)self;
    getString(adapter->rowList, buffer, size);
}

//Client
StringList rowList = {};
add(&rowList, "line 1");
add(&rowList, "line 2");

//the adapter wraps a list that already exists
TextAdapter adapter = {{adapterGetText}, &rowList};
CText *target = &adapter.base;
char text[32];
target->getText(target, text, sizeof text);
//text: line 1
//      line 2

printf("text:\n%s\n", text);


Преобразует интерфейс класса в другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного паттерна из-за несовместимости интерфейсов.
Read more

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

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

Как читать пример. Объектов здесь два: адаптируемый и адаптер, который его хранит. В большинстве языков адаптируемый объект создаётся отдельно и передаётся адаптеру. В немногих адаптер сам создаёт свой адаптируемый объект, а клиент наполняет его через поле адаптера. Результат клиент читает только через целевой метод, и адаптер передаёт этот вызов адаптируемому. Некоторые адаптеры возвращают пустой результат, если адаптируемого объекта у них нет. В некоторых языках адаптер - просто функция, которая захватывает адаптируемый объект.

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

Частая ошибка. Вкладывать в адаптер новую логику. Адаптер только переводит один вызов в другой. Проверкам и бизнес-правилам место не здесь.