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