Adapter (inheritance)

Design patterns / Structural patterns
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)


Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
Read more

The problem. Your program reads text from any source through one method. A ready library class keeps lines of text and can join them, but through a method with another name. You cannot change the library. You also do not want to change all the code that calls the method it expects.

The idea. An adapter makes an existing class, the adaptee, fit the interface that the client expects, the target. In this version the adapter is a subclass of the adaptee and also implements the target. Its target method only calls the inherited method, sometimes with a small conversion. So the adapter is itself an adaptee and keeps all its methods. Compare the adapter by composition: it holds an adaptee instead of extending it.

How to read the example. The adaptee already does the work, but under another method. The target declares the method that the client expects. The adapter extends the adaptee and adds the target method, which calls the inherited one. There is only one object: the adapter is the adaptee. In most languages the client fills it through the adaptee's own methods and reads the result through the target method. Languages without class inheritance imitate it: they embed the adaptee, or they add the target method to the adaptee's own type.

When to use it. Use a class adapter when you adapt one known class and also need its other methods. Do not use it when the object to adapt already exists, or when it may belong to a subclass of the adaptee. A class adapter is always a new object of one fixed class, so it cannot adapt such objects.

A common mistake. Forgetting that the adapter shows every method of the adaptee. Clients start to call these methods directly, and the target no longer hides the adaptee.