1. What is Dependent Injection
Formal definition of dependency injection:
Dependency Injection is the process in which a customer class defines only one injection point because it depends on only one interface of a service class and not on a specific service class.In the process of running the program, the client class does not instantiate the specific service class instance directly. Instead, the running context or specialized components of the client class are responsible for instantiating the service class and injecting it into the client class to ensure the normal operation of the client class.
2. Categories Dependent on Injection
1.Setter Injection
Setter Injection refers to setting a data member of a service class interface type in a client class and setting a Set method as an injection point that accepts a specific service class instance as a parameter and assigns it to a data member of the service class interface type.
Sample code for Setter injection is given below.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal interface IServiceClass { String ServiceInfo(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ServiceClassA : IServiceClass { public String ServiceInfo() { return "I am ServceClassA"; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ServiceClassB : IServiceClass { public String ServiceInfo() { return "I am ServceClassB"; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ClientClass { //Injection Point private IServiceClass _serviceImpl; //Method in client class, initialize injection point public void Set_ServiceImpl(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } public void ShowInfo() { Console.WriteLine(_serviceImpl.ServiceInfo()); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { class Program { static void Main(string[] args) { IServiceClass serviceA = new ServiceClassA(); IServiceClass serviceB = new ServiceClassB(); ClientClass client = new ClientClass(); client.Set_ServiceImpl(serviceA); client.ShowInfo();//Result: I am ServceClassA client.Set_ServiceImpl(serviceB); client.ShowInfo();//Result: I'm ServceClassB Console.ReadLine(); } } }
The results are as follows:
2. Construction Injection
Another dependency injection method is to inject service class instances into a customer class through its constructor.
Constructor Injection is a data member that sets a service class interface type in a client class and takes a constructor as the injection point, which accepts a specific service class instance as a parameter and assigns it to the data member of the service class interface type.
Similar to Setter injection, except that the injection point has changed from a Setter method to a construction method.Note here that since construct injections can only be injected once when instantiating a client class, a single injection will not change the service class instance within a client class object while the program is running.
Since the IServiceClass constructed for injection and Setter injection is the same, ServiceClassA and ServiceClassB are examples of other ClientClass classes.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConstructorInjection { internal class ClientClass { private IServiceClass _serviceImpl; public ClientClass(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } public void ShowInfo() { Console.WriteLine(_serviceImpl.ServiceInfo()); } } }
As you can see, the only change is that the constructor replaces the Set_ServiceImpl method and becomes the injection point.
3. Dependent Acquisition
The aforementioned injection methods refer to the passive acceptance of dependent service classes by client classes, which also corresponds to the term injection.However, there is another way to achieve the same goal as dependency injection, which is dependency acquisition.
Dependency Locate refers to providing a point of acquisition in the system where the client class still depends on the interface of the service class.When a customer class needs a service class, it actively gets the specified service class from the acquisition point. The specific service class type is determined by the configuration of the acquisition point.
As you can see, this method becomes passive and active, enabling client classes to actively acquire service classes when needed, and encapsulating polymorphic implementations into acquisition points.Get points can be implemented in many ways, and perhaps the easiest thing to think of is to create a Simple Factory as the get point, and the customer class passes in a specified string to get the corresponding service class instance.If the service class on which you depend is a series of classes, Dependency Acquisition typically uses the Abstract Factory schema to construct the harvesting points, then transfers the service class polymorphism to factory polymorphism, which relies on an external configuration, such as an XML file.
However, whether you use Simple Factory or Abstract Factory, you cannot avoid judging the type of service class or factory, so there must always be a place in the system where if does not conform to OCP...else or switch...The case structure, a flaw that Simple Factory and Abstract Factory, as well as dependency acquisition itself cannot eliminate, has been thoroughly solved by introducing a reflection mechanism into some languages that support reflection, such as C# (discussed later).
Here's a specific example. Now let's assume there's a program that can use both a Windows-style look and a Mac-style look, but the internal business is the same.
The figure above may seem a bit complicated at first, but if the reader is familiar with the Abstract Factory pattern, it should be easy to understand, which is one application of Abstract Factory in practice.Here Factory Container is a static class as a get point, and its Type constructor determines which factory to instantiate based on an external XML configuration file.Next, let's look at the sample code.Since the code for the different components is similar, only the sample code for the Button component is given here. For the complete code, refer to the complete source program attached at the end of the article.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IButton { String ShowInfo(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsButton : IButton { public String Description { get; private set; } public WindowsButton() { this.Description = "Windows Style Button"; } public String ShowInfo() { return this.Description; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacButton : IButton { public String Description { get; private set; } public MacButton() { this.Description = " Mac Style Button"; } public String ShowInfo() { return this.Description; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IFactory { IWindow MakeWindow(); IButton MakeButton(); ITextBox MakeTextBox(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsFactory : IFactory { public IWindow MakeWindow() { return new WindowsWindow(); } public IButton MakeButton() { return new WindowsButton(); } public ITextBox MakeTextBox() { return new WindowsTextBox(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacFactory : IFactory { public IWindow MakeWindow() { return new MacWindow(); } public IButton MakeButton() { return new MacButton(); } public ITextBox MakeTextBox() { return new MacTextBox(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; namespace DependencyLocate { internal static class FactoryContainer { public static IFactory factory { get; private set; } static FactoryContainer() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("http://www.cnblogs.com/Config.xml"); XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0]; if ("Windows" == xmlNode.Value) { factory = new WindowsFactory(); } else if ("Mac" == xmlNode.Value) { factory = new MacFactory(); } else { throw new Exception("Factory Init Error"); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { class Program { static void Main(string[] args) { IFactory factory = FactoryContainer.factory; IWindow window = factory.MakeWindow(); Console.WriteLine("Establish " + window.ShowInfo()); IButton button = factory.MakeButton(); Console.WriteLine("Establish " + button.ShowInfo()); ITextBox textBox = factory.MakeTextBox(); Console.WriteLine("Establish " + textBox.ShowInfo()); Console.ReadLine(); } } }
Here we use XML as the configuration file.The configuration file Config.xml is as follows:
<?xml version="1.0" encoding="utf-8" ?> <config> <factory>Mac</factory> </config>
As you can see, here we set the configuration to Mac style, compile and run the above code, and the results are as follows:
Running results after configuring Mac style
Now let's just change the Mac in the configuration file to Windows without moving the program. The results are as follows:
Run results configured for Windows style
From the results, we can see that simply by modifying the configuration file, we have changed the behavior of the entire program (we have not even recompiled the program), which is the power of polymorphism and also depends on the injection effect.
Reflection and Dependent Injection
Recalling the Dependency Locate example above, although polymorphism and the Abstract Factory were used, the implementation of OCP was not thorough enough.Before understanding this, friends should be aware of where the potential extensions are. The potential extensions are "New Component Series" instead of "Component Kind". That is, we assume there are three components and no new components will be added, but there may be new appearance series. If you need to add a set of Ubuntu-style components, we can add UbuntuWindow, UbuntuButton, UbuntuTextBox, and UbuntuFactory, respectively, implement the corresponding interfaces, which are OCP compliant because they are extensions.But in addition to modifying the configuration file, the unavoidable modification of FactoryContainer requires a branching condition, which destroys OCP.Dependent injection itself is not capable of solving this problem, but if language supports Reflection, it will be solved.
We think the challenge now is that objects will ultimately be instantiated by "new", which can only instantiate existing classes. If new classes are added in the future, the code must be modified.If we have a way to instantiate an object not by "new", but by the name of a class, we can load future classes without modifying the code by simply using the name of the class as a configuration item.Therefore, reflections give the language the ability to "see the future", increasing the power of polymorphism and dependence injection.
The following are improvements to the above example after introducing the reflection mechanism:
It can be seen that with the introduction of the reflection mechanism, the structure is much simpler, a reflection factory replaces a previous cluster of factories, and the Factory Container is no longer needed.And when a new component family is added in the future, the reflection factory does not need to be changed, only the configuration file can be changed.The code for reflecting factories and configuration files is given below.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Xml; namespace DependencyLocate { internal static class ReflectionFactory { private static String _windowType; private static String _buttonType; private static String _textBoxType; static ReflectionFactory() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("http://www.cnblogs.com/Config.xml"); XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0]; _windowType = xmlNode.ChildNodes[0].Value; _buttonType = xmlNode.ChildNodes[1].Value; _textBoxType = xmlNode.ChildNodes[2].Value; } public static IWindow MakeWindow() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow; } public static IButton MakeButton() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton; } public static ITextBox MakeTextBox() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox; } } }
The configuration file is as follows:
<?xml version="1.0" encoding="utf-8" ?> <config> <window>MacWindow</window> <button>MacButton</button> <textBox>MacTextBox</textBox> </config>
Reflection can be combined not only with Dependency Locate, but also with Setter Injection and Constructor Injection.The introduction of reflection mechanism reduces the complexity of dependency injection structure, makes dependency injection completely OCP compliant, and provides the possibility for the design of generic dependency injection frameworks such as IoC in Spring.NET, Unity, and so on.