【问题标题】:Autofac intentional circular dependencyAutofac 故意循环依赖
【发布时间】:2018-01-11 15:09:41
【问题描述】:

对于 Autofac,注册类型或声明此类圆形图的依赖关系的正确方法是什么?

public interface IComponent
{
    void DoSomething();
}

public class AComponent: IComponent
{
    ...
}

public class BComponent: IComponent
{
    ...
}

public class CompositeComponent: IComponent
{
    public CompositeComponent(IEnumerable<IComponent> components)
    {
        this.components = components;
    }

    public void DoSomething() 
    {
        foreach(var component in components)
            component.DoSomething();
    }
}

最终目标是使 CompositeComponent 成为 IComponent 的默认注册,并简单地将调用传递给所有其他实现。

【问题讨论】:

  • 循环性在哪里? CompositeComponent 依赖于 AComponentBComponent,但 AComponentBComponent 不依赖于 CompositeComponent。你能解释一下这种循环在哪里吗?
  • 我认为“圆圈”是CompositeComponentIComponent 的事实,但它还需要接受所有IComponent 实例除了本身。我会提供一个答案,但简短的说法是这是一个设计问题。
  • @Steven,我为延迟回复道歉,但 Travis 是正确的。 AComponent 和 BComponent 上没有约束面以及 CompositeComponent 的开放依赖关系导致 CompositeComponent 对其自身的循环依赖。我们可以在 CompositeComponent 的注册中使用元数据或键控服务来正确约束注入。

标签: autofac


【解决方案1】:

我收集到问题的意图是您有一些IComponent 的实现,并且您有某种CompositeComponent 也实现了IComponentCompositeComponent 需要所有已注册的 IComponent 实例除了它自己,否则它会创建循环依赖。

整个事情与我们的一个常见问题解答有很大的重叠:"How do I pick a service implementation by context?"

你有一些选择。按照我个人的推荐顺序:

选项 1:重新设计界面

这里实际上有两个概念 - 单独的处理程序的概念和聚合一组单独的处理程序的事物的概念。

使用不太通用的术语,您可能有一个 IMessageHandler 接口,然后是通过 所有 IMessageHandler 实现的集合传递消息的东西,但那个东西聚合了处理程序并处理错误并确保消息仅由正确的处理程序处理,并且所有...不是,本身,也是一个消息处理程序。这是一个消息处理器。所以你实际上有两个不同的接口,即使接口上的方法看起来相同 - IMessageHandlerIMessageProcessor

回到你的通用组件术语,这意味着你有 IComponent 就像你现在做的那样,但你也会添加一个 IComponentManager 接口。 CompositeComponent 会改变以实现它。

public interface IComponentManager
{
    void DoSomething();
}


public class ComponentManager : IComponentManager
{
    public ComponentManager(IEnumerable<IComponent> components)
    {
        this.components = components;
    }

    public void DoSomething() 
    {
        foreach(var component in components)
            component.DoSomething();
    }
}

选项 2:使用密钥服务

如果您不会(或不能)重新设计,您可以使用服务键“标记”哪些注册应该有助于组合。当您注册组合时,不要使用键......但请指定您想要的构造函数参数应该从键控贡献者解析。

builder.RegisterType<AComponent>()
       .Keyed<IComponent>("contributor");
builder.RegisterType<BComponent>()
       .Keyed<IComponent>("contributor");
builder.RegisterType<CompositeComponent>()
       .As<IComponent>()
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.Name == "components",
           (pi, ctx) => ctx.ResolveKeyed<IEnumerable<IComponent>>("contributor")));

当您在不提供密钥的情况下解析 IComponent 时,您将获得 CompositeComponent,因为它是唯一以这种方式注册的。

选项 3:使用 Lambdas

如果您预先知道应该进入组合的组件集,您可以在 lambda 中构建它,而不是对整个事情进行过度 DI。

builder.Register(ctx =>
  {
    var components = new IComponent[]
    {
      new AComponent(),
      new BComponent()
    };
    return new CompositeComponent(components);
  }).As<IComponent>();

它更手动,但也很清楚。如果需要,您可以使用 ctx lambda 参数解析 AComponentBComponent 的各个构造函数参数。

【讨论】:

  • 感谢您的详尽回答,先生。另外,谢谢你的链接。我知道我以前读过,但是用新的眼光和你的额外方向来接近这个,我更好地理解了我所缺少的东西。在我的特定用例中,使用密钥服务似乎是正确的解决方案。
猜你喜欢
  • 1970-01-01
  • 2020-01-13
  • 2012-05-25
  • 1970-01-01
  • 2010-09-12
  • 2021-10-02
  • 1970-01-01
相关资源
最近更新 更多