Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Паттерны проектирования без ООП

Anna | 16.06.2014 | нет комментариев
Во времена, когда я писал на Лиспе и вовсе не был знаком с ООП, я пытался обнаружить паттерны проектирования, которые мог бы применить у себя в коде. И всё время натыкался на какие-то ужасные схемы классов. В результате сделал итог, что эти паттерны в функциональном программировании не применимы.

Сейчас я пишу на Питоне и с ООП знаком. И паттерны мне сейчас гораздо внятней. Но меня по-бывшему воротит от развесистых схем классов. Многие паттерны восхитительно работают в функциональной парадигме. Опишу несколько примеров.
Типичные реализации паттернов приводить не буду. Те, кто с ними не знаком, могут поинтересоваться в Википедии либо в других источниках.

Наблюдатель

Необходимо обеспечить вероятность каким-то объектам подписываться на сообщения, а каким-то эти сообщения отсылать.
Реализуется словарём, тот, что и представляет собой «почту». Ключами будут наименования рассылок, а значениями списки подписчиков.

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
Всеобщий итог

Не непременно городить огород из абстрактных классов, определенных классов, интерфейсов и т.д. Минимальные вероятности обращения с функциями как с объектами первого класса, теснее разрешают достаточно немногословно использовать те же образцы проектирования. Изредка даже не примечая этого :)

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB