Unity - 用于构造函数注入的 DependencyAttribute 或接口继承

Unity - DependencyAttribute or Interface Inheritance for constructor injection

关于使用 Unity 进行构造函数注入的建议做法是什么? 从 DI 的角度来看,接下来的两个例子中哪一个是更好的做法?有更好的解决方案吗?

(这些例子是简单的插图)

public interface ICircle
{
      double Radius{get;set;}
}

Container.RegisterType<ICircle, SmallCircle>("Small");
Container.RegisterType<ICircle, BigCircle>("Big");

public class Bike{
   Public Bike([Dependency("Big") ICircle bigCircle, Dependency("Small") ICircle smallCircle) {     }
}

或者这个更强类型的解决方案...

public interface IBigCircle : ICircle
{
    // **Empty interface**
}

Container.RegisterType<ICircle, SmallCircle>();
Container.RegisterType<IBigCircle, BigCircle>();

public class Bike{
   Public Bike( IBigCircle bigCircle, ICircle smallCircle) {     }
}

让我担心的是,在第二种解决方案中,空接口的数量会随着时间的推移而增加。

在第一种方法中,您的 classes 知道将注入它们的依赖项的名称。所以这段代码:

[Dependency("Big")] ICircle bigCircle

与以下差别不大:

bigCircle = ServiceLocator.Locate<ICircle>("Big");

这是service locator anti-pattern

您的 class 基本上是在引导容器注入具有特定名称的依赖项。它参与组成过程。在进行适当的依赖注入时,classes 应该只是一个被动的角色。

您的第二种方法是尝试通过引入另一个表示相同抽象的接口来解决这个问题,只是为了取悦容器。如果你有一个 ICircledecorator 会发生什么,你是否必须为 IBigCircle 创建一个类似的装饰器?

更好的方法是在没有容器的情况下进行依赖注入。这个叫做Pure DI. See this article,有相关讨论。当您有单个接口的多个实现时(在许多应用程序中都是如此),纯 DI 特别有用。

这个例子带有一些假设。例如,从接口名称中建议 IBigCircle 的半径大于 ISmallCircle 但这取决于实现,而不是接口名称。这些接口无法保证其名称所暗示的含义。 (就像 IEnumerable<string> bigList, IEnumerable<string> smallList 无法真正强制执行哪个更大。)

如果重要的是一个比另一个大,您可以只依赖两个 ICircles 并在运行时确定哪个更大。或者你可以有一个由两个 ICircles 组成的 IWheels 接口,并使用某种工厂从可用的实现中创建可接受的圆对。

IMO,第一个代码示例比第一个更好,因为它没有用不必要的接口(例如 IBigCircle)使设计混乱。然而,这两个例子都不是很好。第一个示例的问题是使用 DependencyAttribute 将容器耦合到应用程序,这是要避免的事情。

那么,如果这两个示例都不是很好,那更好的选择是什么?

这里有一些。第一种选择是使用 InjectionFactory 来实例化所需的对象:

IUnityContainer container = new UnityContainer();

container.RegisterType<ICircle, SmallCircle>("Small");
container.RegisterType<ICircle, BigCircle>("Big");

container.RegisterType<Bike>(new InjectionFactory(
    c => new Bike(c.Resolve<ICircle>("Big"), c.Resolve<ICircle>("Small"))));

在上面的示例中,容器用于解析 "Big" 和 "Small" 已注册的 ICircle 实现。此外,因为我们使用 new Bike,我们可以在编译时检查构造函数参数。

另一种类似的方法是使用 InjectionConstructor 指定要注入的对象:

container.RegisterType<Bike>(new InjectionConstructor(
    new ResolvedParameter<ICircle>("Big"),
    new ResolvedParameter<ICircle>("Small")
    ));

上面告诉 Unity 使用带有两个 ICircle 参数的构造函数,ResolvedParameter 表示按名称解析特定的 ICircle

您可以使用的另一种方法(但可能不是首选,但为了完整起见包含在此处)是在解析时而不是注册时指定需要哪些类型:

container.Resolve<Bike>(
    new ParameterOverride("bigCircle",   container.Resolve<ICircle>("Big")),
    new ParameterOverride("smallCircle", container.Resolve<ICircle>("Small")));