7

我试图弄清楚在实践中如何做到这一点,以免违反开放封闭原则。

假设我有一个名为 HttpFileDownloader 的类,它有一个函数,它接受一个 url 并下载一个将 html 作为字符串返回的文件。这个类实现了一个只有一个函数的 IFileDownloader 接口。因此,在我的整个代码中,我都引用了 IFileDownloader 接口,并且每当 IFileDownloader 被解析时,我的 IoC 容器都会返回一个 HttpFileDownloader 实例。

然后经过一段时间的使用,发现偶尔服务器太忙,抛出异常。我决定要解决这个问题,如果我遇到异常,我将自动重试 3 次,并在每次重试之间等待 5 秒。

所以我创建了 HttpFileDownloaderRetrier,它有一个函数在一个 for 循环中使用 HttpFileDownloader,最多 3 个循环,每个循环之间等待 5 秒。为了测试 HttpFileDownloadRetrier 的“重试”和“等待”能力,我通过让 HttpFileDownloaderRetrier 构造函数采用 IFileDownloader 来注入 HttpFileDownloader 依赖项。

所以现在我希望 IFileDownloader 的所有 Resolving 都返回 HttpFileDownloaderRetrier。但是如果我这样做,那么 HttpFileDownloadRetrier 的 IFileDownloader 依赖项将获得一个自身的实例,而不是 HttpFileDownloader 的一个实例。

所以我可以看到我可以为 HttpFileDownloader 创建一个名为 IFileDownloaderNoRetry 的新接口,并更改 HttpFileDownloader 来实现它。但这意味着我正在更改违反 Open Closed 的 HttpFileDownloader。

或者我可以为 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;
  }
}
4

2 回答 2

5

您或多或少地实现了断路器设计模式。与往常一样,在使用 DI 实现横切关注点时,关键是应用装饰器模式。

像这样编写 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 容器都了解装饰器模式,因此您现在可以配置容器以通过返回包含真实 HttpFileDownloader 的 CircuitBreakingFileDownloader 来解析对 IFileDownloader 的请求。

事实上,这种方法可以推广到如此之多,以至于您可以研究通用的断路器拦截器这是一个使用 Castle Windsor 的示例

于 2010-03-20T15:57:18.297 回答
3

直接从以下派生如何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;
    }
}
于 2010-03-20T15:36:07.673 回答