Decorator pattern with DI

Decorator pattern with DI

The decorator pattern is one of the things that is so simple to explain and so important and powerful to use that helps us keep our SOLID principles right!

If you have not heard of yet this is a basic explanation on it.

Lets have an interface call IDoWork which in our example will hold only one method DoWork.

public interface IDoWork
{
    void DoWork();
}

After implementing that interface in the target class we should have something like


public class HouseWork : IDoWork
{
    public void DoWork()
    {
        Console.WriteLine($"in {typeof(HousWork)} Doing work...");
    }
}

For simplicity we will only write a value in the console to show that we are in HouseWork class.

All good until now, but what if we wanted to add a simple logging inside the DoWork.

First approach would be to alter the class it self right away add some lines to log and we are good to go.

Sadly such way will break the open/close principle which says:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

 Bertrand Meyer wrote about it in 1988 in his book Object-Oriented Software Construction

Which means we can not alter the class as it closed now, so back to step one.

Lets recap what we want to do:

  • Add logging ability to the class without altering the base code or breaking client code that could be using our APIs.

Decorators

Lets see what the end code should look like and then explain it.


public interface IDoWork
{
    void DoWork();
}


public class HouseWork : IDoWork
{
    public void DoWork()
    {
        Console.WriteLine($"in {typeof(HouseWork)} Doing work...");
    }
}


public class LogDoWorkDecorator : IDoWork
{
    private readonly IDoWork decoratedInstance;
    public LogDoWorkDecorator(IDoWork decoratedInstance)
    {

        this.decoratedInstance = decoratedInstance;
    }

    public void DoWork()
    {
        Console.WriteLine("Log some data before do work");
        
        decoratedInstance.DoWork();
     
        Console.WriteLine("Log some data after do work");
    }
}

Now we have added a new class called LogDoWorkDecorator.

So first to stay we need to make the the decorator type of IDoWork so we could pass it as a correct implementation of (IDoWork) with out breaking any client side code.

Second we pass the decorated instance which is the instance we want to add keep its base functionality.

Third we simple write the logging logic and do a call the decorated class method.

And there we have it, we added new functionality with out altering the base classes and causing any need for new dependencies in the old code.

Adding decorator pattern with your DI (Dependency injection)

This one is a bit tricky as we can not simple just register the decorators.

Because as we saw in there constructer they require and instance of there own type (IDoWork) which would cause the container to throw a MemoryOutOfBoundException as it will keep trying to resolve the same instance over and over.

For that I have written a simple method that would do the job

Note: in here I am using Unity for my container and DI setup and wrapped it with IDiContainer interface.

 public void RegisterDecorator<T, Target>(params object[] args) where Target : T
        {
            if (!typeof(T).IsInterface)
                throw new ArgumentException("Invalid registeration of decorator T must be an interface");


            if (this.IsRegistered<T>())
            {
                //Get the registerd instance
                T registerdInstance = this.ResolveType<T>();

                T decoratorInstance = (T)Activator.CreateInstance(typeof(Target), args.Prepend(registerdInstance).ToArray());
                //Re register the decorator
                this.Register<T>(decoratorInstance);
            }
        }

For registering the decorators we need to do it in a bit more of a manual way, so I do depend here on the order of my registrations.

First get the last registered instance in our container.

Note: as per convention we always put the decorated instance as the first parameter in the decorator constructer and then pass any needed args.

After which we re register with the new target type!

so it would be called like this.

 container.RegisterDecorator<IRepository<User>, LoggingRepositoryDecorator<User>>();
//or could pass N args of paramters
container.RegisterDecorator<IRepository<User>, LoggingRepositoryDecorator<User>>(container.ResolveType<ILoggerService>()); 

So in this way we can add as many decorators we need they would always wrap what ever implementation was specified before them.

0
Adaptive Code Via C# (book-review) Adding Custom Configuration Sources

No Comments

No comments yet

Leave a Reply

Your email address will not be published.