Saturday 21 May 2016

Dependency Inversion Principle: Part 5 - Using DIP/IoC container

(Note: you should read Part4 - Refactoring Using DIP before in order to understand this section)

I can just stop after refactoring code in previous section. I don't really need to use a DIP/IoC container. However most containers will have some additional features which enable us to tidy up the class composition code. You can even write your own IoC container with the features you really need.

Most containers will have some kind of registers, where you can register abstractions(interfaces, abstract classes, or just normal classes), and their implementations. However, you would normally interact with the container so you can utilize its features.

In this section, I will use Simple Injector 3.1.4.0 as an example. Using this container I will refactor the class composition code from previous section. I will use type discovery or auto wiring feature from Simple Injector.
        internal static WebAnalytic CreateCompositionRoot()
        {
            string logFilePath = @"D:\log.txt";
            string webAddress = @"http://www.google.com";

            SimpleInjector.Container container = new SimpleInjector.Container();   
          
            container.Register<IWriterStream>(()=> new FileWriterStream(logFilePath));
            container.Register<IWebText>(() => new WebText(webAddress));
            container.Register<IConsole, ConsoleTerminal>();
            container.Register<ILogger, FileLogger>(SimpleInjector.Lifestyle.Singleton);
            container.Register<IScanner, TextScanner>();
            container.Register<WebAnalytic, WebAnalytic>();
            
            return container.GetInstance<WebAnalytic>();
        }
Firstly, I create a container, and then register all interfaces and their implementations. For IWriterStream and IWebText, the implementations are anonymous delegates, because we need to pass the string parameters, logFilePath and webAddress. I used those delegates to return FileWriterSteream and WebText respectively.

For IConsole, ILogger and IScanner, I register the implementation classes. And for ILogger I specifically marked the implementation lifestyle as singleton. One thing to notice that, you don't need to pass dependencies for FileLogger or TextScanner. The container will automatically resolve the dependencies based on the types you have registered. This feature is called type discovery or auto wiring. For example FileLogger has a dependency on IWriterStream, which will be automatically resolved to FileWriterStream, and TextScanner has a dependency on ILogger, which will be resolved to FileLogger.

Lastly, I self register the WebAnalytic. The container does not only register interfaces, but also normal classes. So every time we query the container for this class, like above return statement, it will create WebAnalytic instance by resolving all the dependencies first.

[Update]
You can get rid of the need to use lambda in above case, by abstracting the string or any primitive types into classes instead. For example instead of passing 'webAddress' and 'logFilePath', you can pass an implementation of interface called IValueProvider

        internal interface IValueProvider
        {
             string WebAdress { get; }
             string LogFilePath {get;}
        }

        internal class ConfigValue : IValueProvider
        {
            public string WebAdress 
            {
                 get
                {
                   return @"http://www.google.com";
                }
            }
          
            public string LogFilePath
            {
                get
                {
                   return @"D:\log.txt";
                }
            }
        } 

And now you can pass the implementation of the interface into the container instead

        internal static WebAnalytic CreateCompositionRoot()
        {
            SimpleInjector.Container container = new SimpleInjector.Container();   

            container.Register<IValueProvider, ConfigValue>();
            container.Register<IWriterStream, FileWriterStream>();
            container.Register<IWebText, WebText>();
            container.Register<IConsole, ConsoleTerminal>();
            container.Register<ILogger, FileLogger>(SimpleInjector.Lifestyle.Singleton);
            container.Register<IScanner, TextScanner>();
            container.Register<WebAnalytic, WebAnalytic>();
            
            return container.GetInstance<WebAnalytic>();
        }
Of course you will need to modify the implementation of FileWriterStream and WebText accordingly.

In this section, I only discuss briefly on the IoC container features. The code is also very simple and cannot display the rich features of IoC container. You can just choose any Ioc frameworks and learn the features they have and the problems they are trying to solve. For the purpose of learning DIP, what I have discussed so far should be enough.

Final remarks

All the source code for previous (Refactoring Using DIP) and this section (Using DIP/IoC Container) can be download from Github.

All the materials I discuss in this topic are from my coding experience, reading question-answer on Stack Overflow, blogs and articles I could find on the internet. My understanding on this topic may flaw and I am open to correction. My writing style sometimes can be prescriptive, which is not my intention. You can do whatever you want once you grasp the concept behind it, or you may disagree with my understanding.

There are some blog entries which give me some insights, so I will put it down here.
  1. Dot net trick - Inversion of Control Practical Usage
  2. Dot net trick - Dependency Injection IOC

No comments:

Post a Comment