Iii. Factory Model Based on configuration file and reflection
To eliminate the dependency of mainapp on other components, we introduce the factory mode and use the reflection technology provided by. Net to assemble objects according to the Assembly procedure specified in the configuration file. This part of the Code only provides a function demonstration. If the actual application still needs to be improved (we recommend using some formed IOC frameworks, such as spring.net or Castle ). The dependency between components of the transformed system is as follows:
It can be seen that this time the real "programming for interfaces" is implemented ". All components only depend on interfaces. The objects required by mainapp are dynamically created and assembled by the factory based on the configuration file. When the system requirements change, you only need to modify the configuration file. In addition, there is no dependency between mainapp, sayhello, and hellogenerator, which achieves loose coupling.
How is this implemented? First, we must be able to parse the information in the configuration file and then create an object containing the relevant information. Finally, the object is created using the reflection mechanism based on the information. First, let's take a look at the content contained in the configuration file:
<?xml version="1.0" encoding="utf-8" ?><configuration> <configSections> <sectionGroup name="IocInCSharp"> <section name="objects" type="IocInCSharp.ConfigHandler, MainApp" /> </sectionGroup> </configSections> <IocInCSharp> <objects> <object name="SayHello" assembly="SayHello.dll" typeName="IocInCSharp.SayHello"> <property name="HelloGenerator" assembly="HelloGenerator.dll" typeName="IocInCSharp.CnHelloGenerator"></property> </object> </objects> </IocInCSharp></configuration>
We can see that we have implemented an iocincsharp. confighandler class to process the content in the iocincsharp \ objects node in the configuration file. The confighandler class will process and create a configinfo object based on the content of the node (for the code about configinfo, objectinfo, and propertyinfo, you can view the source code by yourself. I will not go into details here ). The confighandler class code is implemented as follows:
using System;using System.Configuration;using System.Xml;namespace IocInCSharp{ public class ConfigHandler:IConfigurationSectionHandler { public object Create(object parent, object configContext, System.Xml.XmlNode section) { ObjectInfo info; PropertyInfo propInfo; ConfigInfo cfgInfo = new ConfigInfo(); foreach(XmlNode node in section.ChildNodes) { info = new ObjectInfo(); info.name = node.Attributes["name"].Value; info.assemblyName = node.Attributes["assembly"].Value; info.typeName = node.Attributes["typeName"].Value; foreach(XmlNode prop in node) { propInfo = new PropertyInfo(); propInfo.propertyName = prop.Attributes["name"].Value; propInfo.assemblyName = prop.Attributes["assembly"].Value; propInfo.typeName = prop.Attributes["typeName"].Value; info.properties.Add(propInfo); } cfgInfo.Objects.Add(info); } return cfgInfo; } }}
Through confighandler parsing, we finally get a configinfo instance. The factory generates and assembles the required Objects Based on the configuration information contained in the instance using reflection technology. The sayhellofactory code is as follows:
using System;using System.IO;using System.Configuration;using System.Reflection;namespace IocInCSharp{ public class SayHelloFactory { public static object Create(string name) { Assembly assembly; object o = null; object p; string rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; ConfigInfo cfgInfo = (ConfigInfo)ConfigurationSettings.GetConfig("IocInCSharp/objects"); ObjectInfo info = cfgInfo.FindByName(name); if(info != null) { assembly = Assembly.LoadFile(rootPath + info.assemblyName); o = assembly.CreateInstance(info.typeName); Type t = o.GetType(); for(int i=0; i<info.properties.Count; i++) { PropertyInfo prop = (PropertyInfo)info.properties[i]; assembly = Assembly.LoadFile(rootPath + prop.assemblyName); p = assembly.CreateInstance(prop.typeName); t.InvokeMember(prop.propertyName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p}); } } return o; } }}
In the above Code, pay attention to the usage of the three commands:
assembly = Assembly.LoadFile(rootPath + prop.assemblyName);p = assembly.CreateInstance(prop.typeName);t.InvokeMember(prop.propertyName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p});
Assembly.LoadFile()
Used to load external files;assembly.CreateInstance()
Creates an object of the specified type based on the loaded assembly;t.InvokeMember(prop.propertyName, ........BindingFlags.SetProperty, null, o, new Object[] {p})
Use the reflection mechanism to set attribute values for the created object.
In this way, our factory dynamically loads the Assembly according to the configuration file, dynamically creates objects, and sets attributes. With this factory, the content in mainapp is very simple:
using System;namespace IocInCSharp{ public class MainApp { public static void Main() { ISayHello sayHello = (ISayHello)SayHelloFactory.Create("SayHello"); if(sayHello != null) sayHello.SayHelloTo("zhenyulu"); else Console.WriteLine("Got an Error!"); } }}
Currently, mainapp only depends on interfaces and does not depend on other components. It achieves loose coupling. In this example, you can tryIocInCSharp.CnHelloGenerator
ChangeIocInCSharp.EnHelloGenerator
To see if the output content is changed from Chinese to English. This is the effect of "injection.
From the above example, we can see that through the custom configuration file and the reflection technology in. net, we can develop the IOC application and assemble the corresponding objects according to the configuration file information. However, reflection programming has a high technical threshold, and in actual application, the configuration file format and handler design are not as simple as the above Code. Fortunately, we now have a lot of IOC containers to choose from. They all provide a complete dependency injection method and are more mature and stable than self-writing code. Using these frameworks allows programmers to complete "injection" in three or two lines of code. In our next case, we will use spring.net to implement dependency injection. We will find that only a few lines of code can be added and the configuration file can be changed to easily implement dependency injection. (To be continued)