目录
介绍
背景
自定义IoC容器的工作方式
自定义IoC容器
高级模块的使用者
编码自定义IoC容器
步骤1:在Visual Studio中创建一个空白解决方案,并创建以下项目
步骤2:将以下代码添加到Container.cs(DIP.MyIoCContainer项目)
步骤3:构建DIP.Abstractions项目
步骤4:开发DIP.HighLevelModule
步骤5:实现抽象(DIP.Implementation)
步骤6:最终开发实际上将使用HighLevelModule(DIP.Consumer)的消费者类
运行应用程序
总结
- 下载源文件-48.9 KB
这是我的第三篇有关依赖反转原理,IoC容器和依赖注入的文章。在本文的上半部分,我试图解释什么是IoC容器。如果您没有阅读本文的前面部分,请使用下面的链接阅读它们,以更好地了解DIP,IoC和依赖注入概念的要求:
- 第1部分:依赖倒置原则
- 第2部分:控制反转和IoC容器
- 第3部分:自定义IoC容器(当前正在阅读)
- 第4部分:具有生命周期选项的自定义IoC容器
- 第5部分:使用Microsoft Unity的依赖项注入(DI)
在本文中,我将解释如何创建自定义IoC容器,以及如何将其用于遵守依赖反转原理。
背景有很多方法可以实现IoC容器。在本文中,我将解释自定义IoC容器的开发,该容器的工作方式与Microsoft Unity Container的工作方式类似。在本文的这一部分中,我将解释基本的实现,而在本文的后续部分中,将向其中添加更多功能,以便您可以对Microsoft Unity Container的工作有一个了解。
您应该具有反射的实用知识,才能理解本文中编写的代码。
自定义IoC容器的工作方式下图显示了自定义IoC容器的工作方式。每个部分使用一条水平线分开。下图描述的场景包括三个部分:
高级模块
如果您还记得的话,我在本文的第1部分中给出的Copy示例充当高级模块。DIP表示,高级模块不应依赖于低级模块的实现,而应公开一个抽象,低级模块应遵循该抽象。
每当需要低级模块实例时,它都会借助容器。上图中的以下语句返回低级模块的实例。
IReader object = customContainer.Resolve();
高级模块不必理会那些实现IReader接口的类。同样,我们不需要在包含高级模块的项目中添加包含低级模块的项目的引用。容器的责任是创建低级模块(依赖)的实例并将其返回到高级模块。
自定义IoC容器有两种主要方法,它们将在外部公开。那些是:
- Register()
- Resolve()
它维护一个字典,其中TypeToResolve和ResolvedType的组合将使用Register()方法以KeyValuePair的形式存储。
而Resolve方法首先验证TypeToResolve是否已在字典中注册,如果已注册,则它尝试使用反射创建其对应的ResolvedType实例。
注意:可以通过多种方式实现自定义IoC容器。这种实现是方法之一。
高级模块的使用者基本上,这是使用高级模块执行操作的地方。它可以是Windows/Web/控制台应用程序。该应用程序应该了解低级实现。
对于我们的高级模块提供的IReader抽象示例,有一个名为KeyBoardReader的实现类。消费者的项目应参考包含低级模块实现的项目。因此,只要在低级实现中添加了其他内容,使用者就可以更改(取决于使用者的实现),但是高级模块不会更改,因为它确实引用了低级模块,并且不知道谁是IReader抽象的实现者。
编码自定义IoC容器 步骤1:在Visual Studio中创建一个空白解决方案,并创建以下项目- DIP.Abstractions (类库项目)
- IReader.cs
- IWriter.cs
- DIP.HighLevelModule (类库项目)
- Copy.cs
- DIP.MyIoCContainer (类库项目)
- Container.cs
- DIP.Implementation (类库项目)
- KeyboardReader.cs
- PrinterWriter.cs
- DIP.Consumer (控制台应用程序)
- Program.cs
这里,要注意的重要事项是项目引用(项目之间的依赖关系)。
- DIP.Abstractions 项目没有引用任何项目,因为它独立于一切。
- DIP.HighLevelModule 该项目确实有两个引用:
- 引用DIP.Abstractions:依其uses Abstractions而定,并不取决于实现
- 引用DIP.MyIoCContainer:由于它将使用容器来解决依赖关系。
- DIP.Implementations有一个引用DIP.Abstractions,因为它会implement the abstractions
- DIP.MyIoCContainer没有引用任何项目,因为它是一个库并且不依赖任何项目。
- DIP.Consumer 引用了以下项目:
- DIP.Abstractions&DIP.Implementation:必实现DI Registration.
- DIP.MyIocContainer:它将用作容器,该容器将传递到DIP.HighLevelModule以解决依赖关系。
- DIP.HighLevelModule:它将使用DIP.HighLevelModule来执行操作。
注意:在上述项目依赖项中,您可以注意到DIP.HighLevelModule和DIP.Implementation之间没有依赖项。因此,在DIP.implementation中添加或删除类不会更改DIP.HighLevelModule中的任何内容。
步骤2:将以下代码添加到Container.cs(DIP.MyIoCContainer项目)public class Container
{
private Dictionary iocMap = new Dictionary();
public void Register()
{
if (iocMap.ContainsKey(typeof(TypeToResolve)))
{
throw new Exception(string.Format
("Type {0} already registered.", typeof(TypeToResolve).FullName));
}
iocMap.Add(typeof(TypeToResolve), typeof(ResolvedType));
}
public T Resolve()
{
return (T)Resolve(typeof(T));
}
public object Resolve(Type typeToResolve)
{
// Find the registered type for typeToResolve
if (!iocMap.ContainsKey(typeToResolve))
throw new Exception(string.Format("Can't resolve {0}.
Type is not registered.", typeToResolve.FullName));
Type resolvedType = iocMap[typeToResolve];
// Try to construct the object
// Step-1: find the constructor
// (ideally first constructor if multiple constructors present for the type)
ConstructorInfo ctorInfo = resolvedType.GetConstructors().First();
// Step-2: find the parameters for the constructor and try to resolve those
List paramsInfo = ctorInfo.GetParameters().ToList();
List resolvedParams = new List();
foreach (ParameterInfo param in paramsInfo)
{
Type t = param.ParameterType;
object res = Resolve(t);
resolvedParams.Add(res);
}
// Step-3: using reflection invoke constructor to create the object
object retObject = ctorInfo.Invoke(resolvedParams.ToArray());
return retObject;
}
}
- iocMap
一个Dictionary成员,它将保持已注册TypeToResolve的列表和它对应的ResolvedType类型。
- Register
用于获取TypeToResolve类型语法实例的方法:void Register();
- Resolve
用于获取TypeToResolve类型语法实例的方法: Resolve();
步骤3:构建DIP.Abstractions项目该项目包含了HighLevelModule用于执行动作的抽象(接口)。在我们的情况下,我们有两个抽象:
IReader.cs
public interface IReader
{
string Read();
}
IWriter.cs
public interface IWriter
{
void Write(string data);
}
步骤4:开发DIP.HighLevelModule
该模块将使用抽象来执行操作。在我们的例子中,我们有Copy.cs,它将从IReader复制到IWriter。
public class Copy
{
private Container _container;
private IReader _reader;
private IWriter _writer;
public Copy(Container container)
{
_container = container;
_reader = _container.Resolve();
_writer = _container.Resolve();
}
public void DoCopy()
{
string stData = _reader.Read();
_writer.Write(stData);
}
步骤5:实现抽象(DIP.Implementation)
该项目包含在DIP.Abstractions中定义的抽象的实现。单个抽象可以有任意多个实现。例如,我们可以有KeyboardReader,FileReader等从IReader接口实现。
为了使类易于理解,我定义了两个类,KeyboardReader(实现IReader)和PrinterWriter(实现IWriter)。
注意:这并未实现键盘的实际读数。我只是为了说明IoC容器的用法而不是如何从键盘读取而保持简单。
KeyboardReader.cs
public class KeyboardReader:IReader
{
public string Read()
{
return "Reading from \"Keyboard\"";
}
}
PrinterWriter.cs
public class PrinterWriter:IWriter
{
public void Write(string data)
{
Console.WriteLine(string.Format("Writing to \"Printer\": [{0}]", data));
}
}
步骤6:最终开发实际上将使用HighLevelModule(DIP.Consumer)的消费者类
它是一个与用户交互的控制台应用程序。
Program.cs
class Program
{
static void Main(string[] args)
{
Container container = new Container();
DIRegistration(container);
Copy copy = new Copy(container);
copy.DoCopy();
Console.Read();
}
static void DIRegistration(Container container)
{
container.Register();
container.Register();
}
}
您会注意到它包含另一个名为DIRegistration()的方法。它基本上进行依赖项的注册。声明container.Register()寄存器映射IReader和KeyboardReader,所以,每当有需要实现IReader对象,一个KeyboardReader实例将被返回。
注意:有许多方法可以注册依赖项。我们还可以使用配置文件来实现DI注册。
运行应用程序如果运行开发的应用程序,将获得以下输出:
在这里,您可以扩展实现而无需更改高级程序。您唯一需要做的就是更改注册。
总结在本文的这一部分,我试图解释如何开发简单的IoC容器。希望您发现这个主题很好并且易于理解。在下一章中,我将介绍一些高级技术,以使自定义IoC容器更加现实和有用。