我试图弄清楚在实践中如何做到这一点,以免违反开放封闭原则。
假设我有一个名为 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;
}
}