Метка: ABC

Стратегия на Python

​​«Расскажите про любой шаблон проектирования на ваш выбор.»

Случалось слышать такое на собеседованиях? Большинство людей в этот момент начинают рассказывать про синглтон (одиночку). Потому что он… простой? Да, вообще-то не очень. Попробуйте сходу вспомнить, как там реализовать его через метакласс. Да и часто ли приходится? Скорее всего вы пользуетесь уже готовым кодом для синглтона. Его даже называют «анти-паттерном», потому что он часто маскирует плохой дизайн кода, вызывает проблемы при тестировании и нарушает принцип единственной отвественности класса (и порождает себя, и делает какую-то работу). А еще, он может вызывать проблемы с многопоточностью или «многопроцессностью» в случае с Python. Поэтому хвастать знанием синглотона – не лучшая стратегия на собеседовании…

Стратегия выбора работника

Ага! Стратегия! Это именно тот шаблон, который действительно подойдет для рассказа, потому что он простой и реально часто применяется на практике, даже если вы порой сами это не осознаете.

Стратегия – поведенческий шаблон, призванный для обеспечения взаимозаменяемости разных алгоритмов или вариаций алгоритма с одинаковыми интерфейсами. Стратегии – и есть эти варианты. В зависимости от условий (контекст) код выбирает подходящий алгоритм. 

Реализация этого шаблона может быть не только объектная, но и функциональная. С последней и начнем:

# стратегия печатать на экран
def console_writer(info):
    print(info)

# стратегия выводить в файл
def file_writer(info):
    with open('log.txt', 'a') as file:
        file.write(info + '\n')

def client(writer):
    writer('Hello world!')
    writer('Good bye!')

# пользователь выбирает стратегию
if input('Write to file? [Y/N]') == 'Y':
    client(writer=file_writer)
else:
    client(writer=console_writer)

Стратегия выбирается пользователем, а функция client даже не знает, какой вариант алгоритма ей дадут. Она знает лишь то, что writer(info) – это некая функция, принимающая строку (это и есть общий интерфейс для всех стратегий). Таким образом, мы делегируем работу стратегиям, скрывая детали реализации каждой из них.

В объектном варианте:

class Adder:
    def do_work(self, x, y):
        return x + y

class Multiplicator:
    def do_work(self, x, y):
        return x * y

class Calculator:
    def set_strategy(self, strategy):
        self.strategy = strategy

    def calculate(self, x, y):
        print('Result is', self.strategy.do_work(x, y))

calc = Calculator()
calc.set_strategy(Adder())
calc.calculate(10, 20)

calc.set_strategy(Multiplicator())
calc.calculate(10, 20)

Мы обеспечили горячую заменяя алгоритмов для класса Calculator. Для простоты, здесь я не применял наследование (спасибо динамической природе Python), но в серьезных проектах, вам следовало бы написать что-то подобное:

from abc import ABC, abstractmethod

class BaseStrategy(ABC):
    @abstractmethod
    def do_work(self, x, y):
        pass

class Adder(BaseStrategy):
    def do_work(self, x, y):
        return x + y

class Multiplicator(BaseStrategy):
    def do_work(self, x, y):
        return x * y

class Calculator:
    def set_strategy(self, strategy: BaseStrategy):
        self.strategy = strategy

    def calculate(self, x, y):
        print('Result is', self.strategy.do_work(x, y))

Здесь мы создаем общий интерфейс стратегий BaseStrategy – как абстрактный класс ABC. Далее в каждой стратегии реализуем этот интерфейс.

Надеюсь, было полезно. Если хотите еще больше подробностей, то читайте 1, 2, 3.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Абстрактный класс ABC

Просто лого для статьи

Абстрактный класс – класс, содержащий один и более абстрактных методов.

Абстрактный метод – метод, который объявлен, но не реализован.

Абстрактный класс не может быть инстанциирован (создан его экземпляр). Нужно наследовать этот класс и реализовать (переопределить) все абстрактные методы, и только после этого можно создавать экземпляры такого наследника.

В Python нет синтаксической поддержки абстрактных классов, но есть встроенный модуль abc (расшифровка – abstract base classes), который помогает проектировать абстрактные сущности.

Абстрактный класс наследуют от ABC (Python 3.4+) или указывают метакласс ABCMeta (для Python 3.0+):

from abc import ABC, ABCMeta
class Hero(ABC):
    ...

# или:
class Hero(metaclass=ABCMeta):
    ...

Любой из вариантов работает, первый современнее и короче. На данном этапе мы можем создавать объекты этих классов, потому что в них пока не абстрактных методов. Добавим:

from abc import ABC, abstractmethod
class Hero(ABC):
    @abstractmethod
    def attack(self):
        pass

Hero() – выдаст ошибку "TypeError: Can't instantiate abstract class Hero with abstract methods attack", которая говорит, что в классе Hero есть абстрактный метод attack. Мы вставили в него заглушку pass, но вообще там может быть какая-то реализация. Отнаследуем от героя Hero – конкретный подкласс лучника Archer:

class Archer(Hero):
    def attack(self):
        print('выстрел из лука')
Archer().attack()

Вот объект Archer мы можем уже создать и использовать реализацию метода attack

Кроме обычных методов, абстрактными можно обозначить и статические, классовые методы, а также свойства:

class C(ABC):
   @classmethod
   @abstractmethod
   def my_abstract_classmethod(cls):
       ...

   @staticmethod
   @abstractmethod
   def my_abstract_staticmethod():
       ...

   @property
   @abstractmethod
   def my_abstract_property(self):
       ...

   @my_abstract_property.setter
   @abstractmethod
   def my_abstract_property(self, val):
       ...

Абстрактные классы широко фигурируют в ООП, часто всплывают в шаблонах проектирования. Они говорят, что общий интерфейс уже обозначен, но этот класс еще не предназначен для использования, кроме как для наследования от него конкретных потомков.

Формально говоря, абстрактные классы для Python не являются чем-то необходимым в силу динамичности языка. Если мы выкинем все упоминания абстрактности классов и методов из рабочего кода, он продолжит работать, как и ранее. Абстрактные классы нужны на этапе проектирования или расширения кода, чтобы обеспечивать «правильные» взаимодействия новых классов, защищая от создания экземпляров абстрактных классов. Важно помнить, что эта защита срабатывает на этапе выполнения программы, а не компиляции, как в языках Java, C++ или C#!

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