Паттерн Адаптер — Python

Пример №1

Бытовой пример реализации паттерна адаптер. Предположим у нас есть:

  1. Розетка американского типа (usa), которая имеет два плоских параллельных между собой контакта. (рис. 1)
  2. Чайник с вилкой подходящей к американской розетке (рис.2)
  3. Утюг который имеет вилку европейского образца. (рис. 3)

Очевидно, что мы не сможем воткнуть евроровилку (рис. 3) в американскую розетку (рис. 1), для этого нужен переходник.

# Американская вилка
class UsaFork:
    def power_usa(self):
        print('power on. Usa')


# Европейская вилка
class EuroFork:
    def power_euro(self):
        print('power on. Euro')


# Американская розетка
class UsaSocket:
    def __init__(self, fork):
        self.fork = fork

    def connect(self):
        self.fork.power_usa()

# Вставляем американскую вилку в американскую розетку.
uf = UsaFork()
us = UsaSocket(uf)
us.connect()
# >>> power on. Usa


# При попытке вставить европейскую вилку в американскую розетку, будет ошибка
ef = EuroFork() 
us = UsaSocket(ef) 
us.connect() 
# >>> AttributeError: 'EuroFork' object has no attribute 'power_usa'

Обратите внимание, что метода розетки «connect» внутри вызывает метод «power_usa()» — который есть только у американской розетки.

Создадим адаптер переходник, с помощью него мы подключимся к американской розетке

class AdapterEuroInUsa:
    def __init__(self):
        self._euro_fork = EuroFork()

    def power_usa(self):
        self._euro_fork.power_euro()

В адаптере мы создаем метод power_usa() — такой-же как у американской розетки(UsaFork), но реализация этого метода будет от класса европейской вилки. (EuroFork)

Получаем

# Вставляем американскую вилку в американскую розетку. 
uf = UsaFork() 
us = UsaSocket(uf) 
us.connect() 
# >>> power on. Usa

# Вставляем евро-адаптер в американскую розетку. 
ad = AdapterEuroInUsa()
us = UsaSocket(ad)
us.connect() 
# >>> power on. Euro

Получается, что благодаря адаптеру мы можем использовать интерфейс класса `UsaFork`, а реализацию класса `EuroFork`.

Пример №2

Допустим у нас есть система которая работает с текстом, что-то там парсит, чистит, формирует.
Так-же у нас есть вторая система которая способна быстро и очень эффективно подсчитывать частотности слов в любом тексте.
Важно понимать, что это 2 не зависимые между собой системы.
Представим их в виде классов:

# Система №1
class System: # Класс, представляющий систему
    def __init__(self, text):
        tmp = re.sub(r'\W', ' ', text.lower())
        tmp = re.sub(r' +', ' ', tmp).strip()
        self.text = tmp

    def get_processed_text(self, processor): # Метод, требующий на вход класс-обработчик
        result = processor.process_text(self.text) # Вызов метода обработчика
        print(*result, sep='\n')
# Система №2
class WordCounter: # Обработчик, несовместимый с основной системой
    def count_words(self, text):
        self.__words = dict()
        for word in text.split():
            self.__words[word] = self.__words.get(word, 0) + 1

    def get_count(self, word):
        return self.__words.get(word, 0)

    def get_all_words(self):
        return self.__words.copy()

Итак, мы хотим что бы система №1 могла выводить текст в порядке частотности слов, используя при этом мега эффективный метод подсчета из системы №2. Для этого создадим специальный адаптер:

class WordCounterAdapter(TextProcessor): # Адаптер к обработчику
    def __init__(self, adaptee): # В конструкторе указывается, к какому объекту следует подключить адаптер
        self.adaptee = adaptee

    def process_text(self, text): # Реализация интерфейса обработчика, требуемого системой.
        self.adaptee.count_words(text)
        words = self.adaptee.get_all_words().keys()
        return sorted(words, key=lambda x: self.adaptee.get_count(x), reverse=True)

Итак, как все это будет работать.
Адаптер при инициализации принимает в качестве аргумента экземпляр системы №2 (WordCounter).
Далее в методе process_text() с помощью супер эффективных методов экземпляра класса системы №2 (WordCounter)
вычисляется частотность всех слов в тексте, а после результат сортируется.

Теперь нам необходимо передать этот адаптер системе №1 (class System).
Для этого у системы №1 есть метод, который в качестве параметра принимает экземпляр класса адаптер, в нашем примере это переменная «processor»:

def get_processed_text(self, processor): # Метод, требующий на вход класс-обработчик
    result = processor.process_text(self.text) # Вызов метода обработчика
    print(*result, sep='\n')

В итоге связка работает так:

text = 'la bla la la text test'

system_one = System(text)
system_two = WordCounter()
adapter_one_two = WordCounterAdapter(system_two)

system_one.get_processed_text(adapter_one_two)