Original address: https://www.cnblogs.com/AnAng/p/9370913.html
catalogue
catalogue
1 story of igame game company
1.1 seminars
1.2 implementation method of intern Xiao Li
1.3 architect's recommendations
1.4 summary of Xiao Li
2 explore dependency injection
2.1 enlightenment of the story
2.2 formally define dependency injection
3. Those things
3.1 types of dependency injection
3.1.1 Setter injection
3.1.2 constructor injection
3.1.3 dependency acquisition
3.2 reflection and dependency injection
3.3 polymorphic activity and dependent injection
3.3.1 activity of polymorphism
3.3.2 selection of different activity polymorphism dependent injection
4 IoC Container
4.1 inevitability of IOC container
4.2 classification of IOC container
4.2.1 heavyweight IoC Container
4.2.2 lightweight IoC Container
4.3 .NET platform
4.3.1 Spring.NET
4.3.2 Unity
reference
1 story of igame game company
1.1 seminars
In other words, a game company called IGame is developing an ARPG game (action & role-playing games, such as world of Warcraft and fantasy journey to the West). Generally, this kind of game has a basic function, which is to fight monsters (players attack monsters to gain experience, virtual currency and virtual equipment), and the attack effect is also different according to the weapons equipped by the player character. On that day, the development team of IGame company was holding a meeting to discuss how to realize a function point in the strange function. On the big screen in front of them was such a ppt of Requirement Description:
Figure 1.1 demand description ppt
Various developers have had a heated discussion in the face of this demand. Let's take a look at what happened at the seminar.
1.2 implementation mode of intern Xiao Li
After some discussion, Peter, the project leader, felt it necessary to sort out the opinions of all parties. He first asked Xiao Li for his opinions. Xiao Li is a junior in the computer department of a school. He is particularly interested in game development. At present, he is an intern of IGame company.
After a short period of thinking, Xiao Li expounded his opinion:
"I think this requirement can be realized in this way. Of course, HP is an attribute member of the monster, and the weapon is an attribute member of the character. The type can be string, which can be used to describe the weapons equipped by the current character. The character class has an attack method, which takes the attacked monster as the parameter. When an attack is carried out, the attack method is called, and this method first judges what weapons the current character is equipped with, and then carries out the attack on the attacked monster The HP attacking the monster operates to produce different effects. "
After expounding, Xiao Li also quickly wrote a Demo on his computer to demonstrate his idea. The Demo code is as follows.
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLi { /// <summary> ///Monsters /// </summary> internal sealed class Monster { /// <summary> ///Monster's name /// </summary> public String Name { get; set; } /// <summary> ///Monster health /// </summary> public Int32 HP { get; set; } public Monster(String name,Int32 hp) { this.Name = name; this.HP = hp; } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLi { /// <summary> ///Role /// </summary> internal sealed class Role { private Random _random = new Random(); /// <summary> ///A string representing the current weapon held by the character /// </summary> public String WeaponTag { get; set; } /// <summary> ///Attack monster /// </summary> ///< param name = "monster" > attacked monster < / param > public void Attack(Monster monster) { if (monster.HP <= 0) { Console.WriteLine("This monster is dead"); return; } if ("WoodSword" == this.WeaponTag) { monster.HP -= 20; if (monster.HP <= 0) { Console.WriteLine("Attack succeeded! monster" + monster.Name + "Dead"); } else { Console.WriteLine("Attack succeeded! monster" + monster.Name + "Loss 20 HP"); } } else if ("IronSword" == this.WeaponTag) { monster.HP -= 50; if (monster.HP <= 0) { Console.WriteLine("Attack succeeded! monster" + monster.Name + "Dead"); } else { Console.WriteLine("Attack succeeded! monster" + monster.Name + "Loss 50 HP"); } } else if ("MagicSword" == this.WeaponTag) { Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200; monster.HP -= loss; if (200 == loss) { Console.WriteLine("Critical hit!!!"); } if (monster.HP <= 0) { Console.WriteLine("Attack succeeded! monster" + monster.Name + "Dead"); } else { Console.WriteLine("Attack succeeded! monster" + monster.Name + "loss" + loss + "HP"); } } else { Console.WriteLine("The character has no weapons in his hand and cannot attack!"); } } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLi { class Program { static void Main(string[] args) { //Generate monsters Monster monster1 = new Monster("Little monster A", 50); Monster monster2 = new Monster("Little monster B", 50); Monster monster3 = new Monster("Guan Zhu", 200); Monster monster4 = new Monster("final Boss", 1000); //Generate role Role role = new Role(); //Wooden sword attack role.WeaponTag = "WoodSword"; role.Attack(monster1); //Iron sword attack role.WeaponTag = "IronSword"; role.Attack(monster2); role.Attack(monster3); //Magic sword attack role.WeaponTag = "MagicSword"; role.Attack(monster3); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); Console.ReadLine(); } } }
The running results of the program are as follows:
Figure 1.2 running results of Xiao Li program
1.3 architect's recommendations
After Xiao Li explained his ideas and demonstrated the Demo, project leader Peter first affirmed Xiao Li's thinking ability, programming ability and preliminary object-oriented analysis and design idea, and acknowledged that Xiao Li's program correctly completed the functions in the requirements. But at the same time, Peter also pointed out that there are some problems in Xiao Li's design. He asked Xiao Xiao to share his views.
He is an architect with five years of experience in software architecture, and has a deep understanding of software architecture, design patterns and object-oriented ideas. He nodded to Peter and expressed his views:
"Xiao Li has good thinking ability, basic object-oriented analysis and design ability, and the program has correctly completed the required functions. However, from the perspective of architecture, I would like to briefly talk about the problems I think exist in this design.
Firstly, the Attack method of Role class designed by Xiao Li is very long, and there is a lengthy if... else structure in the method, and the business logic of the code of each branch is very similar, but there are few differences.
Moreover, I think a big problem with this design is that it violates the OCP principle. In this design, if we add a new weapon in the future, such as Yitian sword, which loses 500HP per Attack, we need to open the Role and modify the Attack method. Our code should be closed to modification. When a new weapon is added, we should use extension to avoid modifying the existing code.
Generally speaking, when there are lengthy if... else or switch... case structures in a method, and the business of each branch code is similar, it often indicates that polymorphism should be introduced to solve the problem. Here, if different weapon attacks are regarded as a strategy, it is a wise choice to introduce Strategy Pattern.
Finally, let's talk about a small problem. After being attacked, reducing HP and death judgment are the responsibilities of monsters. It's inappropriate to put them in Role here. "
Tip: OCP principle, i.e. open close principle, means that the design should be open to expansion and closed to modification.
Tip: Strategy Pattern, English Name: Strategy Pattern, refers to defining algorithm families and encapsulating them separately so that they can be replaced with each other. This mode makes the change of algorithm independent of customers.
While talking, he drew a UML class diagram to intuitively express his ideas.
Design drawing less than 1.3
Peter asked Xiao Li to reconstruct the Demo according to the design of less than. Xiao Li looked at the design drawing of less than and finished it quickly. Relevant codes are as follows:
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { internal interface IAttackStrategy { void AttackTarget(Monster monster); } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { internal sealed class WoodSword : IAttackStrategy { public void AttackTarget(Monster monster) { monster.Notify(20); } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { internal sealed class IronSword : IAttackStrategy { public void AttackTarget(Monster monster) { monster.Notify(50); } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { internal sealed class MagicSword : IAttackStrategy { private Random _random = new Random(); public void AttackTarget(Monster monster) { Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200; if (200 == loss) { Console.WriteLine("Critical hit!!!"); } monster.Notify(loss); } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { /// <summary> ///Monsters /// </summary> internal sealed class Monster { /// <summary> ///Monster's name /// </summary> public String Name { get; set; } /// <summary> ///Monster health /// </summary> private Int32 HP { get; set; } public Monster(String name,Int32 hp) { this.Name = name; this.HP = hp; } /// <summary> ///When the monster is attacked, the method called is used to handle the state change after being attacked /// </summary> ///< param name = "loss" > HP lost in this attack < / param > public void Notify(Int32 loss) { if (this.HP <= 0) { Console.WriteLine("This monster is dead"); return; } this.HP -= loss; if (this.HP <= 0) { Console.WriteLine("monster" + this.Name + "Killed"); } else { Console.WriteLine("monster" + this.Name + "loss" + loss + "HP"); } } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { /// <summary> ///Role /// </summary> internal sealed class Role { /// <summary> ///Indicates the character's current weapon /// </summary> public IAttackStrategy Weapon { get; set; } /// <summary> ///Attack monster /// </summary> ///< param name = "monster" > attacked monster < / param > public void Attack(Monster monster) { this.Weapon.AttackTarget(monster); } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IGameLiAdv { class Program { static void Main(string[] args) { //Generate monsters Monster monster1 = new Monster("Little monster A", 50); Monster monster2 = new Monster("Little monster B", 50); Monster monster3 = new Monster("Guan Zhu", 200); Monster monster4 = new Monster("final Boss", 1000); //Generate role Role role = new Role(); //Wooden sword attack role.Weapon = new WoodSword(); role.Attack(monster1); //Iron sword attack role.Weapon = new IronSword(); role.Attack(monster2); role.Attack(monster3); //Magic sword attack role.Weapon = new MagicSword(); role.Attack(monster3); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); role.Attack(monster4); Console.ReadLine(); } } }
Compile and run the above code, and the running results are basically consistent with the code of the previous version.
1.4 summary of Xiao Li
Peter is obviously satisfied with the improved code. He asks Xiao Li to make a summary by comparing the two designs and codes. Xiao Li briefly thought about it, and combined with the shortcomings pointed out in the previous design, said:
"I think the improved code has the following advantages:
First, although the number of classes has increased, the code of methods in each class is very short. Without the long method of Attack method and the lengthy if... else, the code structure becomes very clear.
Second, the responsibilities of the class are clearer. In the first design, Role is not only responsible for attacking, but also responsible for reducing HP to the Monster and judging whether the Monster is dead. Obviously, this should not be the responsibility of Role. The improved code moves these two responsibilities into Monster, which makes the responsibilities clear and improves the cohesion of the class.
Third, the introduction of Strategy pattern not only eliminates repetitive code, but also makes the design conform to OCP. If you want to add a new Weapon in the future, just create a new class and implement the iatackstrategy interface. When the Role needs to be equipped with this new Weapon, the customer code just needs to instantiate a new Weapon class and assign it to the Weapon member of Role. The existing Role and Monster codes do not need to be changed. This enables the development of extensions and the closing of modifications. "
Peter and Xiao Xiao were very satisfied after listening and thought Xiao Li's summary was excellent.
The seminar of IGame company is still going on, and the content is very wonderful, but we hear it first, because next, we want to discuss some of them. Don't forget, the theme of this article is dependency injection, and the protagonist hasn't appeared yet! It's not good to keep the protagonist waiting too long.
2 explore dependency injection
2.1 enlightenment of the story
Now let's calm down and aftertaste the story just now. Because the reason for dependency injection is hidden in this story. I said more than once that if you want to really recognize a thing, you can't just look at "what is it? What does it look like?", Instead, we should first find out "how did it come from? What kind of needs and background prompted its birth? What purpose was it created for?".
Think back to the above story. At the beginning, the main requirement is a strange function. Xiao Li made a preliminary object-oriented design: extract the entities (monsters, characters, etc.) in the field scene, encapsulate them into classes, give attributes and methods to each class, and finally complete the monster fighting function through class interaction, which should be regarded as the primary stage of object-oriented design.
On the basis of Xiao Li's design, the architect pointed out several deficiencies, such as non-compliance with OCP, unclear division of responsibilities and so on, and introduced the strategy mode according to the situation. This is a higher level of object-oriented design. In fact, as far as the core is concerned, less than has only done one thing: using polymorphism to isolate changes. It clearly recognizes that some business logic in this monster fighting function is unchanged. For example, if the character attacks the monster, the monster will reduce HP, and if it is reduced to 0, the monster will die; The only change is that when different characters hold different weapons, the effectiveness of each attack is different. So the essence of his architecture is to separate the changed part from the unchanged part, so that when the changed part changes, the unchanged part will not be affected.
Let's take a closer look at the design diagram of less than. After this design, there is a basic problem to be solved: now Role does not rely on specific weapons, but only on an iatackstrategy interface, and the interface cannot be instantiated. Although the Weapon member type of Role is defined as iatackstrategy, it will eventually be given a specific Weapon that implements the iatackstrategy interface, and as the program progresses, A character will be equipped with different weapons to produce different effects. The responsibility assigned to weapons is put in the test code in the Demo.
Here, the test code instantiates a specific Weapon and assigns it to the Weapon member of Role, which is dependency injection! It should be clear here that dependency injection is actually the name of a process!
2.2 formally define dependency injection
Next, in a slightly more formal language, define the background and reason of dependency injection and the meaning of dependency injection. In the process of reading, readers can understand it in combination with the above examples.
Background of dependency injection:
With the development of object-oriented analysis and design, one of the core principles of a good design is to isolate the changes so that when the changed parts change, the unchanged parts are not affected (this is also the purpose of OCP). In order to achieve this, we should make use of polymorphism in object-oriented. After using polymorphism, the customer class no longer directly depends on the service class, but on an abstract interface. In this way, the customer class cannot directly instantiate the specific service class internally. However, the customer class objectively needs a specific service class to provide services in operation, because the interface cannot be instantiated to provide services. There is a contradiction between "customer class is not allowed to instantiate specific service class" and "customer class needs specific service class". In order to solve this contradiction, developers propose a pattern: the customer class (such as Role in the above example) defines an injection point (Public member Weapon) for the injection of service classes (specific classes that implement iatackstrategy, such as WoodSword, IronSword and MagicSword, including all new classes that implement iatackstrategy added later), and the customer class (Program, i.e. test code) of the customer class is responsible for the injection according to the situation, Instantiate the service class and inject it into the customer class, so as to solve this contradiction.
Formal definition of dependency injection:
Dependency Injection is a process in which a customer class only depends on an interface of a service class, not on a specific service class, so the customer class only defines an injection point. In the process of program running, the client class does not directly instantiate the specific service class instance, but the running context or special components of the client class are responsible for instantiating the service class, and then injecting it into the client class to ensure the normal operation of the client class.
3. Those things
Above, we described the source and definition of dependency injection from the perspective of requirements background. However, if there is only so much about dependency injection, there is nothing worth discussing. However, the above discussion is only the connotation of dependency injection, and its extension is very extensive. Many related concepts and technologies have been derived from dependency injection. Let's discuss the "things" of dependency injection.
3.1 types of dependency injection
There are many methods of dependency injection. The above examples are just one of them. Different types of dependency injection are discussed below.
3.1.1 Setter injection
The first way of dependency injection is Setter injection. In the above example, injecting weapons into Role is Setter injection. Officially:
Setter Injection refers to setting a data member of the service class interface type in the customer class, and setting a Set method as the injection point. This Set method accepts a specific service class instance as a parameter and assigns it to the data member of the service class interface type.
Figure 3.1 schematic diagram of setter injection
The figure above shows the structure diagram of Setter injection. The client class ClientClass sets the IServiceClass type member_ serviceImpl and Set_ServiceImpl method as the injection point. Context is responsible for instantiating a specific ServiceClass and injecting it into ClientClass.
The following is a sample code of Setter injection.
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal interface IServiceClass { String ServiceInfo(); } }
+ View Code?
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"; } } }
+ View Code?
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"; } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ClientClass { private IServiceClass _serviceImpl; public void Set_ServiceImpl(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } public void ShowInfo() { Console.WriteLine(_serviceImpl.ServiceInfo()); } } }
+ View Code?
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(); client.Set_ServiceImpl(serviceB); client.ShowInfo(); } } }
The operation results are as follows:
Figure 3.2 operation results of setter injection
3.1.2 structural injection
Another way of dependency injection is to inject service class instances into the client class through the constructor of the client class.
Constructor Injection refers to setting a data member of the service class interface type in the client class, and taking the constructor as the injection point. This constructor accepts a specific service class instance as a parameter and assigns it to the data member of the service class interface type.
Fig. 3.3 structural injection diagram
Figure 3.3 is the schematic diagram of structure injection. It can be seen that it is very similar to Setter injection, but the injection point has changed from Setter method to construction method. It should be noted here that since construction injection can only be injected once when instantiating a client class, it is impossible to change the service class instance in a client class object during program operation.
Since the IServiceClass, ServiceClassA and ServiceClassB injected by the constructor and Setter are the same, the sample code of another ClientClass class is given here.
+ View Code?
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 Set_ServiceImpl method becomes the injection point.
3.1.3 dependency acquisition
The injection methods mentioned above are the service classes that the customer class passively accepts, which is also in line with the word "injection". However, there is another method that can achieve the same purpose as dependency injection, that is, dependency acquisition.
Dependency Locate refers to providing an acquisition point in the system, and the customer class still depends on the interface of the service class. When the customer class needs a service class, it actively obtains the specified service class from the acquisition point. The specific service class type is determined by the configuration of the acquisition point.
It can be seen that this method changes from passive to active, which makes the customer class actively obtain the service class when needed, and encapsulates the implementation of polymorphism into the acquisition point. There are many implementations of acquisition points. Perhaps the easiest thing to think of is to establish a Simple Factory as the acquisition point, and the customer class passes in a specified string to obtain the corresponding service class instance. If the dependent service class is a series of classes, the dependency acquisition generally uses the Abstract Factory pattern to build the acquisition point, and then transfers the polymorphism of the service class to the polymorphism of the factory, and the type of the factory depends on an external configuration, such as XML file.
However, whether Simple Factory or Abstract Factory is used, it is inevitable to judge the service class type or factory type. In this way, there must be an if... else or switch... case structure that does not comply with OCP in the system. This defect cannot be eliminated by Simple Factory, Abstract Factory and dependency acquisition itself. In some languages that support reflection (such as C#), This problem is completely solved by introducing the reflection mechanism (discussed later).
Here is a specific example. Now let's assume that there is a program that can use both Windows style appearance and Mac style appearance, and the internal business is the same.
Figure 3.4 schematic diagram of dependency acquisition
At first glance, the above figure is a little complex, but if readers are familiar with the Abstract Factory mode, they should be able to understand it easily. This is an application of Abstract Factory in practice. The Factory Container here, as the acquisition point, is a static class. Its "Type constructor" determines which factory to instantiate according to the external XML configuration file. Let's look at the sample code. Since the codes of different components are similar, only the sample code of Button component is given here. For the complete code, please refer to the complete source program attached at the end of the article.
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IButton { String ShowInfo(); } }
+ View Code?
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; } } }
+ View Code?
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; } } }
+ View Code?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IFactory { IWindow MakeWindow(); IButton MakeButton(); ITextBox MakeTextBox(); } }
+ View Code?
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(); } } }
+ View Code?
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(); } } }
+ View Code?
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"); } } } }
+ View Code?
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. Configuration file config The XML is as follows:
+ View Code?
<?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 running results are as follows:
Figure 3.5 running results after configuring Mac style
Now, without moving the program, we just change the "Mac" in the configuration file to Windows. After running, the results are as follows:
Figure 3.6 running results after being configured as Windows style
From the running results, we can see that we changed the behavior of the whole program only by modifying the configuration file (we didn't even recompile the program). This is the power of polymorphism and the effect of dependency injection.
This section discusses three basic dependency injection categories. For more information about dependency injection categories and comparison of different categories, please refer to Martin Fowler's< Inversion of Control Containers and the Dependency Injection pattern>.
3.2 reflection and dependency injection
Recall the above example of Dependency Locate. Although we used polymorphism and Abstract Factory, we didn't implement OCP thoroughly enough. Before understanding this point, friends must pay attention to where the potential extensions are. The potential extensions are "new component series" rather than "component types". That is to say, here we assume that there are three kinds of components. New components will not be added, but new appearance series may appear. If we need to add a set of Ubuntu style components, we can add UbuntuWindow, UbuntuButton, UbuntuTextBox and UbuntuFactory, And implement the corresponding interfaces respectively, which is in line with OCP, because it is an extension. However, in addition to modifying the configuration file, we have to inevitably modify the FactoryContainer. We need to add a branch condition, which destroys the OCP. Dependency injection itself does not have the ability to solve this problem, but if the language supports Reflection, this problem will be solved.
Let's think about it. The difficulty is here: the object will eventually be instantiated through "new", and "new" can only instantiate the existing class. If a new class is added in the future, the code must be modified. If we can have a method to instantiate objects not through "new", but through the name of the class, we can load classes that will appear in the future without modifying the code as long as we take the name of the class as a configuration item. Therefore, reflection gives language the ability to "see the future", which greatly increases the power of polymorphism and dependency injection.
The following is the improvement of the above example after introducing the reflection mechanism:
Figure 3.7 Dependency Locate with reflection mechanism
It can be seen that after the reflection mechanism is introduced, the structure is much simpler. A reflection factory replaces the previous batch of factories, and the Factory Container is no longer needed. Moreover, when a new component series is added in the future, the reflection factory does not need to be changed. It can be completed by changing the configuration file. The code of reflection factory and configuration file is given below.
+ View Code?
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:
+ View Code?
<?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 comply with OCP, and provides the possibility for the design of general dependency injection framework (such as IoC part and Unity in Spring.NET).
3.3 polymorphic activity and dependent injection
3.3.1 activity of polymorphism
In this section, we discuss the activity of polymorphism and its close relationship with dependent injection type selection.
First of all, I personally defined the term "polymorphic activity", because I didn't find an existing conceptual noun to express my meaning, so I made a word myself. Here, the activity of a polymorphism refers to the frequency of the changes isolated by this polymorphism. The higher the frequency, the stronger the activity, and vice versa.
As mentioned above, polymorphism can isolate changes, but the frequency of different changes is different, which makes the activity of polymorphism different. This difference affects the type selection of dependent injection.
For example, the weapon polymorphism mentioned at the beginning of this article is very active, because in that program, Role may replace multiple weapons in one run. Now we assume that Role also realizes polymorphism, which is very possible. Because many attributes and businesses of different types of characters (such as night elf, tauren, dwarf, etc.) are figured out in the game, it is very possible to realize polymorphism through an IRole or AbstractRole abstract class. However, Role will not change after instantiation (generally after the user logs in successfully), Few games allow the same player to change the Role type during operation, so once the Role is instantiated, it will not change, but if another player is instantiated (for example, another player logs in), it may change. Finally, there is another polymorphism with very low activity. For example, we are familiar with the polymorphism of data Access layer. Even if we have implemented the Access layer of SQL Server, Oracle and Access and realized dependency injection, we can hardly encounter the situation of changing the database when the program is running or frequent changes of the database in a short time.
The above different polymorphisms not only have different characteristics, but also have different purposes. They are summarized as follows:
High activity polymorphism refers to the polymorphism that the service class may change during the operation of the customer class instance.
Middle live polymorphism refers to that the service class will not change after the customer class is instantiated, but different instances existing at the same time may have different types of service classes.
Low activity polymorphism refers to that after the customer class is instantiated, the service class will not change, and all customer classes have the same type of service class at the same time.
A good example of the above three polymorphisms is the above-mentioned weapon polymorphism (high activity), role polymorphism (medium activity) and data access layer polymorphism (low activity). In addition, we say that a polymorphism is spatially stable. If all instances of the same customer class depend on the same type of service class at the same time, it is called spatially unstable polymorphism. We say that a polymorphism is time stable. If a customer class is instantiated, the service class cannot be changed again. On the contrary, it is called time unstable polymorphism. Obviously, the high activity polymorphism is unstable in time and space; The middle living polymorphism is temporally stable, but spatially unstable; The low activity polymorphism was stable in time and space.
3.3.2 injection dependent selection of different active polymorphisms
In general, highly active polymorphisms are suitable for Setter injection. Because Setter injection is the most flexible and the only injection method that allows you to change the service class during the operation of the same customer class instance. In addition, the service class type is generally specified by the context environment through the parameters of Setter, which is convenient and flexible and suitable for highly active polymorphisms with frequent changes.
For middle live polymorphisms, Constructor injection is suitable. Because the Constructor injection also specifies the service class type by the context environment through the Constructor parameters, but once the client class is instantiated, it cannot be injected again, ensuring its time stability.
For low activity polymorphism, it is suitable to use Dependency Locate and cooperate with the file configuration for dependency injection, or Setter and Constructor cooperate with the configuration file injection, because the dependency source comes from the file. If you want to change the service class, you need to change the configuration file, which ensures the temporal and spatial stability of low activity polymorphism, and the way to change the configuration file is convenient for large-scale service class replacement. (because once the low activity polymorphism changes its behavior, it is often large-scale, such as replacing the whole data access layer. If Setter and Constructor are used to pass parameters, countless places in the program need to be changed)
In essence, this choice is because different dependency injection types have different stability. You can carefully understand the close relationship between "activity", "stability" and "dependency injection type".
4 IoC Container
4.1 inevitability of IOC container
Many topics of dependency injection are discussed above. When it comes to dependency injection, we have to say IoC Container. So what is IoC Container? Let's take a look at its background first.
We know that there is a famous saying in the field of software development: don't invent the wheel again! Because software development emphasizes reuse, for the demand of frequent applications, some people always design various general frameworks and class libraries to reduce people's development burden. For example, data persistence is a very frequent requirement, so various ORM frameworks came into being; For another example, the demand for MVC has spawned a number of frameworks such as Struts to implement MVC.
With the development and maturity of object-oriented analysis and design, OOA & D is more and more widely used in various projects. However, we know that it is impossible to use OO without polymorphism, and it is impossible to use polymorphism without dependency injection. Therefore, dependency injection has become a very frequent requirement. If it is completed manually, it will not only be too heavy, but also easy to make mistakes. Coupled with the invention of reflection mechanism, people naturally began to design and develop various special frameworks for dependency injection. These components or frameworks specifically used to implement dependency injection are IOC containers.
From this point of view, the emergence of IoC Container has its historical inevitability. At present, the most famous IoC is probably the IoC component of the Spring framework on the Java platform NET platform NET and Unity.
4.2 classification of IOC container
Three dependency injection methods have been discussed earlier. However, it is difficult to classify IOC containers through these methods, because now IOC containers are well designed and support almost all dependency injection methods. However, according to the characteristics and idioms of different frameworks, IOC containers can be divided into two categories.
4.2.1 heavyweight IoC Container
The so-called heavyweight IoC Container refers to the IoC Container that generally uses the external configuration file (generally XML) as the dependency source and hosts the instantiation of various classes of the whole system. This kind of IoC Container generally undertakes the dependency injection of almost all polymorphisms in the whole system and the instantiation of all service classes. Moreover, these instantiations depend on an external configuration file. This kind of IoC Container is like defining the polymorphic structure of the whole system through a file. It has a broad vision. To control this kind of IoC Container well, it needs certain architecture design ability and rich practical experience.
Spring and spring Net is an example of a heavyweight IoC Container. Generally speaking, this IoC Container has more stability but less activity, so it is suitable for dependent injection of low activity polymorphism.
4.2.2 lightweight IoC Container
There is also an IoC Container, which generally does not rely on external configuration files, but mainly uses parameter Setter or constructor injection. This kind of IoC Container is called lightweight IoC Container. This framework is very flexible and easy to use, but it is often unstable, and the dependency points are string parameters in the program. Therefore, it is not suitable for low active polymorphisms that need large-scale replacement and relatively stable, but it has a good effect for high active polymorphisms.
Unity is a typical lightweight IoC Container.
4.3 .NET platform
4.3.1 Spring.NET
Spring.NET is a spring pair on the Java platform NET platform, the use method is very similar to spring, and the function is powerful NET platform is one of the first choices for large and medium-sized development of IoC Container. In addition to DI, spring NET also includes many functions such as AOP.
Spring.NET's official website is: http://www.springframework.net/
4.3.2 Unity
For small projects and agile teams, spring Net may be a little too heavyweight, so you can choose lightweight unity. Unity is a lightweight framework launched by Microsoft Patterns & practices team. It is very easy to use. At present, the latest version is 1.2.
Unity's official website is: http://unity.codeplex.com/