简体   繁体   English

IoC - 单一接口的多种实现支持

[英]IoC - Multiple implementations support for a single interface

I am wondering why.Net IoC containers do not easily support multiple implementations for a single interface, May be I am wrong, but as far I have seen, frameworks like Ninject partially supports this feature using annotations ( how? ).我想知道为什么.Net IoC 容器不容易支持单个接口的多个实现,可能是我错了,但据我所知,像 Ninject 这样的框架使用注释部分支持此功能( 如何? )。 I do not think other frameworks like Windsor or simple injector have an easy mechanism to support this scenario.我不认为像 Windsor 或简单注入器这样的其他框架有一个简单的机制来支持这种情况。

Is there any reason why this is not supported by many frameworks?许多框架不支持这有什么原因吗? AFAIK, one of the most important reasons to use interfaces is to achieve loose coupling. AFAIK,使用接口的最重要原因之一是实现松散耦合。 If the frameworks designed to improve loose coupling, do not fluently support multiple implementations for a single interface, I do not understand why!如果框架旨在改善松散耦合,不流畅地支持单个接口的多种实现,我不明白为什么!

PS Of course I understand that there will be a resolution issue during run time, and the container would be confused which implementation to choose, but that is something which has to be considered in the design, right? PS 当然我理解在运行时会有分辨率问题,容器会混淆选择哪个实现,但这是设计中必须考虑的事情,对吧?

Unity has the same functionality Unity具有相同的功能

Register named dependency注册命名依赖

    var container = new UnityContainer();
    container.RegisterType<IConnector, Connector>("TestConnector");

Resolve by name按名称解析

    container.Resolve<IConnector>("TestConnector");

the same approach同样的方法

    [Dependency("TestConnector")]
    public IConnector Connector { get; set; }

Windsor has the same温莎有相同的

class Program
{
    static void Main(string[] args)
    {
        var container = new WindsorContainer()
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorA>().Named("ConnectorA"))
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorB>().Named("ConnectorB"));

        var connectorA = container.Resolve<IConnector>("ConnectorA");
        Console.WriteLine("Connector type: {0}", connectorA.GetType());
        var connectorB = container.Resolve<IConnector>("ConnectorB");
        Console.WriteLine("Connector type: {0}", connectorB.GetType());
        Console.ReadKey();
    }
}

public interface IConnector
{
}

public class ConnectorA : IConnector
{

}

public class ConnectorB : IConnector
{

}

I advice to look at Convention over Configuration and especially Convention Based Dependency Injection and context-based dependency injection.我建议查看约定优于配置,尤其是基于约定的依赖注入和基于上下文的依赖注入。 Most of IoC if not all supports both approaches.大多数 IoC(如果不是全部)都支持这两种方法。 You could find many interesting samples with different IoC libraries, when several implementation bind to one interface, and how useful it could be.当多个实现绑定到一个接口时,您会发现许多具有不同 IoC 库的有趣示例,以及它的用处。

For example does support binding of several implementation of one interface: depends on context or attributes, by names, and so one .例如确实支持绑定一个接口的多个实现:取决于上下文或属性、按名称等等

By context按上下文

Following snippet binds on implemetation depends on target type automaticaly:以下代码段绑定实现取决于目标类型自动:

Bind<IWarrior>().To<Samurai>().WhenInjectedInto(typeof(OnLandAttack));
Bind<IWarrior>().To<SpecialNinja>().WhenInjectedInto(typeof(AmphibiousAttack));

By name按名字

Very helpful when you configuration is in XML or database.当您的配置是 XML 或数据库时非常有用。 Take into account InNamedScope also:还要考虑InNamedScope

Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");

By convention按照惯例

With different dependency configuration at different parts of your project.在项目的不同部分使用不同的依赖配置。

Your premise is wrong.你的前提是错误的。

Windsor quite happily accepts registrations of multiple implementations of the same service. Windsor 非常乐意接受同一服务的多个实现的注册。 In addition to the named component resolution support mentioned by GSerjo, in Windsor (by default), the first registered implementation will win but you can override this by using IsDefault() method when registering an alternative implementation.除了 GSerjo 提到的命名组件解析支持之外,在 Windsor 中(默认情况下),第一个注册的实现将获胜,但您可以在注册替代实现时使用IsDefault()方法覆盖它。 Please see http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx for more details.有关详细信息,请参阅http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx

If you wish to exercise more control over the selection from multiple implementations you can create an IHandlerSelector implementation to do so.如果您希望对从多个实现中进行的选择进行更多控制,您可以创建一个 IHandlerSelector 实现来执行此操作。 Please see http://stw.castleproject.org/Windsor.Handler-Selectors.ashx for more details.有关详细信息,请参阅http://stw.castleproject.org/Windsor.Handler-Selectors.ashx

My container Griffin.Container supports it.我的容器 Griffin.Container 支持它。

registrar.RegisterConcrete<OneImplementation>();
registrar.RegisterConcrete<AnotherImplementation>();

And to fetch:并获取:

var services = container.Resolve<ITheService>();

However, you can't get one specific implementation.但是,您无法获得一种特定的实现。 It's a design decision.这是一个设计决定。 It's much better to register a factory in the container if have to get a specific implementation.如果必须获得特定的实现,最好在容器中注册一个工厂。 Read more here in the best practices section.在最佳实践部分阅读更多信息

Griffin.Container can be found at github: https://github.com/jgauffin/griffin.container Griffin.Container 可以在 github 上找到: https ://github.com/jgauffin/griffin.container

StructureMap provides these abilities: StructureMap 提供以下功能:

