Design patterns: Dependency Injection and its variations

in #design-patterns6 years ago

syringe.jpg

Today, we will be talk about two design patterns, both are similar to the last solid principle, dependency inversion, in the sense that both are about the same problem, namely creating an instance of an object within a class, but they differ from themselves, more precisely about these patterns later in the article.

Both patterns relate to the dependencies between classes, I will use the example of the store in which we will create classes instances of the store parts, that is, roof, floor, walls. Dependency Injection is a really important topic because it relates to the dependencies between classes, so they are related with the last SOLID Dependency Inversion principle.

dependency-injection-yeah.jpg

The problem of dependencies between classes

Let’s use the example of the store, let’s build it, but now in a different way not using the builder:

class Shop
{
    private IRoof roof;
    private IFloor floor;
    private IWall wall;
 
    public Shop()
    {
        this.roof = new Roof();
        this.floor = new Floor();
        this.wall = new Wall();
    }
 
    public void BuildRoof()
    {
        roof.SetHeightRoof(3);
    }
 
    public void BuildWall()
    {
        wall.SetColorWall("Red");
        wall.SetGageWall(1);
    }
 
    public void BuildFloor()
    {
        floor.SetLenghtFloor(10);
        floor.SetNumberOfTilesOnFloor(100);
        floor.SetTypeGreatnessTiles("Medium");
    }
 
    public void BuildShop()
    {
        BuildRoof();
        BuildWall();
        BuildFloor();
    }
}

And call it:

static void Main(string[] args)
{
     Shop shop = new Shop();
     shop.BuildShop();
}

Apparently everything is okay, in fact, it results from this simple example of a few problems:

  1. The Shop class is responsible for creating the Floor, Wall and Roof classes instances, it is hard-coded dependencies, if we would to modify name the Floor class on Floors, we will also have to change the Shop class code and this is a breach of the open-closed principle.
  2. If we would to add some other parameter to the Wall class, eg wall height, we will not only have to modify the Wall class, but also the Shop class.
  3. If these three classes Floor, Wall and Roof will have any dependencies from other classes, then the Shop class will also need to know about them and to pass them.
  4. Testing of the application is difficult, while the functions of the Floor, Wall and Roof classes are run on their real instances during the tests, you would have to intercept those calls somewhere at a higher level because there is no easy way to substitute dependencies for testing time.

Such a simple Shop class and so many problems, everything breaks the open-closed principle, the patterns that will be discussed are responsible to keep this principle. First is the Inversion of control design pattern.

Inversion of control

Just to not confuse Dependency Injection with Inversion of Control. The principle of Inversion of Control tells how classes should depend from themselves, more precisely it consists in transferring the program execution control function to the framework used, hence its name Inversion of control will be explained more directly, and Dependency Injection is just one example of the implementation of the Inversion of Control principle.

Often humorously, the Inversion of Control principle is described in this way:

Don’t call us, we’ll call you.

Speaking more clearly in a classic application, the programmer code invokes functions from external libraries, whereas in the IoC approach this external framework calls the programmer’s code.

I will give the simplest example without IoC:

Console.WriteLine("Enter the password");
string password = Console.ReadLine();
string ValidatePassword = CheckPassword(password);

In this example, the flow of information is completely dependent from the programmer. And now an example using IoC:

var data = new Data();
data.Password = password;
data.CheckPassword();

We see the difference, now we do not have control over this program, it calls our code itself, of course we entered these functions there, but it does not depend on us when they will be used. These are trivial examples, but the point is to understand the ideas of IoC. In IoC, this framework calls our code, not us. Hence the name Inversion of control.

Also calling the building function of our store is an example of IoC, of course this is not IoC in one hundred percent, because further in the shop class modules are dependent on each other, but this program calls our class. It is not up to us when something will happen.

Shop shop = new Shop();
shop.BuildShop();

Dependency Injection

We will now implement IoC in our store example with Dependency Injection. Only that the control will be reversed in the area of relationship between objects.

Implementation of Dependency Injection is so common sometimes even used unconsciously so it is common to use these concepts of Dependency Injection and Inversion of Control interchangeably which is not correct.

There are several ways to implement the Dependency Injection pattern:

  1. Constructor Injection
  2. Setter Injection
  3. Interface Injection

Constructor Injection

This method involves manually creating an instance and passing it to the constructor. In this way:

class Shop
{
    private IRoof roof;
    private IFloor floor;
    private IWall wall;
 
    public Shop(IFloor floor,IRoof roof,IWall wall)
    {
         this.roof = roof;
         this.floor = floor;
         this.wall = wall;
    }
}

