Constructor Injection Alternatives (Castle Windsor)
Asked Answered
S

3

6

I like Constructor injection for dependency injection. It forces a clear declaration of concerns from a type and helps with testability.

I like Constructor injection, in most places...

Logging as an example where I do not like it. If I have a base class from which many other classes inherit, and I want all of those classes to use an instance of my ILogger (or whatever), and I don't want a static factory (Logger.Instance)...I don't want to have to declare a constructor on every sub-class that takes an ILogger.

So, I could have my base class declare the logger as a Property and have it injected that way

public class MyBaseClass 
{
   public ILogger Logger { get; set; }
}

...but

  1. That doesn't assure me that Logger actually gets injected and is not null.
  2. I don't like having ILogger with a public set

So...what other options do I have? (I'm using Castle Windsor).

I've contemplated making an interface

public interface IInitializable<T>
{
    void Initialize(T instance); 
}

public class MyBaseClass : IInitializable<ILogger>, ...could have other IInitializables too...
{
   protected ILogger Logger { get; private set; }

   public void Initialize(ILogger instance) 
   { 
         Logger = instance;
   }
}

Then having a facility on my container that automatically calls all implementations of IInitializable<T> upon type construction...

But I'm wondering what other peoples' thoughts are before I go that route...

Shaunta answered 9/12, 2010 at 17:59 Comment(4)
Why do you not want to use a static factory pattern?Rollet
Ctor injection, etc, is very useful for varying implementations for testing, etc, on an individual basis, but do you really need this for logging? Do you really need the ability to vary logging implementation at the instance level? The static factory pattern will still give you the ability to vary the overall logging implementation which is usually what is required.Rollet
The implementation of what Logger to return needs to be dynamic. Specifically Logger.Instance should be different depending on the context (ie WCF operation, WPF client, SL client, etc). I guess I could do a Service Locator inside .Current, but that's kind of an anti-pattern as I understand it...Shaunta
It would appear you need to vary the logging according to the runtime environment (e.g. WPF Client, SL Client, etc) rather than at the level of type instances. I think a static factory used in conjunction with DI at this point fits the bill nicely.Rollet
M
1

In your case i would use property injection.

Property injection can be switched to mandatory as explained here: http://www.mail-archive.com/[email protected]/msg08163.html

Muricate answered 9/12, 2010 at 18:44 Comment(1)
Any suggestions on how to prevent modification?Shaunta
S
3

You're overcomplicating this. The recommended and documented pattern to inject ILogger is to have NullLogger.Instance as default (i.e. a null object pattern) and make the Logger an optional dependency. There is nothing wrong with having a public setter for the logger. Using a custom IInitializable like the one you show will likely only complicate things and not contribute any real value.

I'll copy the sample from the documentation here for easy reference:

using Castle.Core.Logging;

public class CustomerService
{
   private ILogger logger = NullLogger.Instance;

   public CustomerService()
   {
   }

   public ILogger Logger
   {
      get { return logger; }
      set { logger = value; }
   }

   // ...
}

EDIT: it seems the question is actually about having different logger implementations depending on the context (which has little to do with the original question). If that's the case, use service overrides or handler selectors.

Selfrespect answered 9/12, 2010 at 19:20 Comment(6)
Any suggestions on how to prevent modification? What if I want inheriting classes to be able leverage a service but not change it's implementation?Shaunta
@JeffN825: why would anyone modify it in the first place?Selfrespect
@JeffN825: BTW you can't really have it both ways. It's either a private readonly and constructor injection, or a mutable property. There is no other option in .net that I know of.Selfrespect
The base class should be able to modify it, so it doesn't need to be readonly (ie public ILogger MyLogger { get; private set; } )Shaunta
It's not that they would want to modify it, but rather that this is an API and I don't want to let them modify it.Shaunta
Agreed on the "why would you worry about the logger being changed" points -- but just from a simple standpoint -- yes, it's easy to prevent changes -- in the setter, check if the value == NullLogger.Instance before performing the field assignment -- additionally you can throw an InvalidOperationException if you want to do more than just silently ignore the set.Ciaracibber
M
1

In your case i would use property injection.

Property injection can be switched to mandatory as explained here: http://www.mail-archive.com/[email protected]/msg08163.html

Muricate answered 9/12, 2010 at 18:44 Comment(1)
Any suggestions on how to prevent modification?Shaunta
T
0

If your base class itself is not depending on the ILogger, you should remove the property from the base class. This way you keep the base class constructor clean.

Otherwise you can create a factory that is able to create descendants of MyBaseClass. This might look like this:

public interface IMyBaseClassFactory
{
    MyBaseClass CreateNew<TMyBaseClass>() where TMyBaseClass : MyBaseClass;
}

Now you can create an register an implementation of IMyBaseClassFactory that is able to create new instances and register optional dependencies:

public MyBaseClassFactoryImpl : IMyBaseClassFactory
{
    public MyBaseClass CreateNew<TMyBaseClass>()
    {
        // Call your IoC container to get a descendant.
        var instance = ServiceLocator.GetInstance<TMyBaseClass>();

        // Register missing dependencies
        instance.Logger = ServiceLocator.GetInstance<ILogger>();

        return instance;
    }
}

Most IoC containers allow you to decorate injectable properties with attributes. The advantage of using a factory however is, that your application will stay completely ignorant of the used IoC framework. As a downside, there's more work to do (creating a factory, injecting that factory instead of the instance itself, and calling the factory to create a new instance).

When defining a injectable property, you need to make it read/write. Of course you don't want anyone to accidentally reset that property afterwards. Without constructor injection it is hard to achieve this with compile time support. However, a runtime check is easily made:

private ILogger logger;

public ILogger Logger
{
    get { return this.logger; }
    set
    {
        if (this.logger != null)
        {
            throw new InvalidOperationException("Logger has already been set.");
        }

        this.logger = value;
    }
}

I hope this helps.

Taritariff answered 10/12, 2010 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.