Выбор типа внедрения зависимости

Недавно я столкнулся с проблемой, когда мне нужно выбрать тип на основе параметра. Например: класс, используемый для отправки уведомлений, который должен выбрать правильный канал (электронная почта, смс, ...) на основе входного параметра.

Я выгляжу примерно так:

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, и это только решит проблему.

Есть ли какая-то модель, которая будет делать то же самое, но лучше и чище?

1 ответ

Решение

Я хотел бы предложить вам объединить ваши IEmail а также ISms интерфейсы в IMessageService (при условии, что это не нарушает принципа подстановки Лискова) и использовать шаблон стратегии, чтобы ваша служба уведомлений могла выбирать тип (или типы) 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");
    }
}

Обратите внимание, что если вы выберете такой подход, ваш EmailStrategy класс не нужно будет менять, если вы решите позже добавить или удалить IMessageService - вам нужно только изменить конфигурацию DI.

Другие вопросы по тегам