Dependency injection type-selection

前端 未结 1 1320
悲&欢浪女
悲&欢浪女 2020-11-27 23:38

Recently I\'ve come accross a problem where I have to select a type based on a parameter. For example: a class used for sending notifications that should select the right ch

相关标签:
1条回答
  • 2020-11-28 00:13

    I would suggest that you combine your IEmail and ISms interfaces into an IMessageService (provided that doesn't violate the Liskov Substitution Principal) and to use a strategy pattern to enable your notification service to be able to select the type (or types) of IMessageService that are used.

    Refactor to 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");
        }
    }
    

    Implement the Strategy Pattern

    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);
                }
            }
        }
    }
    

    Usage

    In your DI container, register all types that match IMessageService to be resolved as an array. For example, in StructureMap:

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

    Or alternatively you can use Scan to pickup new types automatically that are added after the fact.

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

    Either way, registering the types with the container is all you need to satisfy the IMessageService[] dependency.

    Then it is just a matter of injecting IMessageStrategy into a class that requires messaging and passing the magic string to select which types of message services to use.

    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");
        }
    }
    

    Note that if you take this approach, your EmailStrategy class won't need to change if you decide later to add or remove a IMessageService - you only need to change the DI configuration.

    0 讨论(0)
提交回复
热议问题