Используя МОК и Внедрение зависимости, как перенести код с новым слоем реализации, не нарушая Открыто закрытый принцип?

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

Скажите, что у меня есть класс под названием HttpFileDownloader, который имеет одну функцию, которая берет URL и загружает файл, возвращая HTML как строку. Этот класс реализует интерфейс IFileDownloader, который просто имеет одну функцию. Таким образом на всем протяжении моего кода у меня есть ссылки на интерфейс IFileDownloader, и у меня есть свой контейнер МОК возврат экземпляра HttpFileDownloader каждый раз, когда IFileDownloader Разрешен.

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

Таким образом, я создаю HttpFileDownloaderRetrier, который имеет одну функцию, которая использует HttpFileDownloader в для цикла с макс. 3 циклами и 5-секундное ожидание между каждым циклом. Так, чтобы я мог протестировать "повторную попытку" и "ожидать" способности HttpFileDownloadRetrier, мне ввели зависимость HttpFileDownloader при наличии конструктора HttpFileDownloaderRetrier, берут IFileDownloader.

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

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

Или я мог реализовать новый интерфейс для HttpFileDownloaderRetrier по имени IFileDownloaderRetrier и затем изменить весь мой другой код для обращения к этому вместо IFileDownloader. Но снова, я теперь нарушаю, Открывают 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;
  }
}
7
задан Laurel 29 March 2016 в 03:14
поделиться

2 ответа

Вы более или менее реализуют шаблон проектирования Автоматический выключатель . Как всегда, когда дело доходит до реализации сквозных задач с использованием 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...
    }
} 

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

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

Фактически, этот подход может быть настолько обобщен, что вы можете рассмотреть универсальный перехватчик Circuit Breaker . Вот пример, использующий Castle Windsor .

5
ответ дан 7 December 2019 в 05:20
поделиться

Как насчет получения непосредственно от 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;
    }
}
3
ответ дан 7 December 2019 в 05:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: