2015-08-12 4 views
0

В настоящее время я пытаюсь найти лучший дизайн для моего многомодульного решения с использованием DI/IOC, но теперь я как-то потерялся. У меня есть решение, где различные типы сущностей могут быть распределены получателям по разным каналам. Это упрощенная версия моих классов:Улучшение дизайна с помощью IOC/DI

#region FTP Module 
public interface IFtpService 
{ 
    void Upload(FtpAccount account, byte[] data); 
} 

public class FtpService : IFtpService 
{ 
    public void Upload(FtpAccount account, byte[] data) 
    { 
    } 
} 
#endregion 

#region Email Module 
public interface IEmailService :IDistributionService 
{ 
    void Send(IEnumerable<string> recipients, byte[] data); 
} 

public class EmailService : IEmailService 
{ 
    public void Send(IEnumerable<string> recipients, byte[] data) 
    { 
    } 
} 
#endregion 

public interface IDistributionService { } 
#region GenericDistributionModule 

public interface IDistributionChannel 
{ 
    void Distribute(); 
} 

public interface IDistribution 
{ 
    byte[] Data { get; } 

    IDistributionChannel DistributionChannel { get; } 

    void Distribute(); 
} 

#endregion 

#region EmailDistributionModule 
public class EmailDistributionChannel : IDistributionChannel 
{ 
    public void Distribute() 
    { 
     // Set some properties 
     // Call EmailService??? 
    } 

    public List<string> Recipients { get; set; } 
} 

#endregion 

#region FtpDistributionModule 
public class FtpDistributionChannel : IDistributionChannel 
{ 
    public void Distribute() 
    { 
     // Set some properties 
     // Call FtpService??? 
    } 

    public FtpAccount ftpAccount { get; set; } 
} 

#endregion 

#region Program 
public class Report 
{ 
    public List<ReportDistribution> DistributionList { get; private set; } 

    public byte[] reportData{get; set; } 
} 

public class ReportDistribution : IDistribution 
{ 
    public Report Report { get; set; } 

    public byte[] Data { get { return Report.reportData; } } 

    public IDistributionChannel DistributionChannel { get; private set; } 

    public void Distribute() 
    { 
     DistributionChannel.Distribute(); 
    } 
} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     EmailService emailService = new EmailService(); 
     FtpService ftpService = new FtpService(); 
     FtpAccount aAccount; 
     Report report; 

     ReportDistribution[] distributions = 
     { 
      new ReportDistribution(new EmailDistributionChannel(new List<string>("[email protected]", "[email protected]"))), 
      new ReportDistribution(new FtpDistributionChannel(aAccount)) 
     }; 
     report.DistributionList.AddRange(distributions); 

     foreach (var distribution in distributions) 
     { 
      // Old code: 
      // if (distribution.DistributionChannel is EmailDistributionChannel) 
      // { 
      //  emailService.Send(...);   
      // }else if (distribution.DistributionChannel is FtpDistributionChannel) 
      // { 
      //  ftpService.Upload(...); 
      // }else{ throw new NotImplementedException();} 

      // New code: 
      distribution.Distribute(); 
     } 
    } 
} 
#endregion 

