7.2. Adapter

  • EN: Adapter

  • PL: Adapter

  • Type: class and object

The Adapter design pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It wraps an existing class with a new interface so that it becomes compatible with the client's interface.

Here's a simple example of the Adapter pattern in Python:

>>> class Target:
...     def request(self):
...         return "Target: The default target's behavior."
...
>>> class Adaptee:
...     def specific_request(self):
...         return ".eetpadA eht fo roivaheb laicepS"
...
>>> class Adapter(Target, Adaptee):
...     def request(self):
...         return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}"
...
>>> def client_code(target: "Target") -> None:
...     print(target.request(), end="")
...
>>> adaptee = Adaptee()
>>> print("Adaptee: " + adaptee.specific_request())
Adaptee: .eetpadA eht fo roivaheb laicepS
>>> adapter = Adapter()
>>> client_code(adapter)
Adapter: (TRANSLATED) Special behavior of the Adaptee.

In this example, Target is the interface that the client uses, Adaptee is the class with an incompatible interface that needs adapting, and Adapter is the class that adapts the Adaptee to the Target interface. The Adapter class inherits from both Target and Adaptee and overrides the request method of Target to call the specific_request method of Adaptee. The client_code function works with objects of the Target class. It remains unaware of the Adapter and Adaptee classes as long as the Adapter follows the Target interface.

7.2.1. Pattern

  • Convert an interface of an object to a different form

  • Like power socket adapter for US and EU

  • Refactoring of a large application

  • Working with legacy code / database

  • Niekompatybilne API dwóch systemów

  • Wymagające różnych sposobów uwierzytelniania (OAuth2, BasicAuth)

  • Tłumaczenie pomiędzy różnymi formatami danych (SOAP/XML, REST/JSON)

  • Iteracyjne przepisywanie legacy systemu na nowy, ale tak, aby móc wciąż korzystać ze starego

design-patterns/structural/img/designpatterns-adapter-pattern.png

7.2.2. Problem

  • BlackAndWhite3rdPartyFilter is from external library

  • Does not conform to Filter interface

  • Do not have apply() method

  • Need manual call of init() at initialization

  • Need manual call of render()

design-patterns/structural/img/designpatterns-adapter-problem.png

from abc import ABC, abstractmethod
from pathlib import Path


#%% 3rd Party
class AmazingFilter:
    def __init__(self):
        print('Setting up filter')

    def transform(self, content: bytes) -> bytes:
        print('Making your phot amazing')
        return content


#%% Abstract
class Filter(ABC):
    @abstractmethod
    def apply(self, content: bytes) -> bytes:
        ...


#%% Implementation
class SepiaFilter(Filter):
    def apply(self, content: bytes) -> bytes:
        print('Making photo in sepia')
        return content

class SharpenFilter(Filter):
    def apply(self, content: bytes) -> bytes:
        print('Making photo sharp')
        return content


#%% Main
class Image:
    path: Path
    content: bytes

    def __init__(self, path):
        self.path = Path(path)
        self.content = self.path.read_bytes()

    def apply(self, filter: Filter) -> None:
        self.content = filter.apply(self.content)


if __name__ == '__main__':
    img = Image('/tmp/myfile.png')
    img.apply(SepiaFilter())
    img.apply(SharpenFilter())

    img.apply(AmazingFilter())
    # AttributeError: 'AmazingFilter' object has no attribute 'apply'

7.2.3. Solution

  • Inheritance is simpler

  • Composition is more flexible

  • Favor Composition over Inheritance

../../_images/designpatterns-adapter-solution.png

Figure 7.1. Please mind, that on Picture there is a Caramel filter but in code BlackAndWhite3rdPartyFilter


from abc import ABC, abstractmethod
from pathlib import Path


#%% 3rd Party
class AmazingFilter:
    def __init__(self):
        print('Setting up filter')

    def transform(self, content: bytes) -> bytes:
        print('Making your phot amazing')
        return content


#%% Abstract
class Filter(ABC):
    @abstractmethod
    def apply(self, content: bytes) -> bytes:
        ...


#%% Implementation
class SepiaFilter(Filter):
    def apply(self, content: bytes) -> bytes:
        print('Making photo in sepia')
        return content

class SharpenFilter(Filter):
    def apply(self, content: bytes) -> bytes:
        print('Making photo sharp')
        return content


# %% Adapter
class AmazingFilterAdapter(Filter):
    filter: AmazingFilter

    def __init__(self):
        self.filter = AmazingFilter()

    def apply(self, content: bytes) -> bytes:
        self.filter.__init__()
        return self.filter.transform(content)


#%% Main
class Image:
    path: Path
    content: bytes

    def __init__(self, path):
        self.path = Path(path)
        self.content = self.path.read_bytes()

    def apply(self, filter: Filter) -> None:
        self.content = filter.apply(self.content)


if __name__ == '__main__':
    img = Image('/tmp/myfile.png')
    img.apply(SepiaFilter())
    img.apply(SharpenFilter())
    img.apply(AmazingFilterAdapter())

7.2.4. Use Case - 0x01

>>> def otherrange(a, b, c):  # function with bad API
...     current = a
...     result = []
...     while current < b:
...         result.append(current)
...         current += c
...     return result
>>>
>>>
>>> def myrange(start, stop, step):  # adapter
...     return otherrange(a=start, b=stop, c=step)
>>>
>>>
>>> myrange(start=10, stop=20, step=2)
[10, 12, 14, 16, 18]

7.2.5. Assignments