Python patterns for fun and profit: The Adapter Pattern

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?

DecoratorAdapter
PurposeThe decorator pattern is used to add behaviour to objects dynamically without modifying the source.Make one interface compatible with another
UseThis 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.
StructureCreating a decorator pattern involves creating a set of decorator classes that implement the same interface as the component, thereby extending or modifying its behaviourCreate 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.
ExampleImagine having a bakery, you add things like GlazingDecorator or a WhippedCreamDecorator wrapping around your Pie-class to produces pies of the desired kindA 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:

  1. In this class we have a _concrete_subject attribute of type ConcreteSubject which is the wrapped object.
  2. 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:

  1. We construct an empty ConcreteSubject struct
  2. We pass that to the constructor of the adapter.
  3. 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:

  1. You wrap the class with the incompatible or new interface in the Adapter class.
  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *