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

Внедрение зависимостей the Python way

Anna | 15.06.2014 | нет комментариев
Зачем необходимо внедрение зависимостей? Оно сокращает связанность компонентов в приложение и упрощает тестирование. У некоторых разработчиков есть суждение, что внедрение зависимостей необходимо только в крупных планах и что оно крепко усложняет программы. Думаю, это исторически сложилось из-за знаменитый фрейморков как бы Спринга либо Джуса в Джаве. Исключительно из-за Спринга, тот, что является невообразимым комбайном.

Python-inject — это маленькая библиотека для внедрения зависимостей в Питоне. Третья версия написана в unix-жанре, т.е. она восхитительно исполняет только одну фукнцию и не пытается быть каждому. В различие от теснее упомянутых Спринга и Джуса Инжект не крадет конструкторы классов у разработчиков, не навязывает разработчикам надобность писать приложение в каком-то определенном жанре и не пытается руководить каждому графом объектов приложения.

Инжект фактически не требует конфигурации (об этом подробнее подкатом) и дюже примитивен в применении.

Скажем в тестах

# Допустимые зависимости
class Db(object): pass
class Mailer(object): pass

# Внедряем зависимости в класс пользователя
class User(object):
    db = inject.attr(Db)
    mailer = inject.attr(Mailer)

    def __init__(self, name):
        self.name = name

    def register(self):
        self.db.save(self)
        self.mailer.send_welcome_email(self.name)

 # Используем в тестах inmemory базу данных и моки.
class TestUser(unittest.TestCase):
    def setUp(self):
        inject.clear_and_configure(lambda binder: binder \
            .bind(Db, InMemoryDb()) \
            .bind(Mailer, Mock()))

        self.mailer = inject.instance(Mailer)

    def test_register__should_send_welcome_email(self):
        # Пример теста.
        user = User('John Doe')

        # Регистрируем нового пользователя.
        user.register()

        # Должно отправиться письмо с приветствием.
        self.mailer.send_welcome_email.assert_called_with('John Doe')

Применение

Отменнее каждого поставить с PyPI, правда дозволено и скачать архив с сайта плана:

[sudo] pip install inject

В приложении:

# Импортируем исключительный модуль.
import inject

# Описываем опциональную конфигурацию
def my_config(binder):
    binder.install(my_config2)  # Импортируем иную конфигурацию
    binder.bind(Db, RedisDb('localhost:1234'))
    binder.bind_to_provider(CurrentUser, get_current_user)

# Создаем инжектор.
inject.configure(my_config)

# Внедряем зависимости с поддержкой inject.instance и inject.attr
class User(object):
    db = inject.attr(Db)

    @classmethod
    def load(cls, id):
        return cls.db.load('user', id)

    def __init__(self, id):
        self.id = id

    def save(self):
        self.db.save('user', self)

def foo(bar):
    cache = inject.instance(Cache)
    cache.save('bar', bar)

# Создаем нового пользователя и сберегаем 
# во внедренную базу данных.
user = User(10)
user.save()

Типы байндингов

Конфигурация инжектора описывается с поддержкой байндингов. Байндинги отвечают за инициализацию
зависимостей. Существует четыре типа:

  1. Instance bindings, которые неизменно возвращают один и тот же объект:
    redis = RedisCache(address='localhost:1234')
    def config(binder):
        binder.bind(Cache, redis)
    
  2. Constructor bindings, которые создают синглтон при первом обращении:
    def config(binder):
        # Creates a redis cache singleton on first injection.
        binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))
    
  3. Provider bindings, которые вызываются при всяком внедрении зависимостей:
    def get_my_thread_local_cache():
        # Custom code here
        pass
    
    def config(binder):
        # Executes the provider on each injection.
        binder.bind_to_provider(Cache, get_my_thread_local_cache)
    
  4. Runtime bindings, которые механически создают синглтоны классов, если в конфигурации для этих классов нет очевидных байндингов. Runtime bindings крепко уменьшают размер конфигурации. Скажем, в коде ниже только класс Config имеет очевидную конфигурацию:
    class Config(object): pass
    
    class Cache(object):
        config = inject.attr(Config)
    
    class Db(object):
        config = inject.attr(Config)
    
    class User(object):
        cache = inject.attr(Cache)
        db = inject.attr(Db)
    
        @classmethod
        def load(cls, user_id):
            return cls.cache.load('users', user_id) or cls.db.load('users', user_id)
    
    inject.configure(lambda binder: binder.bind(Config, load_config_file()))
    user = User.load(10)
    

Отчего нет областей видимости (scopes?)

За много лет применения Спринга и Джуса в Джава-приложениях я так и не полюбил их области видимости (саму доктрину). Инжект по умолчанию создает все объекты как синглтоны. Ему не требуется prototype scope/NO_SCOPE, потому что он разрешает применять типичные конструкторы классов, в различие от Спринга и Джуса, в которых все объекты обязаны быть инициализированны в рамках контекста/инжектора.

Другие области видимости, скажем, request scope либо session scope, по-моему, хрупкие, увеличиваю связанность компонентов в приложении и трудны в тестировании. В инжекте, если необходимо ограничить область видимости объекта, неизменно дозволено написать личный провайдер.

Завершение

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

Источник: programmingmaster.ru

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