Используя IoC и Dependency Injection, как обернуть код новым уровнем реализации, не нарушая принцип Open-Closed? - PullRequest
7 голосов
/ 20 марта 2010

Я пытаюсь выяснить, как это будет сделано на практике, чтобы не нарушать принцип Открыто-Закрыто.

Скажем, у меня есть класс с именем HttpFileDownloader, в котором есть одна функция, которая принимает URL-адрес и загружает файл, возвращающий html в виде строки. Этот класс реализует интерфейс IFileDownloader, который имеет только одну функцию. Поэтому во всем моем коде у меня есть ссылки на интерфейс IFileDownloader, и мой контейнер IoC возвращает экземпляр HttpFileDownloader всякий раз, когда разрешен IFileDownloader.

Затем после некоторого использования становится ясно, что иногда сервер слишком занят в данный момент и выдается исключение. Я решил, что, чтобы обойти это, я собираюсь автоматически повторить попытку 3 раза, если получу исключение, и подождать 5 секунд между каждой попыткой.

Поэтому я создаю HttpFileDownloaderRetrier, в котором есть одна функция, которая использует HttpFileDownloader в цикле for с максимум 3 циклами и 5-секундным ожиданием между каждым циклом. Так что я могу проверить возможности «повторять» и «ждать» HttpFileDownloadRetrier. У меня есть зависимость HttpFileDownloader, введенная с помощью конструктора HttpFileDownloaderRetrier, принимающего IFileDownloader.

Так что теперь я хочу, чтобы все Resolving of IFileDownloader возвращал HttpFileDownloaderRetrier. Но если я сделаю это, то зависимость IFileDownloader HttpFileDownloadRetrier получит экземпляр самого себя, а не HttpFileDownloader.

Итак, я вижу, что могу создать новый интерфейс для HttpFileDownloader с именем IFileDownloaderNoRetry и изменить HttpFileDownloader для его реализации. Но это означает, что я меняю HttpFileDownloader, который нарушает Open Closed.

Или я мог бы реализовать новый интерфейс для HttpFileDownloaderRetrier под названием IFileDownloaderRetrier, а затем изменить весь мой другой код, чтобы ссылаться на него вместо IFileDownloader. Но опять же, я теперь нарушаю Open Closed во всем моем другом коде.

Так чего мне здесь не хватает? Как обернуть существующую реализацию (загрузку) новым уровнем реализации (повторение и ожидание) без изменения существующего кода?

Вот некоторый код, если он помогает:

public interface IFileDownloader
{
  string Download(string url);
}

public class HttpFileDownloader : IFileDownloader
{
  public string Download(string url)
  {
    //Cut for brevity - downloads file here returns as string
    return html;
  }
}

public class HttpFileDownloaderRetrier : IFileDownloader
{
  IFileDownloader fileDownloader;

  public HttpFileDownloaderRetrier(IFileDownloader fileDownloader)
  {
    this.fileDownloader = fileDownloader;
  }

  public string Download(string url)
  {
    Exception lastException = null;
    //try 3 shots of pulling a bad URL.  And wait 5 seconds after each failed attempt.
    for (int i = 0; i < 3; i++)
    {
      try { fileDownloader.Download(url); }
      catch (Exception ex) { lastException = ex; }
      Utilities.WaitForXSeconds(5);
    }
    throw lastException;
  }
}

Ответы [ 2 ]

5 голосов
/ 20 марта 2010

Вы более или менее используете шаблон проектирования Автоматический выключатель . Как всегда, когда дело доходит до реализации сквозных задач с использованием DI, ключом является применение шаблона Decorator .

Напишите загрузчик CircuitBreakingFileDownloader следующим образом:

public class CircuitBreakingFileDownloader : IFileDownloader
{ 
    private readonly IFileDownloader fileDownloader;

    public CircuitBreakingFileDownloader(IFileDownloader fileDownloader)
    {
        if (fileDownloader == null)
        {
            throw new ArgumentNullException("fileDownloader");
        }

        this.fileDownloader = fileDownloader;
    }

    public string Download(string url)
    {
        // Apply Circuit Breaker implementation around a call to
        this.fileDownloader.Download(url)
        // here...
    }
} 

Этот подход следует принципу Open / Closed и предпочитает композицию наследованию . Он также удовлетворяет Принципу единой ответственности , поскольку автоматический выключатель имеет дело только с этим аспектом, в то время как оформленный IFileDownloader концентрируется на собственной ответственности.

Большинство правильных DI-контейнеров понимают шаблон Decorator, поэтому теперь вы можете настроить свой контейнер для разрешения запроса IFileDownloader, возвратив CircuitBreakingFileDownloader, который содержит настоящий HttpFileDownloader.

Фактически, этот подход может быть обобщен настолько, что вы можете взглянуть на прерыватель общего назначения перехватчик . Вот пример, который использует Замок Виндзор .

3 голосов
/ 20 марта 2010

Как насчет производных от HttpFileDownloader:

public class HttpFileDownloader : IFileDownloader
{
    public virtual string Download(string url)
    {
        //Cut for brevity - downloads file here returns as string
        return html;
    }
}

public class HttpFileDownloaderWithRetries : HttpFileDownloader
{
    private readonly int _retries;
    private readonly int _secondsBetweenRetries;

    public HttpFileDownloaderWithRetries(int retries, int secondsBetweenRetries)
    {
        _retries = retries;
        _secondsBetweenRetries = secondsBetweenRetries;
    }

    public override string Download(string url)
    {
        Exception lastException = null;
        for (int i = 0; i < _retries; i++)
        {
            try 
            { 
                return base.Download(url); 
            }
            catch (Exception ex) 
            { 
                lastException = ex; 
            }
            Utilities.WaitForXSeconds(_secondsBetweenRetries);
        }
        throw lastException;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...