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

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

//Target
interface IText {
    //Request()
    fun getText(): String
}

//Adaptee
open class StringList {
    private val rows = mutableListOf<String>()

    //SpecificRequest()
    fun getString(): String {
        return rows.joinToString("\n")
    }

    fun add(value: String) {
        rows.add(value)
    }
}

//Adapter
class TextAdapter: IText {
    var rowList: StringList? = null

    //Request()
    override fun getText(): String {
        if (rowList == null) {
            return ""
        }
        return rowList!!.getString()
    }
}

private fun getTextAdapter(): TextAdapter {
    val adapter = TextAdapter()
    val rowList = StringList()
    rowList.add("line 1")
    rowList.add("line 2")
    adapter.rowList = rowList
    return adapter
}

//Client
val adapter = getTextAdapter()
val text = adapter.getText()
//text: line 1
//      line 2

println("text is $text")


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

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

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

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

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

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