- PVSM.RU - https://www.pvsm.ru -
Зачем нужно внедрение зависимостей? Оно уменьшает связанность компонентов в приложение и упрощает тестирование. У некоторых разработчиков есть мнение, что внедрение зависимостей нужно только в больших проектах и что оно сильно усложняет программы. Думаю, это исторически сложилось из-за популярный фрейморков вроде Спринга или Джуса в Джаве. Особенно из-за Спринга, который является невероятным комбайном.
Python-inject [1] — это небольшая библиотека для внедрения зависимостей в Питоне. Третья версия написана в 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()
Конфигурация инжектора описывается с помощью байндингов. Байндинги отвечают за инициализацию
зависимостей. Существует четыре типа:
redis = RedisCache(address='localhost:1234')
def config(binder):
binder.bind(Cache, redis)
def config(binder):
# Creates a redis cache singleton on first injection.
binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))
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)
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)
За много лет использования Спринга и Джуса в Джава-приложениях я так и не полюбил их области видимости (саму концепцию). Инжект по умолчанию создает все объекты как синглтоны. Ему не требуется prototype scope/NO_SCOPE, потому что он позволяет использовать нормальные конструкторы классов, в отличие от Спринга и Джуса, в которых все объекты должны быть инициализированны в рамках контекста/инжектора.
Другие области видимости, например, request scope или session scope, по-моему, хрупкие, увеличиваю связанность компонентов в приложении и сложны в тестировании. В инжекте, если нужно ограничить область видимости объекта, всегда можно написать собственный провайдер.
Это уже третья версия инжекта. Первые две частично были похожи на Спринг и Джус, и они также пытались быть комбайнами. Последняя версия крошечная по сравнению с ними, зато она простая, гибкая и ее удобно использовать. Код проекта можно найти на Гитхабе [1].
Автор: drJonnie
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/54743
Ссылки в тексте:
[1] Python-inject: https://github.com/ivan-korobkov/python-inject
[2] Источник: http://habrahabr.ru/post/212217/
Нажмите здесь для печати.