【问题标题】:Dependency injection type-selection依赖注入类型选择
【发布时间】:2016-03-23 14:29:03
【问题描述】:

最近我遇到了一个问题,我必须根据参数选择类型。例如:用于发送通知的类,应根据输入参数选择正确的渠道(电子邮件、短信、...)。

我看起来像这样:

public class NotificationManager 
{
    IEmail _email;
    ISms _sms;

    public NotificationManager (IEmail email, ISMS sms) 
    {
        _email = email;
        _sms = sms;
    }

    public void Send(string type) 
    {
        switch(type) 
        {
            case "email":
                _email.send;
                break;

            case "sms":
                _sms.send;
                break;
        }
    }
}

这里的问题是,当我使用这种构造时,构造函数会随着发送通知的所有不同方法快速增长。

我真的不喜欢这个,它使单元测试这个选择单元变得难以控制。

我不能简单的说new email();,因为通知类邮件会依赖IEmailManager,这样只会解决问题。

是否有某种模式可以做同样的事情,但以更好、更简洁的方式?

【问题讨论】:

    标签: c# dependency-injection architecture


    【解决方案1】:

    我建议您将IEmailISms 接口组合成IMessageService(前提是不违反Liskov Substitution Principal)并使用strategy pattern 使您的通知服务能够选择使用的IMessageService 的类型。

    重构为 IMessageService

    public interface IMessageService
    {
        void Send(string subject, string body);
        bool AppliesTo(IEnumerable<string> providers);
    }
    
    public class EmailMessageService : IMessageService
    {
        public EmailMessageService(/* inject dependencies (and configuration) here */)
        {
            // Set dependencies to private (class level) variables
        }
    
        public void Send(string subject, string body)
        {
            // Implementation - use dependencies as appropriate
        }
    
        public bool AppliesTo(IEnumerable<string> providers)
        {
            return providers.Contains("email");
        }
    }
    
    public class SmsMessageService : IMessageService
    {
        public SmsMessageService(/* inject dependencies (and configuration) here */)
        {
            // Set dependencies to private (class level) variables
        }
    
        public void Send(string subject, string body)
        {
            // Implementation - use dependencies as appropriate
        }
    
        public bool AppliesTo(IEnumerable<string> providers)
        {
            return providers.Contains("sms");
        }
    }
    

    实施策略模式

    public interface IMessageStrategy
    {
        void Send(string message, string body, string provider);
        void Send(string message, string body, IEnumerable<string> providers);
    }
    
    public class MessageStrategy : IMessageStrategy
    {
        private readonly IMessageService[] messageServices;
    
        public MessageStrategy(IMessageService[] messageServices)
        {
            if (messageServices == null)
                throw new ArgumentNullException("messageServices");
            this.messageServices = messageServices;
        }
    
        public void Send(string message, string body, string provider)
        {
            string[] providers = provider.Split(';').Select(p => p.ToLower().Trim()).ToArray();
            this.Send(message, body, providers);
        }
    
        public void Send(string message, string body, IEnumerable<string> providers)
        {
            foreach (IMessageService messageService in messageServices)
            {
                if (messageService.AppliesTo(providers))
                {
                    messageService.Send(message, body);
                }
            }
        }
    }
    

    用法

    在您的 DI 容器中,注册与 IMessageService 匹配的所有类型以解析为数组。例如,在 StructureMap 中:

    container.For<IMessageService>().Use<EmailMessageService>();
    container.For<IMessageService>().Use<SmsService>();
    

    或者,您也可以使用 Scan 自动拾取事后添加的新类型。

    var container = new Container(x => x.Scan(scan =>
    {
        scan.TheCallingAssembly();
        scan.WithDefaultConventions();
        scan.AddAllTypesOf<IMessageService>();
    }));
    

    无论哪种方式,只需向容器注册类型即可满足 IMessageService[] 依赖关系。

    那么只需将IMessageStrategy 注入到需要消息传递的类中,并传递魔术字符串来选择要使用的消息服务类型即可。

    public class SomeService : ISomeService
    {
        private readonly IMessageStrategy messageStrategy;
    
        public SomeService(IMessageStrategy messageStrategy)
        {
            if (messageStrategy == null)
                throw new ArgumentNullException("messageStrategy");
            this.messageStrategy = messageStrategy;
        }
    
        public void DoSomething()
        {
            // Send a message via email
            this.messageStrategy.Send("This is a test", "Hello", "email");
    
            // Send a message via SMS
            this.messageStrategy.Send("This is a test", "Hello", "sms");
    
            // Send a message via email and SMS
            this.messageStrategy.Send("This is a test", "Hello", "email;sms");
        }
    }
    

    请注意,如果您采用这种方法,如果您稍后决定添加或删除 IMessageService,您的 EmailStrategy 类将不需要更改 - 您只需更改 DI 配置。

    【讨论】:

      猜你喜欢
      • 2021-08-13
      • 2018-06-29
      • 1970-01-01
      • 1970-01-01
      • 2012-12-29
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多