Introduction
The Adapter pattern is used to make one interface compatible with another. It allows objects with different, and therefore incompatible interface to work together.
If you for example want to bring an existing class into your system with an interface that doesn’t match any existing interface, and you can not modify this class, you write an adapter class to bridge the gap.
It looks like this:
The pattern consists of different parts:
- The client has an object which implements the Subject interface.
- The object in question is the Adapter.
- The implementation of the operation() method in the Adapter wraps the concrete_operation in the RealSubject class.
That way we can talk to the RealSubject class using an already known interface. The Adapter class takes care of converting our method-calls for the RealSubject. Also, because this pattern is loosely coupled, changing the RealSubject should be relatively painless.
What is the difference between the Decorator and the Adapter pattern?
Decorator | Adapter | |
Purpose | The decorator pattern is used to add behaviour to objects dynamically without modifying the source. | Make one interface compatible with another |
Use | This pattern is used to when you want to add or extend behaviour to existing classes in a flexible and reusable way without creating a hierarchy of subclasses. | Used to integrate an existing class with an interface which is not compatible with existing interfaces. Modifying this class is not needed if you wrap it in an Adapter. |
Structure | Creating a decorator pattern involves creating a set of decorator classes that implement the same interface as the component, thereby extending or modifying its behaviour | Create an Adapter class, implementing the desired interface. This class also holds an instance of the class you want to adapted, the adaptee. The adapter delegates calls to the adaptee converting or transforming the input or output of these calls. |
Example | Imagine having a bakery, you add things like GlazingDecorator or a WhippedCreamDecorator wrapping around your Pie-class to produces pies of the desired kind | A classic example would be the conversion of units of measurement. If you have a classes which expects lengths to be in feet and inches, yet your client needs meters, you can wrap the classes in ConverterAdapter and have the conversion done automatically |
Difference between Decorator and Adapter
In short, Adapter is used to make incompatible things work together, while the Decorator adds and extends functionality.
Implementation in Python
A short explanation what we will build:
- We have an existing class which doubles numbers, which takes an int16 as a parameter
- However our client produces numbers as strings.
This is a very trivial example on purpose, but it touches on the important points of this pattern.
We will start with the Subject class, which has the interface the client sees:
class Subject:
def request(self, number: str) -> str:
pass
This interface is self-explanatory
Next we define the ConcreteSubject class. This is an empty class because in this example, the ConcreteSubject does not hold any state. In it we have the specific code to double the number:
class ConcreteSubject:
def specific_request(self, number: int) -> int:
return number * 2
The Adapter class looks like this:
class Adapter(Subject):
_concrete_subject: ConcreteSubject = None
def __init__(self, new_subject: ConcreteSubject):
self._concrete_subject = new_subject
def request(self, number: str) -> str:
n: int = int(number)
result: int = self._concrete_subject.specific_request(n)
return str(result)
A few notes:
- In this class we have a _concrete_subject attribute of type ConcreteSubject which is the wrapped object.
- As you can see the Adapter class derives from the Subject class, and so it implements the request() method.
Time to test
Before we can put our code to the test, we need a small extra function:
if __name__ == "__main__":
subject = ConcreteSubject()
adapter = Adapter(subject)
client_code(adapter)
Line by line:
- We construct an empty ConcreteSubject struct
- We pass that to the constructor of the adapter.
- In clientCode() we call the Request() method on the subject. The Adapter will handle this, since it derives from the Subject class
Conclusion
This pattern can be quite confusing at first, at least that is what I found. The main points of this pattern are:
- You wrap the class with the incompatible or new interface in the Adapter class.
- Delegate method-calls where necessary to the ‘adaptee’ class, and make sure you convert both the input and output parameters where needed.
Once I realized this, implementing this pattern was quite easy. I realize that implementing this pattern in existing systems and legacy code might be a bit more work.