static void Main(string[] args)
{
    Floor floor = new Floor();
    Roof roof = new Roof();
    Wall wall = new Wall();
 
    Shop shop = new Shop(floor, roof, wall);
    shop.BuildShop();
 
    Console.ReadKey();
}

Setter Injection

Assumptions similar to Constructor Injection, only done in a slightly different way, first we create a store instance, and only then we set dependencies on the created object:

class Shop
{
    public IRoof roof;
 
    public IFloor floor;
 
    public IWall wall;
 
    public Shop()
    {}
 
    //other methods
}

static void Main(string[] args)
{
     Shop shop = new Shop();
     shop.floor = new Floor();
     shop.roof = new Roof();
     shop.wall = new Wall();
 
     Console.ReadKey();
}

Interface Injection

The principle of operation is the same as in the previous example only through interfaces, it is best to see the example:

interface IDependency
{
    void InjectDependencyFloor(IFloor floor);
 
    void InjectDependencyRoof(IRoof roof);
 
    void InjectDependencyWall(IWall wall);
}
 
class Shop: IDependency
{
    public IRoof roof;
 
    public IFloor floor;
 
    public IWall wall;
 
    public void InjectDependencyFloor(IFloor floor)
    {
        this.floor = floor;
    }
 
    public void InjectDependencyRoof(IRoof roof)
    {
        this.roof = roof;
    }
 
    public void InjectDependencyWall(IWall wall)
    {
        this.wall = wall;
    }
 
    //other methods
}
 
static void Main(string[] args)
{
    Shop shop = new Shop();
    shop.InjectDependencyFloor(new Floor());
    shop.InjectDependencyRoof(new Roof());
    shop.InjectDependencyWall(new Wall());
 
    Console.ReadKey();
}

We create the IDependency interface with the appropriate methods that have variables with the interface types of individual parts of the store, we implement its methods in the Shop class and finally set the dependencies in the Main function.

Service Locator

A similar pattern to Dependency Injection is Service Locator, it consists in downloading dependencies when they are needed. In javascript you have to download dependencies depending on their names, but in C # there are extended types mechanisms, we will create our own container and in it we will register the classes that are in the project.

First, let’s see what the Shop class looks like with the Service Locator pattern implemented:

class Shop
{
    public IRoof roof;
    public IFloor floor;
    public IWall wall;
 
    public Shop()
    {
        RegisterTypeInterface();
 
        floor = ServiceLocator.Resolve<IFloor>();
        roof = ServiceLocator.Resolve<IRoof>();
        wall = ServiceLocator.Resolve<IWall>();
    }
 
    public void RegisterTypeInterface()
    {
        ServiceLocator.Register<IFloor, Floor>();
        ServiceLocator.Register<IRoof, Roof>();
        ServiceLocator.Register<IWall, Wall>();
    }
 
    //other methods
}

In the RegisterTypeInterface method, we only inform what classes have interfaces and in the constructor, we already write a specific instance of classes to interfaces, we can say that:

floor = ServiceLocator.Resolve<IFloor>();
roof = ServiceLocator.Resolve<IRoof>();
wall = ServiceLocator.Resolve<IWall>();

is equal to:

floor = new Floor();
roof = new Roof();
wall = new Wall();

However, what happens inside the Resolve and Register methods? From this we have our container:

public static class ServiceLocator
{
    readonly static Dictionary<Type, Type>
    services = new Dictionary<Type, Type>();
 
    public static void Register<TRegister ,TImplement >()
    {
        services.Add(typeof(TRegister), typeof(TImplement));
    }
 
    public static T Resolve<T>()
    {
        Type type = services[typeof(T)];
 
        return (T)Activator.CreateInstance(type);
    }
}

At first glance, it may look horribly twisted and complicated, but calmly 🙂 It is not that at all 🙂

In the generic Register method we give first the interface type and then the class to which this interface belongs, then using typeof key words we write these types to the dictionary, it is thanks to this dictionary after entered only the interface in the Resolve method, the compiler knows which instances should be created . If you do not remember how a dictionary works, I’ve made two articles about it in the C# language section.

Of course, the container can be used in various types of dependency injection, mainly so that you do not have to letter all of the dependencies after comma if they were, for example, 15 in the constructor, or not would have to write multi-level dependencies. In the Service Locator pattern, the container methods are static, but normally these are normal methods. Registering dependencies in a regular container in a normal the Dependency Injection would look something like this:

ServiceLocator servicelocator= new ServiceLocator();
servicelocator.Register<IFloor, Floor>();
servicelocator.Register<IRoof, Roof>();
servicelocator.Register<IWall, Wall>();