For<IMyInterface>().Add<MyInterfaceImpl1>().Named("MyInterfaceImpl1");
For<IUsingInterface>().Add<UsingInterfaceImpl>().Ctor<IMyInterface>().Is(i => i.GetInstance<IMyInterface>("MyInterfaceImpl1"));

Your question is a bit vague, since you don't supply a concrete example of when you think you need this.你的问题有点含糊,因为你没有提供一个具体的例子来说明你什么时候需要这个。 In most cases there is a problem in your application or design, or you aren't following DI best practices.在大多数情况下,您的应用程序或设计存在问题,或者您没有遵循 DI 最佳实践。

All containers allow you to register multiple dependencies with the same interface as an IEnumerable<ThatInterface> , even if they do not have deep support for multiple instances.所有容器都允许您使用与IEnumerable<ThatInterface>相同的接口注册多个依赖项,即使它们没有对多个实例的深度支持。 Injecting lists of services into other services however, is a design smell, and it would be better to hide this list behind a Composite.然而,将服务列表注入其他服务是一种设计味道,最好将此列表隐藏在组合后面。 This hides the fact that there are multiple implementations behind the abstraction, and allows you to easily change the way those multiple implementations are used, by changing just a single place in the application.这隐藏了抽象背后有多个实现的事实,并允许您通过仅更改应用程序中的一个地方来轻松更改这些多个实现的使用方式。 I don't believe any IoC framework has any support for generating composites for you, since the there is no one default way of processing the wrapped implementations.我不相信任何 IoC 框架都支持为您生成复合材料,因为没有一种默认方法来处理包装的实现。 You'll have to write this Composite yourself.您必须自己编写此 Composite。 However, since writing such a composite is really, really simple, this justifies not having such feature in the framework.但是,由于编写这样的组合非常非常简单,因此框架中没有这样的功能是合理的。

If you want to have multiple implementations, but always need one to be returned, based on some configuration, there are always ways to do this.如果你想有多个实现,但总是需要返回一个,基于一些配置,总有办法做到这一点。 Most containers allow you to configure those dependencies in an XML configuration file.大多数容器允许您在 XML 配置文件中配置这些依赖项。 But even if a container does not contain such feature, reading this value from the configuration file manually and registering the right type in the container is very easy.但即使容器不包含此类功能,手动从配置文件中读取此值并在容器中注册正确的类型也非常容易。

If you have one implementation of a certain interface for production and another implementation for unit testing purposes, you should only register the production implementation in the container.如果你有一个用于生产的特定接口的实现和用于单元测试目的的另一个实现,你应该只在容器中注册生产实现。 Your unit tests should be clear of any DI container, and you should manually create a class under test, and inject fake dependencies in its constructor, by simply new ing the type up.您的单元测试应该清除任何 DI 容器,并且您应该手动创建一个被测类,并通过简单地new类型来在其构造函数中注入假依赖项。 Using a DI container, pollutes and complicates your tests.使用 DI 容器会污染并使您的测试复杂化。 To pull this off, you will need to design such type around the constructor injection pattern.要做到这一点,您需要围绕构造函数注入模式设计此类类型。 Don't call the container (or any other facade over the container) inside the service under test, to retrieve dependencies.不要在被测服务内部调用容器(或容器上的任何其他外观)来检索依赖项。

If you want to access implementations with certain conditions, you can use Dictionary.如果要访问具有特定条件的实现,可以使用 Dictionary。

UC_Login: The user must validate their credentials according to the Authentication Mode (By Database or Active Directory), each authentication mode has different business logic. UC_Login:用户必须根据身份验证模式(通过数据库或活动目录)验证其凭据,每种身份验证模式都有不同的业务逻辑。

My code: I have an Interface called IAuthService.cs I have two classes called DatabaseAuthService.cs and ActiveDirectoryAuthService.cs both with the same IsValidCredential (User user) method that depend on the same Interface.我的代码: 我有一个名为 IAuthService.cs 的接口 我有两个名为 DatabaseAuthService.cs 和 ActiveDirectoryAuthService.cs 的类,它们都具有依赖于同一接口的相同 IsValidCredential(用户用户)方法。

public interface IAuthService
{
  Task<bool> IsValidCredentialAsync(User user);
}

public class DatabaseAuthService : IAuthService
{
  private readonly IDatabaseAuthRepository _databaseAuthRepository;
  // User IServiceProvider for access to any other interfaces
  // using Microsoft.Extensions.DependencyInjection; using System;
  public DatabaseAuthService(IServiceProvider serviceProvider)
  => _databaseAuthRepository = serviceProvider.GetService<IDatabaseAuthRepository>();

  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // return await _databaseAuthRepository.something...
  }
}

public class LdapAuthService : IAuthService
{
  public LdapAuthService()
  {
  }
  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // something...
  }
}

Condition Implemented: I use the AuthenticationAppServiceclass, with the LoginAsync (LoginDto dto) method.实施条件:我使用 AuthenticationAppService 类和 LoginAsync (LoginDto dto) 方法。

public class AuthenticationAppService
{
  private readonly Dictionary<AuthenticationModeEnum, IAuthService> _authProviders =
      new Dictionary<AuthenticationModeEnum, IAuthService>();
  public AuthenticationAppService(IServiceProvider serviceProvider)
  {
    _authProviders.Add(AuthenticationModeEnum.Database, new DatabaseAuthService(serviceProvider));
    _authProviders.Add(AuthenticationModeEnum.ActiveDirectory, new LdapAuthService());
  }

  public Task<bool> LoginAsync(LoginDto dto)
  {
    var user = Mapper.Map<user, LoginDto>(dto);
    return await _authProviders[(AuthenticationModeEnum)dto.AuthMode].IsValidCredentialAsync(user);
  }
}

maybe not on topic, but hope it helps.也许不是主题,但希望它有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM