Navigation and service panel


Content

Dependency Injection sets you free from Conditional Statements

By Dawid Dworak on 10. September 2014, No comments

Conditional statements have been present to developers for nearly as long as the programming itself. Unfortunately in the world of OOP they are very often abused even by most exceptional developers and thus increase complexity of created applications. There are number of campaigns all over the world that raise awareness of effective use of software design principles and practices first of all by removing bad IFs and replacing them with alternative constructs. Dependency Injection make it possible to enhance the maintainability of code and let developers stay away from overwhelming amount of conditional statements.

"I believe that if you show people the problems and you show them the solutions they will be moved to act." ~Bill Gates

Problem

It’s a very common scenario developing Sitecore solutions that you want to assign some general value to a property, but only for some special types of items to overwrite it by some other value. The size and complexity of the method increases with time as everybody in the team adds his or her own IF statements and makes the code more and more difficult to understand, debug or extend. After a number of weeks you end up with the piece of code similar to the following.

To visualize the problem I used a clean Sitecore installation with the Glass Mapper installed and a number of domain models. The method presented below is responsible for getting textual characteristic of an animal. In general the characteristic consists of a short text and an overridden ToString() method. For a number of animal types the logic for building the characteristic is special – it depends on the context of the type of animal:

public class AnimalService : IAnimalService
{
    public string GetAnimalCharacteristic(Animal animal)
    {
        var animalCharacteristic = "This is general charactersitic of an animal" + animal.ToString();

        if (animal is Cat)
        {
            const string CatDescription = "Cat is very special. It has very good ";
            var catBestSense = CatUtil.GetCatBestSense();
            animalCharacteristic = CatDescription + catBestSense + animal.ToString();
        }
        else if (animal is Horse)
        {
            const string HorseDescription = "Horse is very special. It has very good ";
            var horseBestSense = HorseUtil.GetHorseStrongestSense();
            animalCharacteristic = HorseDescription + horseBestSense + animal.ToString();
        }
        else if (animal is Dog)
        {
            const string DogDescription = "Dog is very special. It has very good ";
            var dogBestSense = DogUtil.GetBestSense();
            animalCharacteristic = DogDescription + dogBestSense + animal.ToString();
        }

        return animalCharacteristic;
    }
}

It’s clear that the more animal types there are, the more of them might be special and the more conditional statements are created. The code becomes less and less readable, maintainable and harder to extend with time. Fortunately there is a good alternative that solves this problem.

Solution

The technique that helps with the problem above is called contextual binding. It’s one of the most powerful features that latest versions of the popular dependency injection frameworks like NInject, Autofac or Unity have to offer. In general this technique requires registration of multiple type bindings for the same service type and adding binding metadata, so that it is possible to indicate which one is appropriate.

The process consists of the following steps:

  • Create an interface and declare a method that returns a required type of value
  • Create context specific classes and make them implement the interface
  • Register the interface, the classes and its metadata with selected DI framework
  • Try to resolve the type based on the metadata and if it exists, call the method to get the value

Some Code

Let’s refactor the GetAnimalCharacteristic method and make use of the contextual binding technique. Unfortunately not every DI framework supports it, for instance Castle Windsor, so I limited the examples to NInject. First of all new interface and method responsible for the characteristic of special animals should be created.

public interface IAnimalCharacteristicResolver
{
    string GetCharacteristic(Animal animal);
}

There are the classes responsible for the characteristic of cats, horses and dogs:

public class CatCharacteristicResolver : IAnimalCharacteristicResolver
{
    public string GetCharacteristic(Animal animal)
    {
        const string CatDescription = "Cat is very special. It has very good ";
        var catBestSense = CatUtil.GetCatBestSense();
        return CatDescription + catBestSense + animal.ToString();
    }
}

public class HorseCharacteristicResolver : IAnimalCharacteristicResolver
{
    public string GetCharacteristic(Animal animal)
    {
        const string HorseDescription = "Horse is very special. It has very good ";
        var horseBestSense = HorseUtil.GetHorseStrongestSense();
        return HorseDescription + horseBestSense + animal.ToString();
    }
}

public class DogCharacteristicResolver : IAnimalCharacteristicResolver
{
    public string GetCharacteristic(Animal animal)
    {
        const string DogDescription = "Dog is very special. It has very good ";
        var dogBestSense = DogUtil.GetBestSense();
        return DogDescription + dogBestSense + animal.ToString();
    }
}

Depending on the DI framework registration entries look a little bit different.

NInject:

In order to register the interfaces, classes and metadata with NInject known for its huge capabilities:

// Animal Characteristic Resolvers
this.Bind<IAnimalCharacteristicResolver>().To<CatCharacteristicResolver>().Named(typeof(Cat).FullName);
this.Bind<IAnimalCharacteristicResolver>().To<HorseCharacteristicResolver>().Named(typeof(Horse).FullName);
this.Bind<IAnimalCharacteristicResolver>().To<DogCharacteristicResolver>().Named(typeof(Dog).FullName);

The last step is refactoring the original GetAnimalCharacteristic method by trying to resolve the IAnimalCharacteristicResolver depending on the type of animal and calling the GetCharactersitic method:

public string GetAnimalCharacteristic(Animal animal)
{
        var animalCharacteristic = "This is general charactersitic of an animal" + animal.ToString();

        var characteristicResolver = Container.TryGet<IAnimalCharacteristicResolver>(animal.GetType().FullName);

        if (characteristicResolver != null)
        {
            animalCharacteristic = characteristicResolver.GetCharacteristic(animal);
        }

        return animalCharacteristic;
}

Summary

Contextual binding in many cases makes the code more flexible, maintainable and extensible. There is no need to modify the refactored method anymore and thus the better separation of concerns is gained. Of course there are situations where using the technique is like taking a sledgehammer to crack a nut and it would be better to use the IF statement or some other technique like polymorphism but it's beneficial to be aware of it and enhance one's programming toolbox in order to improve the quality of created code.

If you have any comments, questions or ideas feel free to ask. All feedback is much appreciated.

No comments

Add your comment

Your email address will not be published. Required fields are marked *

*