【发布时间】:2013-01-18 14:49:51
【问题描述】:
我的情况
我目前正在编写一个 Python 项目,我想用它来学习更多关于软件架构的知识。我已经阅读了一些文本并观看了一些关于依赖注入的讨论,并学会了喜欢构造函数注入如何清晰地显示对象的依赖关系。 但是,我有点挣扎如何将依赖项传递给对象。我决定不使用 DI 框架,因为:
- 我没有足够的 DI 知识来指定我的要求,因此无法选择框架。
- 我想让代码不包含更多“神奇”的东西,因为我觉得引入一个很少使用的框架会大大降低可读性。 (更多代码需要阅读,其中只使用了一小部分)。
因此,我使用自定义工厂函数来创建对象并显式传递它们的依赖项:
# 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 对象的上下文中必须相同并因此与@ 耦合时,我遇到了一个问题987654328@ 对象生命周期。作为一个例子,考虑一个实现计数器的前缀(有关实现细节,请参见下面的代码示例)。
我有两个想法如何实现这一点,但是,它们对我来说似乎都不完美:
1) 通过 Foo 传递前缀
第一个想法是给Foo添加一个构造函数参数,让它在每个Foo实例中存储前缀。
明显的缺点是,它混淆了Foo 的职责。它控制业务逻辑并为Bar 提供依赖项之一。一旦Bar 不再需要依赖,Foo 就必须修改。对我来说似乎是不行的。由于我真的不认为这应该是一个解决方案,因此我没有在此处发布代码,而是为非常感兴趣的读者提供了 on pastebin ;)
2) 使用带状态的函数
这种方法不是将Prefix 对象放在Foo 中,而是尝试将其封装在create_foo 函数中。通过为每个Foo 对象创建一个Prefix 并使用lambda 在一个无名函数中引用它,我将细节(a.k.a there-is-a-prefix-object)远离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++ 背景,我希望一个函数依赖于它的参数、它的类成员(如果它是一个方法)或一些全局状态。因此,不使用全局状态的无参数函数每次调用时都必须返回完全相同的值。这不是这里的情况。一旦返回的对象被修改(这意味着prefix 中的counter 已增加),该函数返回一个对象,该对象的状态与第一次返回时的状态不同。
这种假设是否只是由于我在 python 方面的有限经验引起的,我是否必须改变我的心态,即不要考虑函数而是 something 可调用的?还是为函数提供状态是对 lambda 的无意误用?
3) 使用可调用类
为了克服我对有状态函数的疑虑,我可以使用可调用类,其中方法 2 的 create_foo 函数将被替换为:
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 下运行 - 如果您遇到任何问题,请在 cmets 中报告,我会尝试修复我的代码。
【问题讨论】:
-
除非
create_prefix()有副作用,否则选项 2 中的create_foo()函数已经没有副作用。这里可观察到的副作用是什么?您应该通过在可调用对象中评估它来延迟 foo 的创建,否则它很好。 -
我认为你是对的,我使用的功能是无副作用的。我所指的事实是
create_bar参数的结果既不依赖于参数也不依赖于全局状态,而是可以通过修改其返回值来修改。我将改写我问题的相应部分。感谢您指出这一点。
标签: python dependency-injection inversion-of-control manual