I recently came across a very cool library, thanks to this video by Nick Chapsas. The library is Scrutor. In this post, I’m going to run through a version of the Open-Closed Principle that this makes possible.
An Overly Complex Hello World App
Let’s start by creating a needlessly complex app that prints Hello World. Instead of simply printing Hello World we’ll use DI to inject a service that prints it. Let’s start with the main program.cs code (in .Net 6):
using Microsoft.Extensions.DependencyInjection; using scrutortest; var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<ITestLogger, TestLogger>(); var serviceProvider = serviceCollection.BuildServiceProvider(); var testLogger = serviceProvider.GetRequiredService<ITestLogger>(); testLogger.Log("hello world");
Impressive, eh? Here’s the interface that we now rely on:
internal interface ITestLogger { public void Log(string message); }
And here is our TestLogger class:
internal class TestLogger : ITestLogger { public void Log(string message) { Console.WriteLine(message); } }
If you implement this, and run it, you’ll see that it works fine – almost as well as the one line version. However, let’s imagine that we now have a requirement to extend this class. After every message, we need to display —OVER— for… some reason.
Extending Our Overly Complex App to be Even More Pointless
There’s a few ways to do this: you can obviously just change the class itself, but that breaches the Open-Closed Principle. That’s where the Decorator Pattern comes in. Scrutor allows us to create a new class that looks like this:
internal class TestLoggerExtended : ITestLogger { private readonly ITestLogger _testLogger; public TestLoggerExtended(ITestLogger testLogger) { _testLogger = testLogger; } public void Log(string message) { _testLogger.Log(message); _testLogger.Log("---OVER---"); } }
There’s a few things of note here: firstly, we’re implementing the same interface as the main / first class; secondly, we’re Injecting said interface into our constructor; and finally, in the Log method, we’re calling the original class. Obviously, if you just register this in the DI container as normal, bad things will happen; so we use the Scrutor Decorate method:
using Microsoft.Extensions.DependencyInjection; using scrutortest; var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<ITestLogger, TestLogger>(); serviceCollection.Decorate<ITestLogger, TestLoggerExtended>(); var serviceProvider = serviceCollection.BuildServiceProvider(); var testLogger = serviceProvider.GetRequiredService<ITestLogger>(); testLogger.Log("hello world");
If you now run this, you’ll see that the functionality is very similar to inheritance, but you haven’t coupled the two services directly: