C#Unity - 在运行时更改具体实现

时间:2018-01-19 05:00:51

标签: c# dependency-injection inversion-of-control unity-container

我在IoC周围遇到了一些麻烦 - 特别是使用Unity。

假设我有一个我想用来发送电子邮件的应用程序。我会这样建模:

public interface IEmailSender
{
  void SendEmail();
}

然后创建一些接口实现:

public class GmailEmailSender : IEmailSender
{
  public void SendEmail()
  {
      //code to send email using Gmail
  }
}

public class YahooEmailSender : IEmailSender
{
  public void SendEmail()
  {
      //code to send email using Yahoo
  }
}

我还有一个实际发送电子邮件的课程

public class EmailSender
{
   IEmailSender _emailSender;
   public EmailSender(IEmailSender emailSender)
   {
       _emailSender= emailSender;
   }

   public void Send()
   {
      _emailSender.SendEmail();
   }
}

所以我理解如何配置Unity以始终使用其中一个实现:

IUnityContainer container = new UnityContainer().RegisterType<IEmailSender, GmailEmailSender>());

但我不太了解的是,如果我想根据某些标准选择YahooEmailSender,并根据其他标准选择GmailEmailSender,我在哪里编写逻辑来做出决定,然后注入适当的具体实现到EmailSender构造函数,而不使用

EmailSender emailSender = new EmailSender(new YahooEmailSender());

1 个答案:

答案 0 :(得分:4)

你的问题很公平。基于一些用户输入或配置设置在运行时解决依赖性是众所周知的问题。 Mark Seemann将他的伟大着作Dependency Injection in .NET的单独部分用于此问题 - &#34; 6.1将运行时值映射到抽象&#34;。 而且我不能完全同意NightOwl888这不是DI问题。

这个问题有两个主要解决方案。

Mark Seeman在他的书中描述的第一个是让工厂将所选实施的指示作为参数。以下是它的工作原理。

首先,你应该以某种方式传递你想要使用的实现。它可能只是一个字符串(例如&#34; gmail&#34;,&#34; yahoo&#34;),但最好通过enum来完成:

public enum EmailTarget
{
    Gmail,
    Yahoo,
}

然后你应该定义工厂本身。通常,它看起来像这样:

public interface IEmailSenderFactory
{
    IEmailSender CreateSender(EmailTarget emailTarget);
}

然后你应该为工厂提供实施。它可以很简单:

public class EmailSenderFactory : IEmailSenderFactory
{
    public IEmailSender CreateSender(EmailTarget emailTarget)
    {
        switch (emailTarget)
        {
            case EmailTarget.Gmail:
                return new GmailEmailSender();

            case EmailTarget.Yahoo:
                return new YahooEmailSender();

            default:
                throw new InvalidOperationException($"Unknown email target {emailTarget}");
        }
    }
}

但是,在更复杂的情况下,还应通过DI容器创建IEmailSender的实例。在这种情况下,您可以使用基于IUnityContainer的工厂:

public class EmailSenderFactory : IEmailSenderFactory
{
    private readonly IUnityContainer diContainer;

    public EmailSenderFactory(IUnityContainer diContainer)
    {
        this.diContainer = diContainer;
    }

    public IEmailSender CreateSender(EmailTarget emailTarget)
    {
        switch (emailTarget)
        {
            case EmailTarget.Gmail:
                return diContainer.Resolve<GmailEmailSender>();

            case EmailTarget.Yahoo:
                return diContainer.Resolve<YahooEmailSender>();

            default:
                throw new InvalidOperationException($"Unknown email target {emailTarget}");
        }
    }
}

然后你应该调整EmailSender并在其中注入IEmailSenderFactorySend()方法应使用指定所选发件人的EmailTarget枚举值进行扩展:

public class EmailSender
{
    private readonly IEmailSenderFactory senderFactory;

    public EmailSender(IEmailSenderFactory senderFactory)
    {
        this.senderFactory = senderFactory;
    }

    public void Send(EmailTarget emailTarget)
    {
        var sender = senderFactory.CreateSender(emailTarget);
        sender.SendEmail();
    }
}

最后一件事是合适的组合根:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<IEmailSenderFactory, EmailSenderFactory>();

最后当你需要发送电子邮件时:

var sender = container.Resolve<EmailSender>();
sender.Send(EmailTarget.Gmail);

第二种方法更简单。它没有使用工厂,它基于Unity命名依赖项。通过这种方法,您的课程可以保持原样。这是组合根:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<EmailSender>("gmail", new InjectionConstructor(new ResolvedParameter<IEmailSender>("gmail")));
container.RegisterType<EmailSender>("yahoo", new InjectionConstructor(new ResolvedParameter<IEmailSender>("yahoo")));

发件人按以下方式创建:

var emailSender = container.Resolve<EmailSender>("gmail");
emailSender.Send();

由您来决定使用哪种方法。纯粹主义者会说第一个更好,因为你没有将特定的DI容器与你的应用程序逻辑混合在一起。实际上,如果工厂是基于DI容器,但它集中在一个地方,可以很容易地更换。然而,第二种方法更简单,可用于最简单的场景。