Теперь я пишу на Питоне и с ООП знаком. И паттерны мне теперь намного понятней. Но меня по-прежнему воротит от развесистых схем классов. Многие паттерны прекрасно работают в функциональной парадигме. Опишу несколько примеров.
Классические реализации паттернов приводить не буду. Те, кто с ними не знаком, могут поинтересоваться в Википедии или в других источниках.
Наблюдатель
Нужно обеспечить возможность каким-то объектам подписываться на сообщения, а каким-то эти сообщения отсылать.
Реализуется словарём, который и представляет собой «почту». Ключами будут названия рассылок, а значениями списки подписчиков.
from collections import defaultdict mailing_list = defaultdict(list) def subscribe(mailbox, subscriber): # Подписывает функцию subscriber на рассылку с именем mailbox mailing_list[mailbox].append(subscriber) def notify(mailbox, *args, **kwargs): # Вызывает подписчиков рассылки mailbox, передавая им параметры for sub in mailing_list[mailbox]: sub(*args, **kwargs)
Теперь можно любые функции подписывать на рассылки. Главное, чтобы интерфейс функций входящих в одну и ту же группу рассылки, был совместим.
def fun(insert): print 'FUN %s' % insert def bar(insert): print 'BAR %s' % insert
Подписываем наши функции на рассылки:
>>> subscribe('insertors', fun) >>> subscribe('insertors', bar) >>> subscribe('bars', bar)
В любом месте кода вызываем уведомления для этих рассылок и наблюдаем, что все подписчики реагируют на событие:
>>> notify('insertors', insert=123) FUN 123 BAR 123 >>> notify('bars', 456) BAR 456
Шаблонный метод
Нужно обозначить каркас алгоритма и дать возможность пользователям переопределять определенные шаги в нём.
Функции высшего порядка, такие как map, filter, reduce по сути и являются такими шаблонами. Но давайте посмотрим, как можно провернуть такое же самому.
def approved_action(checker, action, obj): # Шаблон, который выполняет над объектом obj действие action, # если проверка checker дает положительный результат if checker(obj): action(obj) import os def remove_file(filename): approved_action(os.path.exists, os.remove, filename) import shutil def remove_dir(dirname): approved_action(os.path.exists, shutil.rmtree, dirname)
Имеем функции удаления файла и папки, проверяющие предварительно, есть ли нам чего удалять.
Если вызов «шаблона» напрямую кажется противоречащим паттерну, можно определять функции с помощью каррирования. Ну и ввести до кучи возможность «переопределения» не всех частей алгоритма.
def approved_action(obj, checker=lambda x: True, action=lambda x: None): if checker(obj): action(obj) from functools import partial remove_file = partial(approved_action, checker=os.path.exists, action=os.remove) remove_dir = partial(approved_action, checker=os.path.exists, action=shutil.rmtree) import sys printer = partial(approved_action, action=sys.stdout.write)
Состояние
Нужно обеспечить разное поведение объекта в зависимости от его состояния.
Давайте представим, что нам нужно описать процесс выполнения заявки, который может потребовать несколько циклов согласований.
from random import randint # Функции, выполняющие работу в каждом из состояний. # Аргументом ко всем является обрабатываемая заявка # Вызовы randint эмулируют логику, принимающую какие-то решения в зависимости от внешних обстоятельств def start(claim): print u'заявка подана' claim['state'] = 'analize' def analize(claim): print u'анализ заявки' if randint(0, 2) == 2: print u'заявка принята к исполнению' claim['state'] = 'processing' else: print u'требуется уточнение' claim['state'] = 'clarify' def processing(claim): print u'проведены работы по заявке' claim['state'] = 'close' def clarify(claim): if randint(0, 4) == 4: print u'пользователь отказался от заявки' claim['state'] = 'close' else: print u'уточнение дано' claim['state'] = 'analize' def close(claim): print u'заявка закрыта' claim['state'] = None # Определение конечного автомата. Какие функции в каком состоянии вызывать state = {'start': start, 'analize': analize, 'processing': processing, 'clarify': clarify, 'close': close} # Запуск заявки в работу def run_claim(): claim = {'state': 'start'} # Новая заявка while claim['state'] is not None: # Крутим машину, пока заявка не закроется fun = state[claim['state']] # определяем запускаемую функцию fun(claim)
Как видим, основную часть кода занимает «бизнес-логика», а не оверхед на применение паттерна. Автомат легко расширять и изменять, просто добавляя/заменяя функции в словаре state.
Запустим пару раз, чтобы убедиться в работоспособности:
>>> run_claim() заявка подана анализ заявки требуется уточнение уточнение дано анализ заявки заявка принята к исполнению проведены работы по заявке заявка закрыта >>> run_claim() заявка подана анализ заявки требуется уточнение пользователь отказался от заявки заявка закрыта
Команда
Задача – организовать «обратный вызов». То есть, чтобы вызываемый объект мог из своего кода обратиться к вызывающему.
Этот паттерн видимо возник из-за ограничений статичных языков. Функциональщики бы его даже звания паттерна не удостоили. Есть функция – пожалуйста, передавай её куда хочешь, сохраняй, вызывай.
def foo(arg1, arg2): # наша команда print 'FOO %s, %s' (arg1, arg2) def bar(cmd, arg2): # Приемник команды. Ничего не знает о функции foo... print 'BAR %s' % arg2 cmd(arg2 * 2) # ...но вызывает её
В исходных задачах паттерна Команда есть и возможность передавать некоторые параметры объекту-команде заранее. В зависимости от удобства, решается либо каррированием…
>>> from functools import partial >>> bar(partial(foo, 1), 2) BAR 2 FOO 1, 4
…либо заворачиванием в lambda
>>> bar(lambda x: foo(x, 5), 100) BAR 100 FOO 200, 5
Общий вывод
Не обязательно городить огород из абстрактных классов, конкретных классов, интерфейсов и т.д. Минимальные возможности обращения с функциями как с объектами первого класса, уже позволяют довольно лаконично применять те же шаблоны проектирования. Иногда даже не замечая этого 🙂
ссылка на оригинал статьи http://habrahabr.ru/post/184156/
Добавить комментарий