Dependency inversion principle

Home Forums Programming Software engineering Object-oriented programming Dependency inversion principle

This topic contains 0 replies, has 1 voice, and was last updated by  Васильев Владимир Сергеевич 3 months, 1 week ago.

  • Author
    Posts
  • #4090

    This is an article on one of the five SOLID design principles, dependency inversion principle, based on low cohesion of types.

    The formulation of the dependency inversion principle consists of two rules that can have an extremely positive impact on the structure of your code:

    • top-level modules should not depend on low-level modules. Both should depend on abstractions.
    • abstractions should not depend on the details. Details should depend on abstractions.

    Initially it does not sound too appealing, and the reader probably already prepared for a tedious article with a bunch of terms, figures of speech and complex examples which still do not help much to understand the point of the topic. But this is far from the truth, because, in general, the point of the dependency inversion principle, reduces to the proper use of abstraction and polymorphism in the context of the basic idea – the reuse of code.

    Another concept that is important here – is the loose coupling of types, that means reducing or eliminating their dependency on each other, which in fact is achieved by means of abstraction, and polymorphism. That is the essence of the dependency inversion principle .

    And now let’s look at an example that will demonstrate what loose coupling looks like in action.

    Let’s assume, we decided to buy a birthday cake. In order to do this, we went to a wonderful bakery on the corner of the street. After we asked if they can make a cake under the high-sounding title “Luxury” for us and got a positive response, we ordered it. That’s all.

    Now, we shall decide on what abstractions it is necessary to provide in this code. To do this, we just ask ourselves a few questions:

    • Why cake? We can order muffins or a pie as well as the cake.
    • Why a birthday cake? And what if it was a wedding or a prom?
    • Why “Luxury”, what if I suddenly like the “Anthill” or “Prague”?
    • Why in this bakery, and not for example in a confectionery shop in the city center or anywhere else?

    And each of these “why” and “what if” is the point at which your code might require extensibility, and accordingly – the use of loose coupling of types through abstraction.

    Now let’s come closer to the constructing of types.

    We are defining the interface for any confectionary masterpiece that we could order.

    interface IPastry
    {
       string Name { get; set; }
    }

    And here is the specific implementation, according to our case – a birthday cake. As you can see, traditionally a birthday cake requires some candles )))

    class BirthdayCake: IPastry
    {
       public int NumberOfCandles { get; set; }
       public string Name { get; set; }
       public override string ToString()
       {
          return String.Format("{0} with {1} nice candles", Name, NumberOfCandles) ;
       }
    }

    Now, if we need a cake for a wedding or just for a tea, or if we want some muffins or profiteroles, we have a base interface for any of them.

    Now the next question is -what is the difference between all the confectionery (except for the name, of course). And the answer follows – the recipe!

    The concept of any recipe includes a list of ingredients and a description of the preparation process. Therefore, for any kind of ingredient we will have a common interface:

    interface IIngredient
    {
       string IngredientName { get;set;}
       double Quantity { get; set; }
       string Units { get; set; }
    }

    And here are the ingredients for out cake: flour, butter, sugar and creme:

    class Flour:IIngredient
    {
       public string IngredientName { get; set; }
       public double Quantity { get; set; }
       public string Units { get; set; }
       public string Quality { get; set; }
    }
    class Butter : IIngredient
    {
       public string IngredientName { get; set; }
       public double Quantity { get; set; }
       public string Units { get; set; }
    }
    class Sugar : IIngredient
    {
       public string IngredientName { get; set; }
       public double Quantity { get; set; }
       public string Units { get; set; }
       public string Kind { get; set; }
    }
    class Creme: IIngredient
    {
       public string IngredientName { get; set; }
       public double Quantity { get; set; }
       public string Units { get; set; }
       public double Fat { get; set; }
    }

    The list of ingredients may vary in different recipes and confections, but this list is enough for our recipe.

    And now it’s time to move on to the concept of a recipe. Whatever we cook, we know what it’s called, what it is, what ingredients are included in the dish and how to cook it.

    interface IRecipe
    {
       Type PastryType { get; set; }
       string Name { get;set;}
       IList<IIngredient> Ingredients { get;set;}
       string Description { get;set;}
    }

    A specific class representing a birthday cake recipe looks like this:

    class BirthdayCakeRecipe : IRecipe
    {
       public Type PastryType { get; set; }
       public string Name { get;set;}
       public IList<IIngredient> Ingredients { get; set; }
       public string Description { get; set; }
    
       public BirthdayCakeReipe()
       {
          Ingredients = new List<IIngredient>();
       }
    }

    Now we get back to our wonderful bakery on the corner of the street.

    Of course, we could apply to many other bakeries, so we define the basic interface for any of them. And what is most important for a bakery? The ability to bake of course ;)

    interface IBakery
    {
      IPastry Bake(IRecipe recipe);
    }

    And here is the class for our bakery:

    class NiceBakeryOnTheCornerOFMyStreet
    {
       Dictionary<string, IRecipe> menu = new Dictionary<string, IRecipe>();
    
       public void AddToMenu(IRecipe recipe)
       {
          if (!menu.ContainsKey(recipe.Name))
          {
             menu.Add(recipe.Name, recipe);
          }
          else
          {
             Console.WriteLine("It is already on the menu");
          }
       }
    
       public IRecipe FindInMenu(string name)
       {
           if (menu.ContainsKey(name))
          {
             return menu[name];
          }
             Console.WriteLine("Sorry...currently we don't have " + name);
             return null;
       }
       public IPastry Bake(IRecipe recipe)
       {
          if (recipe != null)
          {
             IPastry pastry = Activator.CreateInstance(recipe.PastryType) as IPastry;
             if (pastry != null)
             {
                pastry.Name = recipe.Name;
                return pastry as IPastry;
             }
         }
             return null;
       }
    }

    All we need to do now is to test our code:

    class Program
    {
       static void Main()
       {
          //creating an inctance of the bakery class
          var bakery = new NiceBakeryOnTheCornerOFMyStreet();
    
          //preparing ingredients for the recipe
          var flour = new Flour() { IngredientName = "Flour",
     Quantity = 1.5, Units = "kg" };
          var butter = new Butter() { IngredientName = 
    "Butter", Quantity = 0.5, Units = "kg" };
          var sugar = new Sugar() { IngredientName = "Sugar",
     Quantity = 0.7, Units = "kg" };
          var creme = new Creme() { IngredientName= "Creme", 
    Quantity = 1.0, Units = "liters" };
    
          //and this is the recipe itself
          var weddingCakeRecipe = new BirthdayCakeRecipe() { 
    PastryType = typeof( BirthdayCake), Name = "Birthday Cake Luxury", 
    Description = "description on how to make a beautiful birthday cake" };
          weddingCakeRecipe.Ingredients.Add(flour);
          weddingCakeRecipe.Ingredients.Add(butter);
          weddingCakeRecipe.Ingredients.Add(sugar);
          weddingCakeRecipe.Ingredients.Add(creme);
    
           //adding our cake recipe to the bakery's menu
          bakery.AddToMenu(weddingCakeRecipe);
    
          //now let's order it!!
          BirthdayCake cake = 
    bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) as BirthdayCake;
    
          //adding some candles ;)
          cake.NumberOfCandles = 10;
    
         //and here we are!!!
          Console.WriteLine(cake); 
       }
    }

    Now let’s look at all the code above once again and make a conclusion. A simple code structure, clearly delineated types, use of abstraction encapsulation data and functionality, the code provides extensibility and reusability. Each segment can safely be replaced, and it does not ruin the rest of the code.

    It is possible to add as many new types of ingredients, recipes for confectioneries, create other classes describing bakeries, pastry shops and other similar shops.

    We got a good result. And all thanks to the fact that we implemented the low cohesion between the types.

    And now let’s see what consequences we will have in the case of breaking the dependency inversion principle:

    1. First, stiffness (in it would be very difficuld to make some changes in code, because every change would affect many different parts of it).

    2. Second, fragility (if you make any changes to one part of the system, other parts become unstable , and sometimes it’s not too obvious at first glance).

    3. Third, immobility (the reuse of code in other systems becomes impossible, because the modules are tightly coupled).

    Well, now make your own conclusion on how the use of dependency inversion principle can be useful for your code. I think it is pretty obvious.

You must be logged in to reply to this topic.