Адаптер (наследование)

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

-- Adaptee
local StringList = {}
StringList.__index = StringList

function StringList.new()
  return setmetatable({rows = {}}, StringList)
end

function StringList:add(value)
  self.rows[#self.rows + 1] = value
end

-- SpecificRequest()
function StringList:get_string()
  return table.concat(self.rows, "\n")
end

-- Adapter: a child of the adaptee with
-- the method the client expects
local TextAdapter = {}
TextAdapter.__index = TextAdapter
setmetatable(TextAdapter, {__index = StringList})

function TextAdapter.new()
  return setmetatable(StringList.new(),
                      TextAdapter)
end

-- Request()
function TextAdapter:get_text()
  return self:get_string()
end

-- Client
local adapter = TextAdapter.new()
-- add comes from StringList
adapter:add("line 1")
adapter:add("line 2")
local text = adapter:get_text()
-- text = line 1
-- line 2

print("text = " .. text)


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

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

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

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

Когда применять. Адаптер класса нужен, когда подгоняется один известный класс и заодно нужны его остальные методы. Он не годится, если подгоняемый объект уже существует или может оказаться подклассом адаптируемого. Адаптер класса - это всегда новый объект одного жёстко заданного класса, поэтому такие объекты он подогнать не может.

Частая ошибка. Забыть, что адаптер открывает наружу все методы адаптируемого класса. Клиенты начинают вызывать их напрямую, и целевой интерфейс больше не скрывает адаптируемый.