2016-08-07 4 views
1

В программировании для развлечения я заметил, что управление зависимостями кажется скучной работой, которую я хочу свести к минимуму. After reading this, я придумал с инжектором супер тривиальной зависимости, в результате чего экземпляры зависимостей ищутся с помощью ключа строки:Инъекция зависимостей Python для ленивых callables

def run_job(job, args, instance_keys, injected): 
    args.extend([injected[key] for key in instance_keys]) 
    return job(*args) 

Этот дешевый трюк работает с вызовами в моей программе всегда лениво определенной (где функция ручка хранятся отдельно от своих аргументов) в итераторе, например:

jobs_to_run = [[some_func, ("arg1", "arg2"), ("obj_key",)], [other_func,(),()]] 

причина в том, что из центрального main loop, который должен планировать все события. Он имеет ссылку на все зависимости, поэтому для инъекции "obj_key" может быть передан в объекте ДИКТ, например:

# inside main loop 
injection = {"obj_key" : injected_instance} 
for (callable, with_args, and_dependencies) in jobs_to_run: 
    run_job(callable, with_args, and_dependencies, injection) 

Так, когда произошло событие (ввод данных пользователя, и т.д.), основной цикл может вызвать update() на конкретный объект, который реагирует на этот вход, который, в свою очередь, строит список заданий для main loop, чтобы запланировать, когда есть ресурсы. Для меня он чист к key-reference любые зависимости для кто-то еще для инъекций, а не для того, чтобы все объекты формировали прямые отношения.

Поскольку я лениво определяю все вызовы (функции) для game clock engine to run them on its own accord, приведенный выше наивный подход работал с очень небольшой добавленной сложностью. Тем не менее, есть воровство кода в том, что нужно ссылаться на объекты по строкам. В то же время, было вонючим, чтобы проходить зависимости вокруг, и constructor or setter injection было бы излишним, как возможно, самым большим dependency injection libraries.

Для особого случая инъекционные зависимостей в вызываемых объектов, которые лениво определяется, есть ли более выразительные образцы дизайна в существовании?

+1

Возможно, вы могли бы усовершенствовать свой текущий подход, чтобы использовать конструкцию 'kwargs', чтобы иметь именованные функциональные параметры, а затем рассматривать это как инъекционный' dict'. Таким образом, ваши исходные строки более явны в качестве параметров функции. – user268396

ответ

1

Я заметил, что управление зависимостями кажется скучной работой, которую я хочу свести к минимуму.

Прежде всего, вы не должны предполагать, что инъекция зависимостей - это средство минимизации работы управления зависимостями. Он не уходит, он просто откладывается на другое место и время и, возможно, делегируется кому-то другому.

Сказанное: если то, что вы строите, будет использоваться другими, было бы разумно включить в ваши «инъекции» определенную форму проверки версий, так что ваши пользователи будут иметь простой способ проверить, соответствует ожидаемому.

Есть ли более выразительные шаблоны проектирования в существовании?

Вашего метод, как я понимаю, это по существу Strategy-Pattern, то есть код на Иове (вызываемой) опирается на вызов методов на одном из нескольких конкретных объектов. То, как вы это делаете, вполне разумно - оно работает и эффективно.

Возможно, вы захотите его формализовать, чтобы упростить чтение и обслуживание, например.

from collections import namedtuple 

Job = namedtuple('Job', ['callable', 'args', 'strategies']) 

def run_job(job, using=None): 
    strategies = { k: using[k] for k in job.strategies] } 
    return job.callable(*args, **strategies) 

jobs_to_run = [ 
    Job(callable=some_func, args=(1,2), strategies=('A', 'B')), 
    Job(callable=other_func, ...), 
] 

strategies = {"A": injected_strategy, ...} 
for job in jobs_to_run: 
    run_job(job, using=strategies) 

# actual job 
def some_func(arg1, arg2, A=None, B=None): 
    ... 

Как вы можете увидеть код по-прежнему делает то же самое, но это мгновенно более читаемым, и он концентрирует знания о структуре задания() объектов в run_job. Кроме того, вызов функции задания, такой как some_func, будет терпеть неудачу, если будет передано неправильное количество аргументов, а функции задания легче закодировать и отлаживать из-за их явно перечисленных и именованных аргументов.

0

Если вам не нужно вводить всегда один и тот же объект, то есть вы можете создать новый экземпляр зависимостей каждый раз, когда вызывается функция ввода, тогда вы можете найти полезный @autowired decorator, он поддерживает ленивую инициализацию зависимостей с помощью без усилий.

Это так же просто, как украшение вашей функции.

Вы включаете подобный код:

class Example: 
    def __init__(self, *, lazy_service: Service = None): 
     self._service = lazy_service 

    @property 
    def service(self) -> Service: 
     if self._service is None: 
      self._service = Service() 

     return self._service 

    # actual code 

В это:

class Example: 
    @autowired(lazy=True) 
    def __init__(self, *, lazy_service: Service): 
     self.service = service 

    # actual code 

Я поддерживаю проект. Я создал декоратор, чтобы автоматизировать много ненужного и повторяющегося кода, который используется для инициализации экземпляров зависимостей, когда вызывающий объект не предоставляет сами зависимости.