关于设计模式:我们是否有效地使用IoC?

Are we using IoC effectively?

所以我的公司使用了温莎城堡国际奥委会集装箱,但在某种程度上感觉"不舒服":

  • 所有数据类型都注册在代码中,而不是配置文件中。
  • 所有数据类型都经过硬编码以使用一个接口实现。事实上,对于几乎所有给定的接口,存在并且将永远只有一个实现。
  • 所有注册的数据类型都有一个默认的构造函数,因此温莎不会为任何注册的类型实例化对象图。

设计该系统的人坚持使用IOC容器使系统更好。我们有1200多个公共类,所以这是一个大系统,你可以在那里找到温莎这样的框架。但我仍然怀疑。

我的公司使用IOC是否有效?使用winsor的新对象比使用new关键字的新对象有优势吗?


简短的回答:不,您的公司没有有效地使用DI。

稍微长一点的答案:主要问题是所有类都有默认的构造函数。在这种情况下,如何解决依赖关系?

您的构造函数或者具有这样的硬编码依赖项:

1
2
3
4
public Foo()
{
    IBar bar = new Bar();
}

在这种情况下,如果不重新编译该应用程序,就不能改变依赖项。

更糟糕的是,他们可能会使用静态服务定位器反模式:

1
2
3
4
public Foo()
{
    IBar bar = Container.Resolve<IBar>();
}

DI容器应该解析应用程序的组成根目录中的整个依赖关系图,并避开这个问题。

一般来说,通过在代码中使用启发式方法来配置容器,最好使用约定而不是配置。XML配置应该为少数需要在不重新编译的情况下重新配置依赖项的情况保留,这些情况下依赖项只应该是一个子集。简而言之,我认为在代码中配置容器没有固有的问题。实际上,这是首选的方法。

每个接口只有一个实现也不是问题。这是一个开始,如果应用程序是真正松散耦合的,那么它是一个不断打开的机会窗口。迟早,您很可能会引入替代实现,但如果接口已经就位,并且整个代码库都遵循Liskov替换原则,则最好这样做。


All the data types are registered in code, not the config file.

根据数据类型,我认为您指的是绑定。例如,IFoo应该与Foo绑定。如果是这样,那就好了。事实上,在许多人眼中(包括我在内),这比通过XML指定绑定要好得多。这里的自然好处是在编译时进行检查。你不会像他们说的那样用XML编程。

All data types are hard-coded to use
one interface implementation. In fact,
for nearly all given interfaces, there
is and will only ever be one
implementation.

你确定?如果是这样,这可能是一种气味。不过,你已经说过这是一个很大的应用程序。如果正在进行任何单元测试(或自动测试),那么每个实现都有一个接口是完全有效的。

Is my company using IoC effectively?
Is there an advantage to new'ing
objects with Windsor than new'ing
objects with the new keyword?

是的,据我所知。对于任何形式的自动化测试,在对象内创建依赖项都是一件糟糕的事情。通过使用IOC容器,可以在实际将生产代码连接在一起时移除类的复杂连接。这简化了应用程序,并允许作为开发人员的您专注于特性和其他任务,而不是确保整个事情实际上是构建的。

此外,通过反转控件,对应用程序的模块化部分进行更改非常容易。例如,假设您希望切换模块以获得更好的版本。使用IOC容器,您只需修改一行或两行绑定。如果没有IOC,您必须手动遍历应用程序,并确保其内部连接正确。

这是一个很好的问题,与你的最后一点有关,为什么你应该使用IOC而不是直接人工方法。


我相信其他人会贴出更详细的答案,但我有:

All the data types are registered in code, not the config file.

使用代码而不是XML配置来注册依赖项的IOC/依赖项注入框架(至少某些框架)有一个趋势。主要的好处是,在编译时,您知道是否有错误的连接。例如,如果您决定添加/删除一个构造函数参数,那么当您的配置不同步时,您的代码实际上不会编译(并且它会防止打字错误在运行时显示为随机的空引用异常)。

All data types are hard-coded to use one interface implementation. In fact, for nearly all given interfaces, there is and will only ever be one implementation.

我不太确定"一个接口实现"是什么意思。如果您的意思是接口向下投射到实现它们的对象上,那么这绝对不是犹太洁食。如果不是这样,那么编码到接口而不是具体的实现是有好处的。主要的功能显然是能够在单元测试中使用支持所述接口的模拟对象。编程到接口(理论上)迫使您使代码更松散地耦合,这有助于测试性。它还打开了一些非常酷的设计选项,比如这个选项构建了一个带有Hibernate和SpringAOP的通用类型安全DAO。

All registered data types have a default constructor, so Windsor doesn't instantiate an object graph for any registered types.

这也不一定是坏事。我认为一般来说,经验法则是,如果依赖关系对于类执行其函数至关重要,那么应该使用构造函数注入。如果不是(日志记录可能是最常用的例子),那么属性注入是一个更合适的选项。虽然我不熟悉CaseWistor,但我知道至少有些Java IOC容器不支持早期版本中的构造函数注入(如果熟悉CaseWistor的人可以对此发表评论,那太棒了)。如果Castle Windsor在早期版本中没有此功能,这也可能导致您在系统中看到的设计。

至于在任何地方使用IOC和调用new,这也取决于用法。我所做的几个项目都依赖于IOC(Java中的Spring和.NET中的AutoFac),在一个地方把我所有的依赖关系连接起来,使我们能够快速而无痛苦地对对象层次结构和组成进行实验。


你的前两个问题不是真正的问题(finglas描述了原因)。

我认为"所有注册的数据类型都有一个默认的构造函数"是一种代码味道(假设类直接从IOC容器加载依赖项)。当发生这种情况时,其1)一个应用程序入口点,在该入口点可以从IOC容器中获取对象(无论如何,您可能需要初始化该IOC容器),或2)一个可以使用工厂的地方。

不鼓励使用默认的构造函数填充依赖项。原因是你在复制如何创建类的知识。理想情况下,每个类甚至不必知道IOC容器在哪里——这是应用程序入口点的工作。

听起来你们公司的IOC使用总体上还不错。