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

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

//Target
trait Text {
    //Request()
    fn get_text(&self) -> String;
}

//Adaptee
struct StringList<'a> {
    rows: Vec<&'a str> 
}

impl<'a> StringList<'a> {
    //SpecificRequest()
    fn get_string(&self) -> String {
        self.rows.join("\n")
    }

    fn add(&mut self, value: &'a str) {
        self.rows.push(value);
    }
}

//Adapter
struct TextAdapter<'a> {
    row_list: StringList<'a>
}

impl Text for TextAdapter<'_> {
    //Request()
    fn get_text(&self) -> String {
        self.row_list.get_string()
    }
}

fn get_text_adapter<'a>() -> TextAdapter<'a> {
    let mut row_list = StringList { rows: vec![] };
    row_list.add("line 1");
    row_list.add("line 2");
    let adapter = TextAdapter { 
        row_list: row_list };
    adapter
}

//Client
let adapter = get_text_adapter();
let text = adapter.get_text();
//text: line 1
//      line 2 
println!("text:\n{text}");


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

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

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

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

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

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