使用简单注入器解析具有相同类和接口的多个对象

本文关键字:接口 同类 对象 简单 注入器 | 更新日期: 2023-09-27 18:17:06

我正试图在我的新项目中从Unity迁移到简单注入器。它比Unity快得多,所以我不得不尝试一下。我有过一些挫折,但没有什么是我无法解决的。但是我点击了另一个"按键查找"

我读过一篇简单注入器创建者的文章,他认为每个接口不需要解析多个类。

我一定是一个糟糕的程序员,因为我用Unity(它支持得很好)做到了这一点,并想在我当前的项目中做到这一点。

我的场景是我有一个IRepository接口。我有两个独立的存储库,我想使用IRepository接口进行抽象。这样的:

container.Register<FirstData>(() => new FirstData());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<FirstData>()));

container.Register<SecondEntities>(() => new SecondEntities());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<SecondData>()));

IRepository/GenericRepository是一个相当常见的抽象,但是你只能在SimpleInjector

中拥有一个。

在Unity中,我可以注册两个存储库,然后设置构造函数注入来注入我需要的实例。这是使用实例的一个键来完成的。(我不需要在我的正常代码中做Resolve调用,也不需要在我的设置之外添加对Unity的依赖)

对于简单的注入器,这不起作用。但是,不管是好是坏,Simple Injector的所有者认为这个特性是个坏主意。

注意:作者的"in app"系统看起来像是使用字符串键进行查找,但每次仍然需要不同的类(DefaultRequestHandler, OrdersRequestHandlerCustomersRequestHandler)。我只是有一个GenericRepostory,它允许我抽象我的存储库方法,不管我连接到什么。

我想我可以继承我的GenericRepostory每次我想实例化它。或者让它取一个我不需要的随机类型参数。但这打乱了我的设计,所以我希望有另一种方法。

那么是否有任何工作,不让我创建伪造类型来区分我的两个IRepository/GenericRepository实例?

使用简单注入器解析具有相同类和接口的多个对象

我们最终改变了我们的通用存储库,看起来像这样:

/// The Type parameter has no funcionality within the repository, 
/// it is only there to help us differentiate when registering 
/// and resolving different repositories with Simple Injector.
public class GenericRepository<TDummyTypeForSimpleInjector> : IRepository

(我们给它添加了一个类型参数)
然后,我们像这样创建了两个虚拟类(我更改了类的名称以匹配我的示例):

// These are just dummy classes that are used to help 
// register and resolve GenericRepositories with Simple Injector.
public class FirstDataSelector { }
public class SecondDataSelector { }

然后我可以像这样注册它们:

container.Register<FirstData>(() => new FirstData());
container.Register(() => new GenericRepository<FirstDataSelector>
                   (container.GetInstance<FirstData>()));

container.Register<SecondEntities>(() => new SecondEntities());
container.Register(() => new GenericRepository<SecondDataSelector>
                   (container.GetInstance<SecondData>()));

(注意GenericRepository上的泛型类型参数,我没有将其注册为IRepository。

这很好。然后我就可以在业务逻辑的构造函数注入中使用该注册。

container.Register<IFirstBusiness>(() => new FirstBusiness
               (container.GetInstance<GenericRepository<FirstDataSelector>>()));
container.Register<ISecondBusiness>(() => new SecondBusiness
               (container.GetInstance<GenericRepository<SecondDataSelector>>()));

由于我的业务类采用IRepository,因此它工作得很好,并且不会向业务类公开IOC容器或存储库的实现。

我基本上使用Type参数作为查找的键。(我知道,但我的选择有限。)

不得不在我的设计中添加虚拟类有点令人失望,但我们的团队决定,缺点是值得的,而不是放弃简单注入器并回到Unity。

你自己的答案实际上很好,但不幸的是,你把泛型类型参数看作是一个虚拟的;你应该让它成为你设计的头等大事:

public interface IRepository<TData> { }
public clss GenericRepository<TData> : IRepository<TData>
{ 
    public GenericRepository(TData data) { }
}

这样你就可以简单地注册它们如下:

container.Register<IRepository<FirstData>, GenericRepository<FirstData>>();
container.Register<IRepository<SecondData>, GenericRepository<SecondData>>();

在这种情况下,您的业务类可以简单地依赖于通用的IRepository<FirstData>IRepository<SecondData>,并且可以简单地按照以下方式注册:

container.Register<IFirstBusiness, FirstBusiness>();
container.Register<ISecondBusiness, SecondBusiness>();

注意这里给出的注册没有使用任何lambda。简单的注射器可以找到这个为您。这使得你的DI配置更简单,更易读,尤其是:更易维护。

这样你可以使你的设计非常明确和明确。您的设计是模糊的,因为您有一个单一的(非通用的)IRepository接口,应该映射到几个实现。虽然这并不是在所有情况下都很糟糕,但在大多数情况下,这种模糊性可以而且应该被防止,因为这会使您的代码和配置复杂化。

此外,由于您的通用GenericRepository<T>现在映射到通用IRepository<T>,我们可以用一行替换所有Register<IRepository<T>, GenericRepository<T>>()注册:

// using SimpleInjector.Extensions;
container.RegisterOpenGeneric(typeof(IRepository<>),
    typeof(GenericRepository<>);

更进一步,您的业务类也可能受益于泛型类型。以本文为例,每个业务操作都有自己的类,但所有业务操作都隐藏在相同的通用ICommandHandler<TCommand>抽象之后。当您这样做时,所有业务类都可以通过单个调用注册:

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    typeof(ICommandHandler<>).Assembly);

这个调用在提供的程序集中搜索ICommandHandler<TCommand>接口的实现,并在容器中注册每个找到的实现。您可以添加新的业务逻辑片段(用例),而不必更改配置。但这只是对业务逻辑进行这种抽象的众多优势之一。另一个巨大的优点是,它使添加横切关注点(如日志记录、事务处理、安全性、审计跟踪、缓存,您可以想到的)更容易实现。