9.2. Adapter

9.2.1. Rationale

  • EN: Adapter

  • PL: Adapter

  • Type: class and object

9.2.2. Use Cases

  • 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

9.2.3. 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()

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass


class Image:
    pass


class Filter(metaclass=ABCMeta):
    @abstractmethod
    def apply(self, image: Image) -> None:
        pass


class VividFilter(Filter):
    def apply(self, image: Image) -> None:
        print('Applying Vivid Filter')


class BlackAndWhite3rdPartyFilter:
    def init(self):
        """Required by 3rd party library"""

    def render(self, image: Image):
        print('Applying BlackAndWhite Filter')


@dataclass
class ImageView:
    __image: Image

    def apply(self, filter: Filter):
        filter.apply(self.__image)


if __name__ == '__main__':
    image_view = ImageView(Image())
    image_view.apply(BlackAndWhite3rdPartyFilter())
    # Traceback (most recent call last):
    # AttributeError: 'BlackAndWhite3rdPartyFilter' object has no attribute 'apply'

9.2.4. Design

9.2.5. Implementation

  • Inheritance is simpler

  • Composition is more flexible

  • Favor Composition over Inheritance

../_images/designpatterns-adapter-usecase.png

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

Inheritance:

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass


class Image:
    pass


class Filter(metaclass=ABCMeta):
    @abstractmethod
    def apply(self, image: Image) -> None:
        pass


class VividFilter(Filter):
    def apply(self, image: Image) -> None:
        print('Applying Vivid Filter')


class BlackAndWhite3rdPartyFilter:
    def init(self):
        """Required by 3rd party library"""

    def render(self, image: Image):
        print('Applying BlackAndWhite Filter')


@dataclass
class BlackAndWhiteAdapter:
    __filter: BlackAndWhite3rdPartyFilter

    def apply(self, image: Image) -> None:
        self.__filter.init()
        self.__filter.render(image)


@dataclass
class ImageView:
    __image: Image

    def apply(self, filter: Filter):
        filter.apply(self.__image)


if __name__ == '__main__':
    image_view = ImageView(Image())
    image_view.apply(BlackAndWhiteAdapter(BlackAndWhite3rdPartyFilter()))

Composition:

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass


class Image:
    pass


class Filter(metaclass=ABCMeta):
    @abstractmethod
    def apply(self, image: Image) -> None:
        pass


class VividFilter(Filter):
    def apply(self, image: Image) -> None:
        print('Applying Vivid Filter')


class BlackAndWhite3rdPartyFilter:
    def init(self):
        """Required by 3rd party library"""

    def render(self, image: Image):
        print('Applying BlackAndWhite Filter')


@dataclass
class BlackAndWhiteAdapter(BlackAndWhite3rdPartyFilter, Filter):
    def apply(self, image: Image) -> None:
        self.init()
        self.render(image)


@dataclass
class ImageView:
    __image: Image

    def apply(self, filter: Filter):
        filter.apply(self.__image)


if __name__ == '__main__':
    image_view = ImageView(Image())
    image_view.apply(BlackAndWhiteAdapter())

9.2.6. Use Cases

>>> 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]

9.2.7. Assignments

Todo

Create assignments