温莎城堡的注册意外行为

Dan*_*nko 5 c# castle-windsor

我注意到一种奇怪的行为:

public class Bar : IBar
{
...something here
}

public class Foo : IBar
{
...something here
}
Run Code Online (Sandbox Code Playgroud)
container.Register(Component.For<IBar>().ImplementedBy<Bar>());
var test1 = container.Resolve<IBar>(); //returns Bar
container.Register(Component.For<Foo>().ImplementedBy<Foo>());
var test2 = container.Resolve<IBar>(); //returns Foo
Run Code Online (Sandbox Code Playgroud)

为什么test2是Foo?我没有将Foo注册为IBar,我显然已将其注册为Foo的实现。我虽然应该只解决Bar,因为它是IBar唯一的实现。该项目不是我的。其他开发人员可能会进行一些奇怪的设置。

Sco*_*nen 4

非常简短的答案是,如果单独执行,上面显示的代码将按照您的预期工作,返回Barnot Foo。如果它返回,Foo则说明有使用同一容器进行的其他组件注册。

在这种情况下,单元测试是一种简单的方法来查看某些东西是否按照我们认为孤立的方式运行。

该测试通过:

[TestMethod]
public void registration_returns_expected_type()
{
    var container = new WindsorContainer();
    container.Register(Component.For<IBar>().ImplementedBy<Bar>());
    var test1 = container.Resolve<IBar>(); //returns Bar
    container.Register(Component.For<Foo>().ImplementedBy<Foo>());
    var test2 = container.Resolve<IBar>();
    Assert.IsInstanceOfType(test2, typeof(Bar));
}
Run Code Online (Sandbox Code Playgroud)

基于此,我想看看还有什么正在注册依赖项。如果您更改上述内容,以便将Foo和 都Bar注册为 的实现IBar,则它会运行并仍然返回Bar

如果我们将Foo注册更改为:

container.Register(Component.For<IBar>().ImplementedBy<Foo>().IsDefault());
Run Code Online (Sandbox Code Playgroud)

...然后IBar解决为Foo.

IWindsorInstaller还可能发生的情况是,项目中有多个安装程序(实现 的类)以保持注册较小且易于管理,但它们包含冲突的注册。他们被这样处决:

container.Install(FromAssembly.This());
Run Code Online (Sandbox Code Playgroud)

...或使用从其他程序集执行安装程序的命令。

在这种情况下,启动时“获胜”的注册可能是不确定的。我一直被其他人为同一个组件添加不同的注册所困扰,我的代码仍然工作正常,但在另一个环境中选择了另一个组件。

在大多数现实情况下,当我们设计接口时,我们知道它是要在运行时与单个实现还是多个实现一起使用。如果这是一个简单的应用程序并且我们认为这不是问题,那么这可能不是问题。

如果我们预先计划多个实现 - 也许我们打算解决某些问题的集合- 那么我们可以使用命名依赖项预先处理这个问题。

container.Register(Component.For<IBar>().ImplementedBy<Bar>().IsDefault());
Run Code Online (Sandbox Code Playgroud)

现在,如果其他人注册了不同的实现,他们将需要使用命名依赖项或其他机制来解决他们的实现。

但仍然有一个问题。没有什么可以阻止他们IsDefault在注册其依赖项时进行指定,然后您又回到了原点。

(你会发现,在此之后的一切都陷入了采取额外措施来防御可能永远不会发生的事情的困境。)

一个合理的方法是,如果您注册了一个接口实现,如果您认为可能注册了另一个实现(并且如果很容易找到),那么您可以进行检查。如果没有其他的,请将您的注册为默认值。然后下一个人看到你的默认设置后,就会避免与之发生冲突。这并不是万无一失的。

一种防御方法是自己使用命名依赖项,尽管如果您不需要它们,这会很不方便。(这里的根本问题是,如果使用不同的安装程序,您是否需要它们并不完全明显。)

如果您正在为几个组件创建单独的安装程序,那么您可能已经考虑了特定的实现,因此您可以指定它们。

public class MyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<NeedsBar>().DependsOn(Dependency.OnComponent<IBar, Foo>()));
    }
}
Run Code Online (Sandbox Code Playgroud)

我宁愿不必这样做只是为了防止可能永远不会发生的冲突。

这留下了一些选择,但所有这些都有缺陷:

  • 意识到潜在的问题并提防它。我不喜欢用“小心”作为任何事情的解决方案,但在受到此影响后,我发现这已经足够了。
  • 运行集成测试,使用所有运行时组件配置容器,然后检查容器是否存在默认或非默认的重复注册。即使知道重复注册的可能性,我也从未觉得有必要这样做。
  • 希望如果一个接口非常通用,以至于它在应用程序范围内的组件中使用,那么您可以避免多个实现,或者在这种情况下,了解它的使用方式可以导致明确的注册。在其他情况下,只需在靠近需要的地方定义单独的接口即可。