Of course Dependency Injection and Service Locator are not mutually exclusive, you can combine them both, then we will have to modify the Shop class a bit:

class Shop
{
    public IRoof roof;
    public IFloor floor;
    public IWall wall;
 
    public Shop(IFloor floor, IRoof roof, IWall wall)
    {
        this.floor = floor;
        this.roof = roof;
        this.wall = wall;
    }
 
    public static void RegisterTypeInterface()
    {
        ServiceLocator.Register<IFloor, Floor>();
        ServiceLocator.Register<IRoof, Roof>();
        ServiceLocator.Register<IWall, Wall>();
    }
 
    //other methods
}

And its calling:

static void Main(string[] args)
{
     Shop.RegisterTypeInterface();
 
     IFloor floor = ServiceLocator.Resolve<IFloor>();
     IRoof roof = ServiceLocator.Resolve<IRoof>();
     IWall wall = ServiceLocator.Resolve<IWall>();
 
     Shop shop = new Shop(floor, roof, wall);
     shop.BuildShop();
     Console.WriteLine(shop.wall.GageWall);
 
     Console.ReadKey();
}

And one very important remark at the end, with the Service Locator is like with Singleton, is considered as an anti-pattern by many people and in a sense they are right, abusing it kills the whole project, because in the Service locator it’s about that the container was available in the whole project so its class must be static, and static methods are much more difficult to test, and the design is less readable because the global access to the container gets messy, so you have to be careful with this pattern.

However, it is usually not a good idea to combine Dependency Injection with Service Locator, because it breaks the consistency of the project. In general, using the Service Locator pattern is not a good idea, although it is worth knowing, because just like singleton it is useful in some cases. So when you use Service Locator in a project in the right place, if your colleague looks at all your code, then his reaction does not have to be the same as the reaction this Mr below. 🙂

boglindaeng.jpg

Problems with DI

The Dependency Injection pattern, like other patterns, if it is abused, then the effects may be counterproductive, the code will become more difficult to manage and expand and read. As Uncle bob once wrote:

IoC is a good idea. And like all good ideas, it should be used with moderation.

Big constructors

What if we meet with such a constructor below or write it ourselves?

public Shop(IDependency1 dependency1, IDependency2 dependency2, IDependency3 dependency3, IDependency4 dependency4, IDependency5 dependency5,IDependency6 dependency6,IDependency7 dependency7,IDependency8 dependency8)
{
    //some code
}

Dependencies is here too many, in that way the Dependency Injection is overused. Such constructors should be reduced.

What if we really need so many dependencies? Each of them can be divided into five other parts, in which case it is worth being interested in the design pattern of the facade, which will also be mentioned in later lessons.

Cyclic dependencies

Cyclic dependencies are also a big problem, they rely on a class X project which relies on the Y class and the Y class depends in turn on the X class. Such dependence is a sign that one of these classes does not meet the first solid principle, the Single Responsibility principle, the solution is of course the break down of classes into smaller ones.

Summary

Dependency Injection is a very useful pattern, if we do not abuse it, then it can undoubtedly facilitate the development of the application, code management and testing, thanks to DI it is also easier to write the code in accordance with the SOLID principles, especially with the two first.

In this article are the most important things about Dependency Injection, on 99% percent will be in the future a separate section about Dependency Injection where it will be explained in detail, including, useful containers in dependency Injection, testing dependency injection logic and containers with unit tests, about how you can combine dependency Injection with a CQRS pattern (which also will be a separate section 🙂) and other patterns, etc., etc. 🙂

It’s worth learning about the design patterns, because regardless of whether the language or framework changes, the way the code is written and its readability will always depend on the knowledge of the SOLID principles and design patterns.

Link to github with the whole code from this article: https://github.com/Slaw145/ServiceLocatorTutorial

This content also you can find on my blog http://devman.pl/programtech/design-patterns-dependency-injection-variations/

If you recognise it as useful, share it with others so that others can also use it.

Leave upvote and follow and wait for next articles :) .

And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place 🙂

– site on fb: Devman.pl-Sławomir Kowalski

– group on fb: DevmanCommunity

Ask, comment underneath at the end of the post, share it, rate it, whatever you want🙂.

Sort:  

Great Post! Did you know you can automated your account? AutoSteemer(dot)com Get 0.1 STEEM Free just to signup!

Coin Marketplace

STEEM 0.19
TRX 0.15
JST 0.029
BTC 62978.31
ETH 2546.24
USDT 1.00
SBD 2.76