Using IoC and Injection Dependency Injection, how to wrap code with a new level of implementation without violating the Open-Closed principle?

I am trying to understand how this will be done in practice so as not to violate the principle of Open Closed.

Say I have an HttpFileDownloader class that has one function that takes a url and loads a file that returns html as a string. This class implements the IFileDownloader interface, which has only one function. So throughout my code, I have references to the IFileDownloader interface, and I have an IoC container that returns an HttpFileDownloader instance whenever IFileDownloader is allowed.

Then, after some use, it becomes clear that sometimes the server is too busy at that time and an exception is thrown. I decide that in order to get around this, I am going to automatically retry 3 times if I get an exception and wait 5 seconds between each attempt.

So, I create an HttpFileDownloaderRetrier, which has one function that uses an HttpFileDownloader in a for loop with max 3 loops and 5 seconds of waiting between each loop. So that I can test the ability to "repeat" and "wait" for the HttpFileDownloadRetrier, I have the HttpFileDownloader dependency introduced by creating the HttpFileDownloaderRetrier constructor using IFileDownloader.

So now I want all IFileDownloader permission to return HttpFileDownloaderRetrier. But if I do, then the HttpFileDownloadRetrier IFileDownloader dependency will receive an instance of itself, not the HttpFileDownloader.

So, I see that I can create a new interface for the HttpFileDownloader called IFileDownloaderNoRetry and change the HttpFileDownloader to implement it. But that means that I am modifying the HttpFileDownloader, which violates Open Closed.

Or I could implement a new interface for the HttpFileDownloaderRetrier called IFileDownloaderRetrier, and then change all my other code to reference it, not IFileDownloader. But then again, now I'm breaking Open Closed in all my other code.

So what am I missing here? How to transfer an existing implementation (loading) with a new level of implementation (retrying and waiting) without changing the existing code?

Here is some code if it helps:

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
c # dependency-injection ioc-container open-closed-principle
source share
2 answers

You more or less use the Circuit Breaker design template. As always, when it comes to implementing cross-cutting issues using DI, the key is to apply the Decorator pattern.

Write the CircuitBreakingFileDownloader program as follows:

 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... } } 

This approach follows the Open / Closed Principle and supports composition over inheritance . It also satisfies the Single Responsibility Principle , because Circuit Breaker deals only with this aspect, while the issued IFileDownloader focuses on its own responsibility.

The most correct DI containers understand the Decorator pattern, so now you can configure your container to allow the request for IFileDownloader by returning CircuitBreakingFileDownloader, which contains the real HttpFileDownloader.

In fact, this approach can be so generalized that you can study the interceptor . Here is an example that uses Castle Windsor .

+5
source share

How to get directly from 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
source share

All Articles