.net实现依赖注入

1. 问题的提出

开发中,尤其是大型项目的开发中,为了降低模块间、类间的耦合关系,比较提倡基于接口开发,但在实现中也必须面临最终是“谁”提供实体类的问题。Martin Fowler在《Inversion of Control Containers and the Dependency Injection pattern》中也提到了标准的三种实现方式——Constructor Injection、Setter Injection和Interface Injection,很全面的阐释了这个问题。

对于C#而言,由于语法元素上本身要比Java丰富,如何实施注入还有些技巧和特色之处。这方面微软的ObjectBuilder是个不错的教科书,对三种标准方式的实现也都很到位,但就是有些庞大了。

本文中,笔者借鉴Martin Fowler的撰文,也通过一些精简的代码片断向读者介绍C#实现依赖注入的基本技巧。

我有个习惯,每天晚上要看天气预报,就以这个开始好了,先定义待注入对象的抽象行为描述,然后增加一个假的实体类,相关代码和单元测试如下:

C# 
using System; 
namespace VisionLogic.Training.DependencyInjection.Scenario 

/// <summary> 
/// 抽象注入对象接口 
/// </summary> 
public interface IWeatherReader 

string Current { get;} 

}

C# 
using System; 
namespace VisionLogic.Training.DependencyInjection.Scenario.Raw 

/// <summary> 
/// 伪造的一个实现类 
/// </summary> 
class FakeWeatherReader : IWeatherReader 

public string Current { get { return string.Empty; } } 
}

/// <summary> 
/// 客户程序 
/// </summary> 
public class Client 

protected IWeatherReader reader = new FakeWeatherReader();

public virtual string Weather 

get 

string current = reader.Current; 
switch (current) 

case "s": return "sunny"; 
case "r": return "rainy"; 
case "c": return "cloudy"; 
default: 
return "unknown"; 





Unit Test 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario; 
using VisionLogic.Training.DependencyInjection.Scenario.Raw; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest.Raw 

[TestClass] 
public class WeatherReaderTest 

[TestMethod] 
public void Test() 

Client client = new Client(); 
Assert.AreEqual<string>("unknown", client.Weather); 


}

问题就出现了,虽然美好的愿望是Client仅仅依赖抽象的IWeatherReader,但之前总要和一个实体类“轧”一道,那么实际的效果就是实体类作了修改、重新编译了,Client也要处理,没有真正达到隔离的目的。依赖注入通过引入第三方责任者的方法,相对好的梳理了这个关系,这位重要的角色就是一个Assembler类,他和实体类型打交道,对Client而言他总是可以根据约定,加工出需要的IWeatherReader。

2.进一步的分析

看上去,Client被解放了,但又套住了Assembler,为了尽量让他与实体类间松散些需要做什么呢?

 首先要完成自己的职责:可以找到合适的实现类实例,不管是重新构造一个还是找个现成的。 
 既要根据需要加工接口IWeatherReader,又要让自己尽量不与大量的实体类纠缠在一起,最好的办法就是从.Net Framework中再找到一个“第三方”,这里选中了System.Activator。

 还有就是当客户程序调用Assembler的时候,它需要知道需要通过哪个实现类的实例返回,该项工作一方面可以通过一个字典完成,也可以通过配置解决,两者应用都很普遍,怎么选择呢——抽象,提取一个接口,然后都实现。 
由于本文主要介绍依赖注入的实现,为了简单起见,采用一个伪造的内存字典方式,而非基于System.Configuration的配置系统实现一个Assembler的协同类。

C# 新增一个用于管理抽象类型——实体类型映射关系的类型ITypeMap

using System; 
using System.Collections.Generic; 
namespace VisionLogic.Training.DependencyInjection.Scenario 

/// <summary> 
/// 考虑到某些类型没有无参的构造函数,增加了描述构造信息的专门结构 
/// </summary> 
public class TypeConstructor 

private Type type; 
private object[] constructorParameters; 
public TypeConstructor(Type type, params object[] constructorParameters) 

this.type = type; 
this.constructorParameters = constructorParameters; 

public TypeConstructor(Type type) : this(type, null) { }

public Type Type { get { return type; } } 
public object[] ConstructorParameters { get { return constructorParameters; } } 
}

/// <summary> 
/// 管理抽象类型与实体类型的字典类型 
/// </summary> 
public interface ITypeMap 

TypeConstructor this[Type target]{get;} 


C# 实现一个Assembler类型,为了示例方便,同时实现了一个ITypeMap和IWeatherReader 
using System; 
using System.Collections.Generic; 
namespace VisionLogic.Training.DependencyInjection.Scenario 

