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

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

#include <iostream>
#include <sstream>
#include <vector>
using namespace std;

//Target
class CText {
public:
    virtual ~CText() = default;
    //Request()
    virtual string getText() = 0;
};

//Adaptee
class StringList {
    vector<string> rows = {};

public:
    //SpecificRequest()
    string getString() {
        stringstream ss;
        for(size_t i = 0; i < rows.size(); ++i)
        {
            if (i != 0) ss << endl;
            ss << rows[i];
        }
        return ss.str();
    }

    void add(string value) {
        rows.push_back(value);
    }
};

//Adapter
class TextAdapter: public CText {
public:
    StringList rowList;

    //Request()
    string getText() {
        return rowList.getString();
    }
};

//Client
TextAdapter adapter;
adapter.rowList.add("line 1");
adapter.rowList.add("line 2");
string text = adapter.getText();
//text: line 1
//      line 2

cout << "text:" << endl << text;


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

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

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

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

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

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