在python中使用手动构造函数注入传递依赖项的依赖项

时间:2013-01-18 14:49:52

标签: python dependency-injection inversion-of-control manual

我的情况

我目前正在写一个python项目,我想用它来学习更多关于软件架构的知识。我已经阅读了一些文本,并观看了几个关于依赖注入的讨论,并学会了如何清楚构造函数注入显示对象的依赖关系。 但是,我有点苦苦于如何将依赖关系传递给对象。我决定不使用DI框架:

  1. 我没有足够的DI知识来指定我的要求,因此无法选择框架。
  2. 我希望保持代码不受更多“神奇”的影响,因为我觉得引入一个很少使用的框架会大大降低可读性。 (更多代码可供阅读,其中只使用了一小部分)。
  3. 因此,我使用自定义工厂函数来创建对象并显式传递它们的依赖项:

        # Business and Data Objects
    class Foo:
        def __init__(self,bar):
            self.bar = bar
        def do_stuff(self):
            print(self.bar)
    
    class Bar:
        def __init__(self,prefix):
            self.prefix = prefix
        def __str__(self):
            return str(self.prefix)+"Hello"
    
    # Wiring up dependencies
    def create_bar():
        return Bar("Bar says: ")
    
    def create_foo():
        return Foo(create_bar())
    
    # Starting the application
    f = create_foo()
    f.do_stuff()
    

    或者,如果Foo必须自己创建一些Bar,它会通过其构造函数传递创建者函数:

    # Business and Data Objects
    class Foo:
        def __init__(self,create_bar):
            self.create_bar = create_bar
        def do_stuff(self,times):
            for _ in range(times):
                bar = self.create_bar()
                print(bar)
    
    class Bar:
        def __init__(self,greeting):
            self.greeting = greeting
        def __str__(self):
            return self.greeting
    
    # Wiring up dependencies
    def create_bar():
        return Bar("Hello World")
    
    def create_foo():
        return Foo(create_bar)
    
    # Starting the application
    f = create_foo()
    f.do_stuff(3)
    

    虽然我很想听听有关代码的改进建议,但这不是本文的重点。但是,我觉得这个介绍需要理解

    我的问题

    虽然上面看起来相当清晰,可读并且可以理解,但是当Bar的前缀依赖性要求在每个Foo对象的上下文中相同时,我遇到了问题,因此耦合到Foo对象的生命周期。作为示例,考虑实现计数器的前缀(有关实现细节,请参阅下面的代码示例)。 我有两个想法如何实现这一点,但是,对我来说,它们似乎都不是完美的:

    1)通过Foo传递前缀

    第一个想法是将构造函数参数添加到Foo并使其在每个Foo实例中存储前缀。

    明显的缺点是,它混淆了Foo的责任。它控制业务逻辑并为Bar提供一个依赖项。一旦Bar不再需要依赖关系,就必须修改Foo。对我来说似乎是禁忌。由于我不认为这应该是一个解决方案,我没有在这里发布代码,但为感兴趣的读者提供了on pastebin;)

    2)使用状态函数

    这种方法不是将Prefix对象置于Foo内,而是尝试将其封装在create_foo函数中。通过为每个Prefix对象创建一个Foo并使用lambda在无名函数中引用它,我将详细信息(也称为is-is-a-prefix-object)远离{{{ 1}}和我的布线逻辑内部。当然命名函数也可以工作(但是lambda更短)。

    Foo

    这种方法对我来说似乎更有用。但是,我不确定常见的做法,因此担心没有真正建议使用内部函数。来自java / C ++背景,我希望函数依赖于它的参数,它的类成员(如果它是一个方法)或一些全局状态。因此,不使用全局状态的无参数函数每次调用时都必须返回完全相同的值。这不是这种情况。一旦修改了返回的对象(这意味着# Business and Data Objects class Foo: def __init__(self,create_bar): self.create_bar = create_bar def do_stuff(self,times): for _ in range(times): bar = self.create_bar() print(bar) class Bar: def __init__(self,prefix): self.prefix = prefix def __str__(self): return str(self.prefix)+"Hello" class Prefix: def __init__(self,name): self.name = name self.count = 0 def __str__(self): self.count +=1 return self.name+" "+str(self.count)+": " # Wiring up dependencies def create_bar(prefix): return Bar(prefix) def create_prefix(name): return Prefix(name) def create_foo(name): prefix = create_prefix(name) return Foo(lambda : create_bar(prefix)) # Starting the application f1 = create_foo("foo1") f2 = create_foo("foo2") f1.do_stuff(3) f2.do_stuff(2) f1.do_stuff(2) 中的counter已经增加),该函数将返回一个对象,该对象的状态与第一次返回时的状态不同。 这个假设是由我在python中的经验限制造成的吗?我是否必须改变我的思维方式,即不考虑函数而是某些可调用的函数?或者正在向状态提供意外滥用lambda的函数?

    3)使用可调用类

    为了克服我对有状态函数的疑虑,我可以使用可调用类,其中方法2的prefix函数将被替换为:

    create_foo

    虽然这对我来说似乎是一个有用的解决方案,但它更加冗长。

    摘要

    我不完全确定如何处理这种情况。虽然我更喜欢2号,但我还是有疑虑。此外,我仍然希望任何人都能提出更优雅的方式。

    请评论,如果有任何您认为太模糊或可能被误解的内容。只要我的能力允许我这样做,我会改进这个问题:) 所有示例都应该在python2.7和python3下运行 - 如果遇到任何问题,请在评论中报告,我会尝试修复我的代码。

1 个答案:

答案 0 :(得分:1)

如果你想注入一个可调用的对象,但不希望它有一个复杂的设置 - 如果你的例子中,它实际上只是绑定到一个输入值 - 你可以尝试使用{{3}提供函数<>价值对:

def factory_function(arg):
    #processing here
    return configurted_object_base_on_arg

class Consumer(object):
   def __init__(self, injection):
      self._injected = injection

   def use_injected_value():
      print self._injected()

injectable = functools.partial(factory_function, 'this is the configuration argument')
example = Consumer(injectable)
example.use_injected_value() # should return the result of your factory function and argument

顺便说一句,如果您正在创建一个类似于选项3的依赖注入设置,那么您可能想要了解如何将配置配置到工厂类中,而不是像在此处那样进行内联。这样,如果您想在策略之间进行选择,您可以换掉工厂。它在功能上并没有太大的不同(除非创建比这个例子更复杂并且涉及持久状态)但是如果代码看起来像

那么它会更灵活。
factory = FooBarFactory()
bar1 = factory.create_bar()
alt_factory = FooBlahFactory(extra_info)
bar2 = alt_factory.create_bar()