/// <summary> 
/// 测试用的实体类 
/// </summary> 
public class WeatherReaderImpl : IWeatherReader 

private string weather; 
public WeatherReaderImpl(string weather) 

this.weather = weather; 
}

public string Current 

get { return weather; } 

}

/// <summary> 
/// 管理抽象类型与实际实体类型映射关系,实际工程中应该从配置系统、参数系统获得。 
/// 这里为了示例方便,采用了一个纯内存字典的方式。 
/// </summary> 
public class MemoryTypeMap : ITypeMap 

private Dictionary<Type, TypeConstructor> dictionary = 
new Dictionary<Type, TypeConstructor>(); 
public static readonly ITypeMap Instance;

/// <summary> 
/// Singleton 
/// </summary> 
private MemoryTypeMap(){} 
static MemoryTypeMap() 

MemoryTypeMap singleton = new MemoryTypeMap(); 
// 注册抽象类型需要使用的实体类型 
// 该类型实体具有构造参数,实际的配置信息可以从外层机制获得。 
singleton.dictionary.Add(typeof(IWeatherReader), new TypeConstructor( 
typeof(WeatherReaderImpl), "s")); 
Instance = singleton; 
}

/// <summary> 
/// 根据注册的目标抽象类型,返回一个实体类型及其构造参数数组 
/// </summary> 
/// <param name="type"></param> 
/// <returns></returns> 
public TypeConstructor this[Type type] 

get 

TypeConstructor result; 
if (!dictionary.TryGetValue(type, out result)) 
return null; 
else 
return result; 


}

public class Assembler<T> 
where T : class 

/// <summary> 
/// 其实TypeMap工程上本身就是个需要注入的类型,可以通过访问配置系统获得, 
/// 这里为了示例的方便,手工配置了一些类型映射信息。 
/// </summary> 
private static ITypeMap map = MemoryTypeMap.Instance;

public T Create() 

TypeConstructor constructor = map[typeof(T)]; 
if (constructor != null) 

if (constructor.ConstructorParameters == null) 
return (T)Activator.CreateInstance(constructor.Type); 
else 
return (T)Activator.CreateInstance( 
constructor.Type, constructor.ConstructorParameters); 

else 
return null; 



Unit Test 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest 

[TestClass()] 
public class AssemblerTest 

[TestMethod] 
public void Test() 

IWeatherReader reader = new Assembler<IWeatherReader>().Create(); 
Assert.IsNotNull(reader); 
Assert.AreEqual<System.Type>(typeof(WeatherReaderImpl), reader.GetType()); 


}

 3.经典方式下的注入实现

    在完成了Assembler这个基础环境后,就是怎么注入的问题了,下面是对三种方式的经典方法实现:

    3.1 Constructor Injection方式 

Unit Test - Constructor 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest 

[TestClass] 
public class ConstructorInjectionTest 

class Client 

private IWeatherReader reader; 
public Client(IWeatherReader reader) 

this.reader = reader; 

}

[TestMethod] 
public void Test() 

IWeatherReader reader = new Assembler<IWeatherReader>().Create(); 
Client client = new Client(reader); 
Assert.IsNotNull(client); 


}

    3.2 Setter Injection方式 

Unit Test - Setter 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest 

[TestClass] 
public class SetterInjectionTest 

class Client 

private IWeatherReader reader; 
public IWeatherReader Reader 

get { return reader; } 
set { reader = value; } 

}

[TestMethod] 
public void Test() 

IWeatherReader reader = new Assembler<IWeatherReader>().Create(); 
Client client = new Client(); 
client.Reader = reader; 
Assert.IsNotNull(client.Reader); 


}

    3.3 Interface Injection方式

Unit Test - Interface 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest 

[TestClass] 
public class InterfaceInjectionTest 

interface IClientWithWeatherReader 

IWeatherReader Reader { get; set;} 
}

class Client : IClientWithWeatherReader 

private IWeatherReader reader;

#region IClientWithWeatherReader Members 
public IWeatherReader Reader 

get { return reader; } 
set { reader = value; } 

#endregion 
}

[TestMethod] 
public void Test() 

