As the discussion on the altdotnet list continued (some of it around my blog yesterday), it became apparent that some people didn't want to use an IoC container - but more confusing was that their objection to the whole pattern was that they thought the IoC container in here meant I was writing different code because I was using an IoC container. In fact nothing could be further from the truth - my service class would be identical in code, regardless of whether I used a container, or a factory, or any other construction on my object.
Let us look at the proposed service class again, a little expanded from the last example to show a bit more detail:
public class MyService : IMyService
{
private ILogger logger;
public ILogger Logger
{
get
{
if (logger == null) logger = NullLogger.Instance;
return logger;
}
set { logger = value; }
}
public void DoMyThing()
{
// Do something here
Logger.Debug("Hello");
}
}
public interface IMyService
{
void DoMyThing();
}
This service class is rather unimaginatively named, but has the basic parts in place. It has an optional ILogger property, and a method to DoMyThing(). One important thing to note is that the interface does not have an ILogger property.
So if we don't use an IoC container, we need some way to create instance of this class. The "old fashioned" way would use the new keyword, but this leads to all sorts of maintenance problems later on, and so we prefer to use some other kind of construction pattern, in this case a Factory, so here is our "application", albeit rather simple:
class Program
{
static void Main(string[] args)
{
IMyService service = MyServiceFactory.Create();
// Note that IMyService does not have a Logger property ...
service.DoMyThing();
}
}
The thing to note here is that we only refer to the MyService class via the IMyService interface, so our consuming application does not know the service has a Logger property, nor does it care. In the Castle Windsor version earlier the line MyServiceFactory.Create() would have been something like IoC.Resolve<IMyService>() - but apart from that the consumer of the service has no idea how the object is being constructed, and nor does it care.
For the sake of this example, here is some dummy code to represent our Logging classes. In the Castle Windsor case we use the ILogger in the Castle assemblies, we could also use that in this example, but would probably want to write our own if we didn't want to use any of the Castle code.
public class log4netLogger : ILogger
{
public void Debug(string message)
{
// Send to log4net here
}
}
public interface ILogger
{
void Debug(string message);
}
public class NullLogger
{
public static ILogger Instance;
}
Of course our ILogger will probably have a lot of other signatures defined, but this will do for now.
So now the only part of the equation missing is the actual factory we are going to use to replace the IoC container.
public static class MyServiceFactory
{
public static IMyService Create()
{
MyService service = new MyService();
service.Logger = LoggingServiceFactory.Create();
// Do other initialisation here
return service;
}
}
public static class LoggingServiceFactory
{
public static ILogger Create()
{
return new log4netLogger();
}
}
That is all there is to it. It may seem like a lot of code to do a simple thing, and it is in a way - I would really try to avoid writing all this when Castle Windsor or an other IoC container will do all this for me - but it preserves one very important principle - the MyService class is totally clean, it does not have any knowledge of how it was constructed, and it does not know how it gets a logging class, it just knows that it can try and log something, and if it was meant to log, then it will.
This is the principle of Seperation of Concerns at work - the class has only one concern, and it isn't how to create a logger object.