В моем текущем решении можно создавать и сохранять постоянную IDistribution Pocos (i'am использование ReportDistribution здесь) и прикрепить их к распределяемой сущности (а Report в этом примере). . кто-то хочет распространить существующий Report по электронной почте на набор получателей. Поэтому он создает новый ReportDistribution' with an EmailDistributionChannel '. Позже он решает распространять те же Report через FTP на указанный FtpServer. Поэтому он создает еще ReportDistribution с FtpDistributionChannel. Можно распространять те же самые Report несколько раз на одном и том же или разных каналах.

Azure Webjob забирает хранящиеся IDistribution экземпляры и распространяет их. Нынешняя, уродливая реализация использует if-else для распределения Distributions с FtpDistributionChannel через (низкоуровневые) FtpService и EmailDistributionChannels с EmailService.

Теперь я пытаюсь реализовать метод интерфейса Distribute() на FtpDistributionChannel и EmailDistributionChannel. Но для этого нужно, чтобы организации нуждались в ссылке на службы. Внедрение служб в объекты через ConstructorInjection представляется плохой.

Mike Hadlow поставляется с тремя другими решениями:

  1. Создание доменных служб. Я мог бы, например, создайте FtpDistributionService, введите FtpService и напишите Distribute(FtpDistributionChannel distribution) (а также EmailDistributionService). Помимо недостатка, упомянутого Майком, как я могу выбрать подходящий номер DistributionService на основе экземпляра IDistribution? Замена моего старого if-else другим не кажется правильным

  2. Inject IFtpService/EMailService в метод Distribute(). Но как я должен определить метод Distribute() в интерфейсе IDistribution? EmailDistributionChannel нуждается в IEmailService, в то время как FtpDistributionChannel нужен IFtpService.

  3. Структура событий домена. Я не уверен, как это может решить мою проблему.

Позвольте мне объяснить, почему я пришел с этим довольно сложным решением: Это началось с простого списка отчетов. Вскоре кто-то попросил меня отправлять отчеты некоторым получателям (и хранить список получателей). Легко!

Позже кто-то добавил требование отправить отчет в FtpAccount. В приложении управляются разные FtpAccounts, поэтому выбранная учетная запись также должна быть сохранена. Это было до такой степени, что я добавил абстракцию IDistributionChannel. Все было хорошо.

Затем кому-то была нужна возможность также отправлять какие-то постоянные лог-файлы по электронной почте. Это привело к моему решению с помощью модуля распределения/распределения/распределения. Если теперь кому-то нужно распространять некоторые другие данные, я могу просто реализовать другое распределение для этих данных. Если требуется другой канал DistributionChannel (например, факс), я реализую его и доступен для всех распространяемых объектов.

Я бы очень признателен за любую помощь/идеи.

ответ

0

Прежде всего, зачем создавать интерфейсы для FtpAccount? Класс изолирован и не содержит поведения, которое нужно отвлечь.

Давайте начнем с вашей первоначальной проблемы и создадим оттуда. Проблема, поскольку я интерпретирую ее как то, что вы хотите отправить что-то клиенту, используя другой набор носителей.

Выражая это в коде это можно сделать, как это вместо:

public void SendFileToUser(string userName, byte[] file) 
{ 
    var distributions = new []{new EmailDistribution(), new FtpDistribution() };   
    foreach (var distribution in distributions) 
    { 
     distribution.Distribute(userName, file); 
    } 
} 

Посмотрите, что я сделал? Я добавил немного контекста. Потому что ваш первоначальный вариант использования был способом общего. Не часто вы хотите распространять некоторые произвольные данные произвольной службе распространения.

Изменение, которое я сделал, представляет собой домен и реальную проблему.

С этими изменениями мы также можем моделировать остальные классы немного иначе.

public class FtpDistributor : IDistributor 
{ 
    private FtpAccountRepository _repository = new FtpAccountRepository(); 
    private FtpClient _client = new FtpClient(); 

    public void Distribute(string userName, byte[] file) 
    { 
     var ftpAccount = _repository.GetAccount(userName); 
     _client.Connect(ftpAccount.Host); 
     _client.Authenticate(ftpAccount.userName, ftpAccount.Password); 
     _Client.Send(file); 
    } 
} 

Посмотрите, что я сделал? Я переложил ответственность за отслеживание учетной записи FTP на фактический сервис. На самом деле у вас, вероятно, есть веб-сайт администрирования или аналогичный, где учетная запись может быть сопоставлена ​​определенному пользователю.

Таким образом, я также выделил всю обработку, относящуюся к FTP, внутри службы и, следовательно, уменьшил сложность вызывающего кода.

Распространение электронной почты будет работать одинаково.

Когда вы начинаете координировать подобные проблемы, попробуйте перейти от top-> down. В противном случае легко создать архитектуру, которая кажется SOLID, в то время как на самом деле она не решит настоящую деловую проблему.

Update

Я прочитал ваши обновления, и я не понимаю, почему вы должны использовать одни и те же классы для новых требований?

Тогда кому-то понадобилось возможность также отправить свой род настойчивого Logfiles по электронной почте

Это совершенно другой случай использования и должны быть отделено от первоначального использования. Создайте для него новый код. SmtpClient в .NET довольно легко для нас и не нужно отвлекать.

Если теперь кому-то нужно распространять другие данные, я могу просто реализовать другое распределение данных для этих данных.

Почему? какую сложность вы пытаетесь скрыть?

Если другой распределительный канал (напр.Факс), я реализую его и доступен для всех распределяемых объектов.

№ Распространение товара A - это не то же самое, что распространять вещь B. Вы не можете, например, транспортировать части большого моста на авиапассажир, требуется грузовое судно или грузовик.

Я пытаюсь сказать, что создание слишком общих абстракций/контрактов для продвижения повторного использования кода кажется хорошей идеей, но обычно это просто делает ваше приложение более сложным или менее читаемым.

Создавайте абстракции, когда есть проблемы с реальной сложностью, а не на руке.

+0

Спасибо, jgauffin! Я думаю, что мой первоначальный образец кода был слишком упрощен. Я изменил его, чтобы быть ближе к моей реальной реализации и добавил некоторые дополнительные комментарии. Ваш код выглядит интересным, но я не уверен, соответствует ли он моим требованиям. Может быть, вы можете еще раз взглянуть на мой отредактированный вопрос. – krombi

+0

@krombi: прочитайте мое обновление – jgauffin

+0

Теперь я чувствую себя глупо, но я думаю, что до сих пор не понимаю вашего предлагаемого решения. My Distribution/EmailDistribution/FtpDistribution-Stuff разрабатывается в отдельных проектах/пакетах nuget. Поэтому добавить функциональность Distribution в другое приложение довольно просто. Я не пытаюсь скрыть сложность, но хочу создать многоразовые модули. Что вы предлагаете? Создайте абстрактный класс AbstractReportDistribution с унаследованными классами ReportEmailDistribution, ReportFtpDistribution, ReportFaxDistribution, а также 'AbstractLogDistribution' и decendants? – krombi

Смежные вопросы