IWeatherReader reader = new Assembler<IWeatherReader>().Create(); 
Client client = new Client(); 
IClientWithWeatherReader clientWithReader = client; 
clientWithReader.Reader = reader; 
Assert.IsNotNull(clientWithReader.Reader); 



 4. 用属性(Attribute)注入

  C#还可以通过Attribute注入,Enterprise Library中大量使用这种方式将各种第三方机制加入到类系统中。例如:

 •运行监控需要的Performance Counter。 
 •用于构造过程的指标信息。 
 •用于日志、密码处理。 
 •等等

  注:Java语言虽然发展比较慢,但在Java 5种也提供了类似的Annotation的机制,换了个名字省去被评估为“抄袭”的嫌疑。)

  为了演示方便,下面设计一个应用情景: 
    
    Scenario 
    1、 应用需要一个集中的机制了解系统中实际创建过多少个特定类型对象的实例,用于评估系统的Capacity要求。 
    2、 为了防止系统资源被用尽,需要控制每类对象实例数量。

  怎么实现呢?如下:

 •增加一个内存的注册器,登记每个类已经创建过的实例实例数量。 
 •然后给每个类贴个标签——Attribute,让Assembler在生成的对象的时候根据标签的内容把把登记到注册器。

4.1定义抽象业务实体

C# 
using System; 
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer 

/// <summary> 
/// 抽象的处理对象 
/// </summary> 
public interface IObjectWithGuid 

string Guid { get; set;} 


定义需要注入的限制接口,并用一个Attribute管理它 
C# 
using System; 
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer 

/// <summary> 
/// 需要注入的用以限制最大数量的接口 
/// </summary> 
public interface ICapacityConstraint 

int Max { get;} 
}

public class CapacityConstraint : ICapacityConstraint 

private int max;

public CapacityConstraint(){this.max = 0;} // 默认情况下不限制 
public CapacityConstraint(int max) { this.max = max; } 
public int Max { get { return max; } } 
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 
public class ConstraintAttribute : Attribute 

private ICapacityConstraint capacity;

public ConstraintAttribute(int max) { this.capacity = new CapacityConstraint(max); } 
public ConstraintAttribute() { this.capacity = null; }

public ICapacityConstraint Capacity { get { return capacity; } } 

}

Assembler上增加通过Attribute注入限制的响应 
C# 
using System; 
using System.Collections.Generic; 
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer 

public class Assembler 

/// <summary> 
/// 登记相关类型对“最大容量”属性的使用情况 
/// </summary> 
private IDictionary<Type, ConstraintAttribute> attributeRegistry = 
new Dictionary<Type, ConstraintAttribute>(); 
/// <summary> 
/// 登记每个类型(如须受到“最大容量”属性限制的话),实际已经创建的对象数量 
/// </summary> 
private IDictionary<Type, int> usageRegistry = new Dictionary<Type, int>();

public T Create<T>() 
where T : IObjectWithGuid, new() 

ICapacityConstraint constraint = GetAttributeDefinedMax(typeof(T)); 
if ((constraint == null) || (constraint.Max <= 0)) // max <= 0 代表是不需要限制数量的。 
return InternalCreate<T>(); 
else 

if (usageRegistry[typeof(T)] < constraint.Max) // 检查是否超出容量限制 

usageRegistry[typeof(T)]++; // 更新使用情况注册信息 
return InternalCreate<T>(); 

else 
return default(T); 

}

// helper method 
// 直接生成特定实例,并setter 方式注入其guid。 
private T InternalCreate<T>() 
where T : IObjectWithGuid, new() 

T result = new T(); 
result.Guid = Guid.NewGuid().ToString(); 
return result; 
}

/// helper method. 
// 获取特定类型所定义的最大数量, 同时视情况维护attributeRegistry 和usageRegistry 的注册信息。 
private ICapacityConstraint GetAttributeDefinedMax(Type type) 

ConstraintAttribute attribute = null; 
if (!attributeRegistry.TryGetValue(type, out attribute)) //新的待创建的类型 

// 填充相关类型的“最大容量”属性注册信息 
object[] attributes = type.GetCustomAttributes(typeof(ConstraintAttribute), false); 
if ((attributes == null) || (attributes.Length <= 0)) 
attributeRegistry.Add(type, null); 
else 

attribute = (ConstraintAttribute)attributes[0]; 
attributeRegistry.Add(type, attribute); 
usageRegistry.Add(type, 0); // 同时补充该类型的使用情况注册信息 


if (attribute == null) 
return null; 
else 
return attribute.Capacity; 


}

4.2对方案的测试 

C# 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using VisionLogic.Training.DependencyInjection.Scenario.Attributer; 
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest.Attributer 

[TestClass()] 
public class AssemblerTest 

public abstract class ObjectWithGuidBase : IObjectWithGuid 

protected string guid; 
public virtual string Guid 

get { return guid; } 
set { guid = value; } 

}

[Constraint(2)] // 通过属性注入限制 
public class ObjectWithGuidImplA : ObjectWithGuidBase { }

[Constraint(0)] // 通过属性注入限制 
public class ObjectWithGuidImplB : ObjectWithGuidBase { }

