JavaRush /Java Blog /Random EN /The Easy Way to Inject Dependencies

The Easy Way to Inject Dependencies

Published in the Random EN group
Dependency injection (DI) is a tricky concept to understand, and applying it to new or existing applications is even more confusing. Jess Smith will show you how to implement dependency injection without an injection container in the C# and Java programming languages. The Easy Way to Inject Dependencies - 1In this article, I will 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 under the acronym SOLID). The D in SOLID refers to Dependency of Inversion (DOI), which later became known as dependency injection. The original and most commonly used definition: dependency inversion is an inversion of the way a base class manages dependencies. Martin's original article used the following code to illustrate a class's dependency 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 list or types of a method's parameters WritePrinter, you must inject 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 issue is that the Copy class is no longer a potential candidate for reuse. For example, what if you need to output the characters you enter from the keyboard to a file instead of 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 emergence of a new dependency WriteDisk, the situation did not improve (rather worse) because another principle was violated: "software entities, that is, classes, modules, functions, and so on, should be open for extension, but closed for change." Martin explains that these new 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 Readerand types Writer, which makes it possible to reuse them with different implementations. But if all this seems like some kind of gibberish 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 converted to 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 is a simple example:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Any project class that uses this code has a dependency on the class HtmlUserPresentation, resulting in the usability and maintainability issues described above. An improvement immediately suggests itself: the creation of an interface with the 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 type instantiation HtmlUserPresentation, we can now use an interface type instead of a 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 type 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 that use the type are still aware of its existence. To remove this direct dependency, you can pass the interface type IHtmlUserPresentationto the constructor (or method parameter list) of the class or method that will use it:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
The constructor UploadFilenow has access to all the functionality of type IHtmlUserPresentation, but it does not know anything about the internals of the class that implements this interface. In this context, type injection occurs when an instance of a class is created UploadFile. An interface type IHtmlUserPresentationbecomes reusable, 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 be directly dependent on each other when one of them creates an instance of another in order to access the target type's functionality. To decouple a direct dependency between two types, you must 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 constructor or class method, the class/method that needs the functionality doesn't know any details about the type that implements the interface. Because of this, an interface type can be reused for different classes that require similar but not the same behavior.
  • To experiment with dependency injection, review your code from one or more applications and try to convert a heavily used base type into an interface.

  • Modify the classes that directly instantiate this base type so that they use this new interface type and pass it through the constructor or parameter list of the class method that should use it.

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