首页 文章

使用IoC和依赖注入,如何在不违反Open-Closed原则的情况下使用新的实现层包装代码?

提问于
浏览
7

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

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

然后经过一些使用后,很明显有时服务器太忙而且抛出异常 . 我决定绕过这个,如果我得到异常,我将自动重试3次,并在每次重试之间等待5秒 .

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

所以现在我想要所有解析IFileDownloader来返回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;
  }
}

2 回答

  • 5

    如何直接从 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

    您或多或少地实现了 Circuit Breaker 设计模式 . 与使用DI实现 cross-cutting concerns 一样,关键是要应用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 Principlefavors composition over inheritance . 它也满足 Single Responsibility Principle 因为断路器只处理那个方面,而装饰的IFileDownloader集中于自己的责任 .

    大多数正确的DI容器都了解Decorator模式,因此您现在可以通过返回包含真实HttpFileDownloader的CircuitBreakingFileDownloader来配置容器以解析对IFileDownloader的请求 .

    事实上,这种方法可以推广到很多,你可以看一下通用的断路器 interceptor . Here's an example that uses Castle Windsor .

相关问题