0

我的情况

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

  1. 我没有足够的 DI 知识来指定我的要求,因此无法选择框架。
  2. 我想让代码没有更多“神奇”的东西,因为我觉得引入一个很少使用的框架会大大降低可读性。(要阅读的更多代码仅使用了一小部分)。

因此,我使用自定义工厂函数来创建对象并显式传递它们的依赖项:

    # 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必须自己创建多个Bars,它会通过其构造函数获取创建者函数:

# 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必须修改。对我来说似乎是不行的。因为我真的不认为这应该是一个解决方案,所以我没有在这里发布代码,而是在 pastebin上为非常感兴趣的读者提供了它;)

2) 使用带状态的函数

不是将Prefix对象放置在Foo这种方法中,而是尝试将其封装在create_foo函数中。Prefix通过为每个对象创建一个Foo并在无名函数中使用 引用它lambda,我将细节(又名存在前缀对象)远离Foo并保留在我的布线逻辑中。当然,命名函数也可以工作(但 lambda 更短)。

# 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)

这种方法对我来说似乎更有用。但是,我不确定常见的做法,因此担心不建议在函数内部使用状态。来自 java/C++ 背景,我希望一个函数依赖于它的参数、它的类成员(如果它是一个方法)或一些全局状态。因此,不使用全局状态的无参数函数每次调用时都必须返回完全相同的值。这不是这里的情况。一旦返回的对象被修改(这意味着counterinprefix已增加),该函数返回一个对象,该对象的状态与第一次返回时的状态不同。这个假设是否只是由于我在 python 方面的有限经验引起的,我是否必须改变我的心态,即不要考虑函数而是考虑可调用的东西?或者为函数提供状态是对 lambda 的无意滥用?

3) 使用可调用类

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

class BarCreator:
    def __init__(self, prefix):
        self.prefix = prefix
    def __call__(self):
        return create_bar(self.prefix)
     
def create_foo(name):
    return Foo(BarCreator(create_prefix(name)))

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

概括

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

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

4

1 回答 1

1

如果您想注入一个可调用对象但不希望它具有复杂的设置——如果像在你的示例中那样,它实际上只是绑定到单个输入值——你可以尝试使用functools.partial来提供一个函数<> 值对:

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()
于 2013-01-18T22:37:43.930 回答