C#依赖注入实例
http://qing.weibo.com/tj/400082fa33001h7x.html
1.5 实现依赖注入1.5.1 背景介绍
设计模式中,尤其是结构型模式很多时候解决的就是对象间的依赖关系,变依赖具体为依赖抽象。平时开发中如果发现客户程序依赖某个(或某类)对象,我们常常会对它们进行一次抽象,形成抽象的抽象类、接口,这样客户程序就可以摆脱所依赖的具体类型。
这个过程中有个环节被忽略了——谁来选择客户程序需要的满足抽象类型的具体类型呢?通过后面的介绍你会发现很多时候创建型模式可以比较优雅地解决这个问题。但另一问题出现了,如果您设计的不是具体业务逻辑,而是公共库或框架程序,这时候您是一个“服务方”,不是您调用那些构造类型,而是它们把抽象类型传给您,怎么松散地把加工好的抽象类型传递给客户程序就是另一回事了。
这个情形也就是常说的“控制反转”,IOC:Inverse of Control;框架程序与抽象类型的调用关系就像常说的好莱坞规则:Don’t call me, I’ll call you.
参考Martin Fowler在《Inversion of Control Containers and the Dependency Injection pattern》一文,我们可以采用“依赖注入”的方式将加工好的抽象类型实例“注入”到客户程序中,本书的示例也将大量采用这种方式将各种依赖项“注入”到模式实现的外部——客户程序。下面我们结合一个具体的示例看看为什么需要依赖注入,以及Martin Fowler文中提到的三种经典方式,然后依据C#语言的特质,再扩展出一个基于Attribter方式注入(参考原有的Setter命名,这里将基于Attribute的方法称为Attributer)。
1.5.2 示例情景
客户程序需要一个提供System.DateTime类型当前系统时间的对象,然后根据需要仅仅把其中的年份部分提取出来,因此最初的实现代码如下:
C#
using System;
using System.Diagnostics;
namespace MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example1
{
class TimeProvider
{
public DateTime CurrentDate { get { return DateTime.Now; } }
}
public class Client
{
public int GetYear()
{
TimeProvider timeProvier = new TimeProvider();
return timeProvier.CurrentDate.Year;
}
}
}
后来因为某种原因,发现使用.NET Framework自带的日期类型精度不够,需要提供其他来源的TimeProvider,确保在不同精度要求的功能模块中使用不同的TimeProvider。这样问题集中在TimeProvider的变化会影响客户程序,但其实客户程序仅需要抽象地使用其获取当前时间的方法。为此,增加一个抽象接口,确保客户程序仅依赖这个接口ITimeProvider,由于这部分客户程序仅需要精确到年,因此它可以使用一个名为SystemTimeProvider (:ITimeProvider)的类型。新的实现代码如下:
C#
using System;
namespace
MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example2
{
interface ITimeProvider
{
DateTime CurrentDate { get;}
}
class TimeProvider : ITimeProvider
{
public DateTime CurrentDate { get { return DateTime.Now; } }
}
public class Client
{
public int GetYear()
{
ITimeProvider timeProvier = new TimeProvider();
return timeProvier.CurrentDate.Year;
}
}
}
这样看上去客户程序后续处理权都依赖于抽象的ITimeProvider,问题似乎解决了?没有,它还要知道具体的SystemTimeProvider。因此,需要增加一个对象,由它选择某种方式把ITimeProvider实例传递给客户程序,这个对象被称为Assembler。新的结构如图1-19所示。
图1-19 增建装配对象后新的依赖关系
其中,Assembler的职责如下:
l 知道每个具体TimeProviderImpl的类型。
l 可根据客户程序的需要,将抽象ITimeProvider反馈给客户程序。
l 本身还负责对TimeProviderImpl的创建。
下面是一个Assembler的示例实现:
C#
public class Assembler
{
/// <summary>
/// 保存“抽象类型/实体类型”对应关系的字典
/// </summary>
private static Dictionary<Type, Type> dictionary =
new Dictionary<Type, Type>();
static Assembler()
{
// 注册抽象类型需要使用的实体类型
// 实际的配置信息可以从外层机制获得,例如通过配置定义
dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider));
}
/// 根据客户程序需要的抽象类型选择相应的实体类型,并返回类型实例
/// <returns>实体类型实例</returns>
public object Create(Type type) // 主要用于非泛型方式调用
{
if ((type == null) || !dictionary.ContainsKey(type)) throw new
NullReferenceException();
Type targetType = dictionary[type];
return Activator.CreateInstance(targetType);
}
/// <typeparam name="T">抽象类型(抽象类/接口/或者某种基类)</typeparam>
public T Create<T>() // 主要用于泛型方式调用
{
return (T)Create(typeof(T));
}
}
1.5.3 Constructor注入
构造函数注入,顾名思义,就是在构造函数的时候,通过Assembler或其他机制把抽象类型作为参数传递给客户类型。这种方式虽然相对其他方式有些粗糙,而且仅在构造过程中通过“一锤子”方式设置好,但很多时候我们设计上正好就需要这种Read Only的注入方式。其实现方式如下:
C#
/// 在构造函数中注入
class Client
{
private ITimeProvider timeProvider;
public Client(ITimeProvider timeProvider)
{
this.timeProvider = timeProvider;
}
}
Unit Test
[TestClass]
public class TestClient
{
[TestMethod]
public void Test()
{
ITimeProvider timeProvider =
(new Assembler()).Create<ITimeProvider>();
Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例
Client client = new Client(timeProvider); // 在构造函数中注入
}
}
1.5.4 Setter注入
Setter注入是通过属性赋值的办法解决的,由于Java等很多语言中没有真正的属性,所以Setter注入一般通过一个set()方法实现,C#语言由于本身就有可写属性,所以实现起来更简洁,更像Setter。相比较Constructor方式而言,Setter给了客户类型后续修改的机会,它比较适应于客户类型实例存活时间较长,但Assembler修改抽象类型指定的具体实体类型相对较快的情景;不过也可以由客户程序根据需要动态设置所需的类型。其实现方式如下:
C#
/// 通过Setter实现注入
class Client
{
private ITimeProvider timeProvider;
public ITimeProvider TimeProvider
{
get { return this.timeProvider; } // getter本身和以Setter方式实现
注入没有关系
set { this.timeProvider = value; } // Setter
}
}
Unit Test
[TestClass]
public class TestClient
{
[TestMethod]
public void Test()
{
ITimeProvider timeProvider =
(new Assembler()).Create<ITimeProvider>();
Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例
Client client = new Client();
client.TimeProvider = timeProvider; // 通过Setter实现注入
}
}
1.5.5 接口注入
接口注入是将包括抽象类型注入的入口以方法的形式定义在一个接口里,如果客户类型需要实现这个注入过程,则实现这个接口,客户类型自己考虑如何把抽象类型“引入”内部。实际上接口注入有很强的侵入性,除了要求客户类型增加需要的Setter或Constructor注入的代码外,还显式地定义了一个新的接口并要求客户类型实现它。除非还有更外层容器使用的要求,或者有完善的配置系统,可以通过反射动态实现接口方式注入,否则笔者并不建议采用接口注入方式。
既然Martin Fowler文中提到了这个实现方式,就给出如下示例:
C#
/// 定义需要注入ITimeProvider的类型
interface IObjectWithTimeProvider
{
ITimeProvider TimeProvider { get;set;}
}
/// 通过接口方式注入
class Client : IObjectWithTimeProvider
{
private ITimeProvider timeProvider;
/// IObjectWithTimeProvider Members
public ITimeProvider TimeProvider
{
get { return this.timeProvider; }
set { this.timeProvider = value; }
}
}
Unit Test
[TestClass]
public class TestClient
{
[TestMethod]
public void Test()
{
ITimeProvider timeProvider = (new
Assembler()).Create<ITimeProvider>();
Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例
IObjectWithTimeProvider objectWithTimeProvider = new Client();
objectWithTimeProvider.TimeProvider = timeProvider; // 通过接口方式注入
}
}
1.5.6 基于Attribute实现注入——Attributer
如果做个归纳,Martin Fowler之前所介绍的三种模式都是在对象部分进行扩展的,随着语言的发展(.NET从1.0开始,Java从5开始),很多在类元数据层次扩展的机制相继出现,比如C#可以通过Attribute将附加的内容注入到对象上。直观上的客户对象有可能在使用上做出让步以适应这种变化,但这违背了依赖注入的初衷,三个角色(客户对象、Assembler、抽象类型)之中两个不能变,那只好在Assembler上下功夫,谁叫它的职责就是负责组装呢?
为了实现上的简洁,上面三个经典实现方式实际将抽象对象注入到客户类型都是在客户程序中(也就是那三个Unit Test部分)完成的,其实同样可以把这个工作交给Assembler完成;而对于Attribute方式注入,最简单的方式则是直接把实现了抽象类型的Attribute定义在客户类型上,例如:
C#
(错误的实现情况)
class SystemTimeAttribute : Attribute, ITimeProvider { … }
[SystemTime]
class Client { … }
相信您也发现了,这样虽然把客户类型需要的ITimeProvider通过“贴标签”的方式告诉它了,但事实上又把客户程序与SystemTimeAttribute“绑”上了,它们紧密地耦合在一起。参考上面的三个实现,当抽象类型与客户对象耦合的时候我们引入了Assembler,当Attribute方式出现类似的情况时,我们写个AttributeAssembler不就行了么?还不行。设计上要把Attribute设计成一个“通道”,考虑到扩展和通用性,它本身要协助AttributeAssembler完成ITimeProvider的装配,最好还可以同时装载其他抽象类型来修饰客户类型。示例代码如下:
C#
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
sealed class DecoratorAttribute : Attribute
{
/// 实现客户类型实际需要的抽象类型的实体类型实例,即得注入客户类型的内容
public readonly object Injector;
private Type type;
public DecoratorAttribute(Type type)
{
if (type == null) throw new ArgumentNullException("type");
this.type = type;
Injector = (new Assembler()).Create(this.type);
}
/// 客户类型需要的抽象对象类型
public Type Type { get { return this.type; } }
}
/// 帮助客户类型和客户程序获取其Attribute定义中需要的抽象类型实例的工具类
static class AttributeHelper
{
public static T Injector<T>(object target)
where T : class
{
if (target == null) throw new ArgumentNullException("target");
Type targetType = target.GetType();
object[] attributes = targetType.GetCustomAttributes(
typeof(DecoratorAttribute), false);
if ((attributes == null) || (attributes.Length <= 0)) return null ;
foreach (DecoratorAttribute attribute in
(DecoratorAttribute[])attributes)
if (attribute.Type == typeof(T))
return (T)attribute.Injector;
return null;
}
}
[Decorator(typeof(ITimeProvider))]
// 应用Attribute,定义需要将ITimeProvider通过它注入
class Client
{
public int GetYear()
{
// 与其他方式注入不同的是,这里使用的ITimeProvider来自自己的Attribute
ITimeProvider provider =
AttributeHelper.Injector<ITimeProvider>(this);
return provider.CurrentDate.Year;
}
}
Unit Test
[TestMethod]
public void Test()
{
Client client = new Client();
Assert.IsTrue(client.GetYear() > 0);
}
1.5.7 小结
依赖注入虽然被Martin Fowler称为一个模式,但平时使用中,它更多地作为一项实现技巧出现,开发中很多时候需要借助这项技巧把各个设计模式所加工的成果传递给客户程序。各种实现方式虽然最终目标一致,但在使用特性上有很多区别。
l Constructor方式:它的注入是一次性的,当客户类型构造的时候就确定了。它很适合那种生命期不长的对象,比如在其存续期间不需要重新适配的对象。此外,相对Setter方式而言,在实现上Constructor可以节省很多代码;
l Setter方式:一个很灵活的实现方式,对于生命期较长的客户对象而言,可以在运行过程中随时适配;
l 接口方式:作为注入方式具有侵入性,很大程度上它适于需要同时约束一批客户类型的情况;
l 属性方式:随着开发语言的发展引入的新方式,它本身具有范围较小的固定内容侵入性(一个DecoratorAttribute),它也很适合需要同时约束一批客户类型情景。它本身实现相对复杂一些,但客户类型使用的时候非常方便——“打标签”即可。
C#依赖注入实例的更多相关文章
- 在net Core3.1上基于winform实现依赖注入实例
目录 在net Core3.1上基于winform实现依赖注入实例 1.背景 2.依赖注入 2.1依赖注入是什么? 2.1依赖注入的目的 2.2依赖注入带来的好处 2.2.1生命周期的控制 2.2.2 ...
- Asp.Net Mvc3.0(MEF依赖注入实例)
前言 在http://www.cnblogs.com/aehyok/p/3386650.html前面一节主要是对MEF进行简单的介绍.本节主要来介绍如何在Asp.Net Mvc3.0中使用MEF. 准 ...
- .NET MVC5+ Dapper+扩展+微软Unity依赖注入实例
1.dapper和dapper扩展需要在线安装或者引用DLL即可 使用nuget为项目增加Unity相关的包 2.model类 public class UserInfo { public int I ...
- spring依赖注入实例
依赖注入: BL public class T01BL implements Serializable { private static final Log logger = LogFactory.g ...
- 深度理解依赖注入(Dependence Injection)
前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相当于对那篇文章的解读.所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文:但是,如果您和笔者一样,以前曾经看过,似乎看 ...
- [转]深度理解依赖注入(Dependence Injection)
http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html 前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相 ...
- [学习笔记]Spring依赖注入
依赖: 典型的企业应用程序不可能由单个对象(在spring中,也可称之bean)组成,再简单的应用也是由几个对象相互配合工作的,这一章主要介绍bean的定义以及bean之间的相互协作. 依赖注入: s ...
- 依赖注入与Service Locator
为什么需要依赖注入? ServiceUser是组件,在编写者之外的环境内被使用,且使用者不能改变其源代码. ServiceProvider是服务,其类似于ServiceUser,都要被其他应用使用,不 ...
- Console app 里的依赖注入及其实例生命周期
依赖注入是 ASP.NET Core 里的核心概念之一,我们平常总是愉快地在Startup类的ConfigureServices方法里往IServiceCollection里注册各种类型,以致有一些同 ...
随机推荐
- 【OF框架】配置信息Config添加配置和代码调用api
一.配置规范 配置信息全部写在OF.WebShell项目文件config.json中 配置键使用OF.开头,配置格式如下: { "OF.IgnoredUrl": "log ...
- P4393 [BOI2007]Sequence 序列问题[贪心]
题目描述 对于一个给定的序列a1, -, an,我们对它进行一个操作reduce(i),该操作将数列中的元素ai和ai+1用一个元素max(ai,ai+1)替代,这样得到一个比原来序列短的新序列.这一 ...
- springboot+支付宝完成秒杀项目的初体验
springboot+支付宝完成秒杀项目的初体验 思考的问题: 首先是秒杀的商品查询,考虑到是热点数据,所以写一个接口读取当日批次的秒杀商品到redis中(那么接下来对商品的操作都放入redis中). ...
- php中危险的木马函数-eval()函数
eval() 函数可将字符串转换为代码执行,并返回一个或多个值. 如果eval函数在执行时遇到错误,则抛出异常给调用者. 类似的函数是loadcode ,loadcode并不立即执行代码,而是返回一个 ...
- Mybatis-Plus 插件学习
官方指南 1.逻辑删除 在相应字段上添加注解 @TableLogic private Integer deleted; 说明: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会) ...
- Ubuntu增加swap交换空间的步骤
1.首先用命令free查看系统内 Swap 分区大小. free -m total used free shared buffers cached Mem: 2012 1960 51 0 748 95 ...
- 创建型模式(一) 单例模式(Singleton)
一.动机(Motivation) 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性.以及良好的效率. 如何绕过常规的构造器,提供一种机制来保证一个类只 ...
- 用CSS 实现 浮动元素的 水平居中
问题描述: 基本的html结构: <div> <!-- <span>1</span> <span>2</span> <span& ...
- 7月新的开始 - Axure学习05 - 元件库的创建
元件库的创建 元件库的创建.载入.编辑和删除等操作 元件库的后缀名名:.rplib 学习结果: 实现iPhone6的原型图 分辨率是 1080*1920,现在我们按比例缩小去实现(360*640)
- Python中日志logging模块
# coding:utf-8 import logging import os import time class Logger(object): def __init__(self): # 创建一个 ...