📜 ⬆️ ⬇️

Using IoC containers. Pros and cons

In the light of the release of the new version of the Enterprise Library, one of the most important parts of which is the Unity IoC container, I personally expect a surge of interest in the subject of IoC (Inversion of Control) containers, which, in fact, are the implementation of the rather well-known Service Locator pattern.

In this topic, I would like to discuss on the subject of “proper” use of such containers, as well as warn newbies against using it “everywhere and everywhere.” Well, just interested in the possibilities of "new technologies" should also be curious.

Background problems


Usually acquaintance with IoC-containers pushes awareness of the need to follow the principles of class design (SOLID) , namely, the last principle, which suggests that each class should depend not on specific objects, but on abstractions (interfaces), and quite wonderful when all these dependencies are declared in the constructor.

That is, the view code:
class TextManager {
public TextManager(ITextReader reader, ITextWriter writer) {
}
}


* This source code was highlighted with Source Code Highlighter .

this is good, but the view code:
')
class TextManager {
public TextManager(TextFromFileReader reader) {
}

public property DatabaseWriter Writer {
set {
_writer = value ;
}
}
}

* This source code was highlighted with Source Code Highlighter .

This is bad.

At the same time, if you have a large application / library, ITextWriter is used in many classes, and which Writer will be used is determined at the library entrance (let's say we have Writers in the database and a file with a common interface), then there is a logical desire somehow connect ITextWriter with a DatabaseWriter somewhere in one place.

Before SOLID, it was considered quite normal to declare a variable of type ITextWriter inside the library in a static class and to store the currently used Writer there. But when you start thinking about normal architecture ... :) the disadvantages of static classes become obvious - it’s completely opaque on what exactly each class depends on what it needs for work and what it does not, and it’s scary to even imagine what it will “pull” along with this class if suddenly it will be necessary to transfer it to another project or at least another library.

IoC container


What is offered to us to solve the problem? The solution has long been invented in the form of IoC containers: we “connect” the interfaces with specific classes as soon as we receive the necessary information:

var container = new UnityContainer();
container.RegisterInstance<ITextWriter>( new DatabaseWriter());


* This source code was highlighted with Source Code Highlighter .


and we have a convenient way to create objects:
container.Resolve<TextManager>();
* This source code was highlighted with Source Code Highlighter .


And if by the example of one dependence, the advantage of containers is not obvious, then if we imagine that designers require 2-3 interfaces, and we have at least 5-10 such classes, then the use of containers will seem like a real salvation to many.

When a container is good ..


Actually, Unity was created for active use in complex composite applications, with many implementations of identical interfaces and non-linear logic of interdependencies between implementations. Simply put, if we have not only one IWriter interface with two implementations of DbWriter and TextWriter, but also, for example, IReader and ITextProcessor, for each of which there are also 3-4 implementations, and TextWriter only works with CsvReader om and ExcelReader'om, and which of the readers should be used depends on the specific type of the current TextProcessor'a, well, on the phase of the moon at the same time.

It is very difficult to give specific code examples, the use of Unity in which, from my point of view, would be reasonable, and not to clutter the text with a ton of unnecessary code. But the described “example” creates a kind of such a complex and complex task :)

And when - not very


The use of constructions like container.Resolve (); it seems very convenient, and at first it makes one use it more often, and instantiate classes in this way wherever possible. All of this leads to the container being thrown "across the depth" of the library / application, and accessing the IUnityContainer either through the class constructors or, returning to the past, through static classes.
class TextManager {
public TextManager(IUnityContainer container) {
_writer = container.Resolve<IWriter>();
_reader = container.Resolve<IReader>();
}
}

* This source code was highlighted with Source Code Highlighter .


This is the extreme use of UnityContainer. Because:


... Profit!


Summing up, it seems to me that the use of IoC containers is ideal precisely in situations with complex interweaving of interdependencies between specific implementations, and only at the very top levels of the library / application.
That is, immediately after the entry point to the library should be registered in the Unity interface implementations, and as soon as possible rezolvit the types we need. Thus, we use all the power of Unity to simplify the process of generating complex-dependent objects, while at the same time we follow the principles of good design and minimize the use of a very abstract “container” in our application, thereby simplifying its testing.

PS Since I myself am not a guru in this topic, in the comments I will be glad to hear about my own mistakes, as well as about other possible uses of IoC containers.

Source: https://habr.com/ru/post/91650/


All Articles