JavaRush /Java Blog /Random EN /An easy way to inject dependencies

An easy way to inject dependencies

Published in the Random EN group
Dependency injection (DI) is not an easy concept to understand, and applying it to new or existing applications is even more confusing. Jess Smith shows you how to do dependency injection without an injection container in the C# and Java programming languages. Simple way of dependency injection - 1In this article, I'll show you how to implement dependency injection (DI) in .NET and Java applications. The concept of dependency injection first came to the attention of developers in 2000, when Robert Martin wrote the article "Design Principles and Patterns" (later known by the acronym SOLID ). The D in SOLID refers to Dependency of Inversion (DOI), which later became known as dependency injection. The original and most common definition: dependency inversion is an inversion of the way a base class manages dependencies. Martin's original article used the following code to illustrate the dependency of a class Copyon a lower-level class WritePrinter:
void Copy()
	{
	 int c;
	 while ((c = ReadKeyboard()) != EOF)
		WritePrinter(c);
	}
The first obvious problem is that if you change the parameter list or types of a method WritePrinter, you need to implement updates wherever there is a dependency on that method. This process increases maintenance costs and is a potential source of new errors.
Interested in reading about Java? Join the Java Developer group !
Another problem: the Copy class is no longer a potential candidate for reuse. For example, what if you need to output characters entered from the keyboard to a file instead of to a printer? To do this, you can modify the class Copyas follows (C++ language syntax):
void Copy(outputDevice dev)
	{
	int c;
	while ((c = ReadKeyboard()) != EOF)
		if (dev == printer)
			WritePrinter(c);
		else
			WriteDisk(c);
	}
Despite the introduction of a new dependency WriteDisk, the situation did not improve (but rather worsened) because another principle was violated: “software entities, that is, classes, modules, functions, and so on, should be open for extension, but closed for modification.” Martin explains that these new conditional if/else statements reduce the stability and flexibility of the code. The solution is to invert the dependencies so that the write and read methods depend on the Copy. Instead of "popping" dependencies, they are passed through the constructor. The modified code looks like this:
class Reader
	{
		public:
		virtual int Read() = 0;
	};
	class Writer
	{
		public:
		virtual void Write(char) = 0;
	};
	void Copy(Reader& r, Writer& w)
	{
		int c;
		while((c=r.Read()) != EOF)
		w.Write(c);
	}
Now the class Copycan be easily reused with different implementations of class methods Readerand Writer. The class Copydoes not have any information about the internal structure of the types Readerand Writer, making it possible to reuse them with different implementations. But if all this seems like some kind of gobbledygook to you, perhaps the following examples in Java and C# will clarify the situation.

Example in Java and C#

To illustrate the ease of dependency injection without a dependency container, let's start with a simple example that can be customized for use DIin just a few steps. Let's say we have a class HtmlUserPresentationthat, when its methods are called, generates an HTML user interface. Here's a simple example:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Any project using this class code will have a dependency on the class HtmlUserPresentation, resulting in the usability and maintainability issues described above. An improvement immediately suggests itself: creating an interface with signatures of all the methods currently available in the class HtmlUserPresentation. Here is an example of this interface:
public interface IHtmlUserPresentation {
	String createTable(ArrayList rowVals, String caption);
	String createTableRow(String tableCol);
	// Оставшиеся сигнатуры
}
After creating the interface, we modify the class HtmlUserPresentationto use it. Returning to instantiating the type HtmlUserPresentation, we can now use the interface type instead of the base type:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Creating an interface allows us to easily use other implementations of the IHtmlUserPresentation. For example, if we want to test this type, we can easily replace the base type HtmlUserPresentationwith another type called HtmlUserPresentationTest. The changes made so far make the code easier to test, maintain, and scale, but do nothing for reuse since all HtmlUserPresentationclasses using the type are still aware of its existence. To remove this direct dependency, you can pass an interface type IHtmlUserPresentationto the constructor (or list of method parameters) of the class or method that will use it:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
The constructor UploadFilenow has access to all the functionality of the type IHtmlUserPresentation, but knows nothing about the internal structure of the class that implements this interface. In this context, type injection occurs when an instance of the class is created UploadFile. An interface type IHtmlUserPresentationbecomes reusable by passing different implementations to different classes or methods that require different functionality.

Conclusion and recommendations for consolidating the material

You learned about dependency injection and that classes are said to directly depend on each other when one of them instantiates another to gain access to the functionality of the target type. To decouple the direct dependency between the two types, you should create an interface. An interface gives a type the ability to include different implementations, depending on the context of the required functionality. By passing an interface type to a class constructor or method, the class/method that needs the functionality does not know any details about the type implementing the interface. Because of this, an interface type can be reused across different classes that require similar, but not identical, behavior.
  • To experiment with dependency injection, look at your code from one or more applications and try converting a heavily used base type into an interface.

  • Change the classes that directly instantiate this base type to use this new interface type and pass it through the constructor or parameter list of the class method that will use it.

  • Create a test implementation to test this interface type. Once your code is refactored, DIit will become easier to implement, and you will notice how much more flexible your application becomes in terms of reuse and maintainability.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION