我试图弄清楚如何在实践中做到这一点,以免违反开放封闭原则。
假设我有一个名为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来实现它。但这意味着我正在改变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;
}
}
答案 0 :(得分:5)
您或多或少地实施断路器设计模式。与使用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模式,因此您现在可以通过返回包含真实HttpFileDownloader的CircuitBreakingFileDownloader来配置容器以解析对IFileDownloader的请求。
事实上,这种方法可以推广到很多,你可以看一下通用的断路器拦截器。 Here's an example that uses Castle Windsor
答案 1 :(得分: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;
}
}