To begin with, let's see what is inversion of control (inversion of control - IoC). When we write some simple program, it (the program) looks something like this:
Точка входа -> наш код -> наш код вызывает и контролирует выполнение стороннего кода.
For example,
void Main() { Console.WriteLine($"What is your name?"); var name = Console.ReadLine(); Console.WriteLine($"Hello, {name}!"); }
What happened here? Immediately after the entry point, OUR code used the console to query the user for his name, read it and display the greeting. Our code completely controls the process of our application. Our code is responsible for starting, for error handling, for everything that happens in the application. That is, control comes from our code.
What does the inversion of control now mean? Inversion of control means a situation where an EXTERNAL code begins to call our code when an external code begins to control our code. We show by example. Suppose we use a framework that consists of class 1:
// Это сторонний код! Не наш! public class Bootstrapper { public void RunAndGo(Action action) => action?.Invoke(); }
Now, change our code like this:
// НАШ код void Main() { var boot = new Bootstrapper(); boot.RunAndGo(MyLogic); } public void MyLogic() { Console.WriteLine($"What is your name?"); var name = Console.ReadLine(); Console.WriteLine($"Hello, {name}!"); }
It seems nothing has changed much, but now the third-party code decides when our logic will work. Third-party code runs and calls our function when it is convenient. Third-party code can be changed, supplemented, updated, without changing our code. Usually, in such scenarios, the programmer can concentrate on a specific task that he wants to solve, and that’s all, he should not care about anything else. The third-party code refers to you: tell me what you want and I will take care of everything else. Examples of this are: controllers in asp.net - you say that they should return and the infrastructure will take care of the rest; PRF in WPF - you describe the modules, views, regions, logic, framework to make sure that everything works in a coordinated manner; in the .NET Framework - you tell the framework what you want to accomplish, and it will take care of the compatibility with the platform. That is, IoC is a broad term.
Now about dependency injection (Dependency injection). For a start, let's deal with the fact that such a relationship. Suppose there is a class
public class Logic : ILogicElement { ConsoleReader _reader; ConsoleWriter _writer; public Logic() { _reader = new ConsoleReader(); _writer = new ConsoleWriter(); } public void Go() { _writer.Write("What is your name?"); var name = _reader.Read(); _writer.Write($"Hello, {name}!"); } }
We see that the class explicitly uses 2 other classes (the implementation of which we are not interested in now). Our class not only assumed responsibility for the creation of other classes, but also its work clearly DEPENDS on how the other classes are implemented, as it explicitly calls their methods within themselves. That is, such auxiliary classes are called dependencies, because our class obviously depends on them.
But what does the flow of work with these dependencies look like? Like that:
Наш класс --> наш класс создает зависимости самостоятельно
In general, there are quite a few questions about this. For example - is it necessary for our class to know at all how its dependencies were created and how they are implemented? Is this his responsibility? Here, of course, violation of the principle of uniqueness of responsibility. What can be done here? Here's what:
public class Logic : ILogicElement { ConsoleReader _reader; ConsoleWriter _writer; public Logic(ConsoleReader reader, ConsoleWriter writer) { _reader = reader; _writer = writer; } public void Go() { _writer.Write("What is your name?"); var name = _reader.Read(); _writer.Write($"Hello, {name}!"); } }
Now the program flow looks like this:
Вызывающий код создает зависимости для нашего класса --> наш класс
As you can see, if earlier our class managed dependencies, now third-party code controls everything. When creating our class, dependencies will have to be IMPLEMENTED in the constructor of our class - this is the essence of dependency injection. You may have already grasped the link between dependency injection and IoC. It really is - dependency injection - a special case of IoC.
Dependency injection can occur not only in the constructor, but also in the property or method. But personally, I use only the constructor for this - because:
- Without specifying all dependencies in the constructor, the class cannot be created
- All dependencies are in one place, they are all clearly visible.
Well, go ahead. Principle of dependency inversion .
Unlike IoC or dependency injection, the principle of dependency inversion is one of the SOLID principles and says the following:
- The modules of the upper levels should not depend on the modules of the lower levels. Both types of modules should depend on abstractions.
- Abstractions should not depend on the details. Details must depend on abstractions.
Let's start with the modules. What is a module? A module, speaking in its own language, is a set of interrelated classes. Related precisely their purpose. You have probably heard the phrase “business logic module” or “data access module” more than once. Further, it is said that modules have levels, where some modules are higher than others. From experience, I would say that usually a top-level module causes a lower module.
Let's look at the example again:
public class Logic : ILogicElement { ConsoleReader _reader; ConsoleWriter _writer; public Logic(ConsoleReader reader, ConsoleWriter writer) { _reader = reader; _writer = writer; } public void Go() { _writer.Write("What is your name?"); var name = _reader.Read(); _writer.Write($"Hello, {name}!"); } }
What is related to the top level module? Definitely, our logic is the top level. What applies to the lower level modules? Obviously, the read / write classes in the console do not belong to logic, that is, they belong to the lower level module, since our module uses them.
Does our upper logic module depend on the lower-level module - working with the console? Obviously yes! After all, he knows about him and uses his methods. That is, if you change the signatures of the methods of the module of the lower level, you will have to change the module of the upper one. How to deal with this? The answer to the surface: Оба типа модулей должны зависеть от абстракций. - that is, we need an abstraction. Here such, for example
public interface IUserInteractionReader { string Read(); } public interface IUserInteractionWriter { void Write(string message); }
Classes for working with the console
public class ConsoleReader : IUserInteractionReader { public string Read() => Console.ReadLine(); } public class ConsoleWriter : IUserInteractionWriter { public void Write(string message) => Console.WriteLine(message); }
Logic class
public class Logic : ILogicElement { IUserInteractionReader _reader; IUserInteractionWriter _writer; public Logic(IUserInteractionReader reader, IUserInteractionWriter writer) { _reader = reader; _writer = writer; } public void Go() { _writer.Write("What is your name?"); var name = _reader.Read(); _writer.Write($"Hello, {name}!"); } }
See what happened? Our logic no longer knows about any module working with the console. Our logic now depends on 2 interfaces - abstractions. And what about the console module? It now also depends on these interfaces, since classes must implement these interfaces.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций - this, in theory, means a simple thing - in our logic, in theory, we only need to read the string and write the string. These are 2 very simple operations for our class, since it relies entirely on the implementation of interfaces. The class doesn’t matter HOW exactly the reading and writing is implemented - it doesn’t matter whether it works with the console or polls the user by email or phone - these details should not affect the interface provided, since the most important thing that is required is already specified in the interface. That is, the implementation details should not affect the abstraction.
Putting it all together
So, let's say we have our modules and abstractions.
public interface ILogicElement { void Go(); } public interface IUserInteractionReader { string Read(); } public interface IUserInteractionWriter { void Write(string message); } public class ConsoleReader : IUserInteractionReader { public string Read() => Console.ReadLine(); } public class ConsoleWriter : IUserInteractionWriter { public void Write(string message) => Console.WriteLine(message); } public class Logic : ILogicElement { IUserInteractionReader _reader; IUserInteractionWriter _writer; public Logic(IUserInteractionReader reader, IUserInteractionWriter writer) { _reader = reader; _writer = writer; } public void Go() { _writer.Write("What is your name?"); var name = _reader.Read(); _writer.Write($"Hello, {name}!"); } }
Further, there is some kind of framework (I used Unity as the DI container)
public class Bootstrapper { IUnityContainer _container; public Bootstrapper(IUnityContainer container) { _container = container; } public void RunAndGo() { foreach(var logic in _container.ResolveAll<ILogicElement>()) logic.Go(); } }
For this our framework, we need to first configure the container. It looks like this:
void Main() { var container = new UnityContainer(); container.RegisterType<IUserInteractionReader, ConsoleReader>(); container.RegisterType<IUserInteractionWriter, ConsoleWriter>(); container.RegisterType<ILogicElement, Logic>("my awesome logic name"); container.Resolve<Bootstrapper>().RunAndGo(); }
What are we doing here? Here we only configure all the details that we need, and pass on the execution of the framework (I mean the IoC), all the dependencies of the logic are declared in the designer (this is about dependency injection - I used a container for dependency injection, but this is not necessary), all 2 module does not depend on each other, but depend on abstractions (principle of inversion of dependencies).
Hopefully, after so much text, the topic of inversions, introductions and principles became a little clearer.
Metod2()will generate an error - tym32167private IServise _srServise;- class field. This ispublic ClassForInject(IServise _srServise)- the constructor and its parameter. Addthis._srServise = _srServise;and the error is gone. - tym32167readonly, so that no other method could suddenly change it. - Ares