[Constraint(-5)] // 通过属性注入限制 
public class ObjectWithGuidImplC : ObjectWithGuidBase { }

public class ObjectWithGuidImplD : ObjectWithGuidBase { }

[TestMethod] 
public void Test() 

Assembler assembler = new Assembler(); 
for (int i = 0; i < 2; i++) 
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplA>()); 
Assert.IsNull(assembler.Create<ObjectWithGuidImplA>()); // 最多两个 
for (int i = 0; i < 100; i++) 
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplB>()); // 不限制 
for (int i = 0; i < 100; i++) 
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplC>()); // 不限制 
for (int i = 0; i < 100; i++) 
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplD>()); // 不限制 


}

5. 进一步讨论 

    上面的例子虽然仅仅通过Attribute注入了一个容量限制接口,但完全可以为他增加一个容器,可以把一组限制接口借助ConstraintAttribute这个通道注入进去,并在Assembler中生效。

6.其他问题 

    实际项目中,为了满足多核系统的需要,Assembler往往和目标对象分别运行在主进程和具体某个线程之中,如何线程安全的注入是必须面临并谨慎设计的问题。

from:http://www.qqread.com/csharp/s396523_4.html

.net实现依赖注入的更多相关文章

  1. webapi - 使用依赖注入

    本篇将要和大家分享的是webapi中如何使用依赖注入,依赖注入这个东西在接口中常用,实际工作中也用的比较频繁,因此这里分享两种在api中依赖注入的方式Ninject和Unity:由于快过年这段时间打算 ...

  2. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  3. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  4. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  5. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

  6. ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

    在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...

  7. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  8. 模拟AngularJS之依赖注入

    一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...

  9. angular2系列教程(八)In-memory web api、HTTP服务、依赖注入、Observable

    大家好,今天我们要讲是angular2的http功能模块,这个功能模块的代码不在angular2里面,需要我们另外引入: index.html <script src="lib/htt ...

  10. angularjs 依赖注入--自己学着实现

    在用angular依赖注入时,感觉很好用,他的出现是 为了"削减计算机程序的耦合问题" ,我怀着敬畏与好奇的心情,轻轻的走进了angular源码,看看他到底是怎么实现的,我也想写个 ...

随机推荐

  1. 三星Galaxy s4(i9505)得到完美root权限教程

    三星Galaxy s4(i9505)完美获取root权限教程 论坛上贴吧上关于三星s4 i9505 root的介绍有非常多,方法多种多样.今天小编来介绍一种使用root软件来实现三星i9505一键ro ...

  2. poj1251--Kruskal

    /* * poj1251-- Kruskal * date 2014/7/15 * state AC */ #include <iostream> #include <algorit ...

  3. 基于Js实现的UrlEncode和UrlDecode函数代码

    <script language="javascript">//UrlEncode函数function UrlEncode(str){  var ret="& ...

  4. 从Ubuntu12.04升级到Ubuntu 14.04之后,系统将无法启动

    进入Ubuntu启动界面.通常有几个选项,Ubuntu,Ubuntu先进... 输入e键,进入grub的设置界面.将里面的ro改动为rw就可以. 以上能够启动,临时性的设置 可是为了永久保存这个设置, ...

  5. java-新浪微博开放平台——话题跟踪

    代码 网盘地址:http://pan.baidu.com/s/1pJ1D0Kz

  6. Swift中文教程(四)--函数与闭包

    原文:Swift中文教程(四)--函数与闭包 Function 函数 Swift使用func关键字来声明变量,函数通过函数名加小括号内的参数列表来调用.使用->来区分参数名和返回值的类型: fu ...

  7. 转载使用Flurl制作可复用的分页组件

    使用Flurl制作可复用的分页组件 使用ASP.NET MVC查询时,一直使用MvcPaging组件,虽然需要自定义MvcPaging.Pager才能达到我想要的效果,但在没有较好的URL库时,还是这 ...

  8. 对[foreach]的浅究到发现[yield]

    原文:对[foreach]的浅究到发现[yield] 闲来无事,翻了翻以前的代码,做点总结,菜鸟从这里起航,呵呵. 一.List的foreach遍历 先上代码段[1]: class Program { ...

  9. 使用Bootstrap

    开始使用Bootstrap 作为一名Web开发者而言,如果不借助任何前端框架,从零开始使用HTML和CSS来构建友好的页面是非常困难的.特别是对于Windows Form的开发者而言,更是难上加难. ...

  10. C--指针数组

    一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,他们都有相应的地址,所谓数组的指针是指数组的其实地址,数组元素的指针是数组元素的地址. 一个数组是有连续的一块内存单元组成 ...