使用Microsoft的IoC框架:Unity来对.NET应用进行解耦

摘要:
控制反转的核心是控制权的转移,从原有的应用程序转移到框架如IoC容器,从而实现模块间的解耦。下面我们用一个简单的例子来演示如何使用Ioc框架:Unity。使用Unity就是要解决这个这个问题。publicinterfaceILogger{TupleGetLogContent;voidLog;}3.2、定义接口的实现类定义2个接口的实现类,分别用来实现对请求的响应的报文进行日志记录。

1、IoC/DI简介

IoC 即 Inversion of Control,DI 即Dependency Injection,前一个中文含义为控制反转,后一个译为依赖注入,可以理解成一种编程模式,详细的说明可参见大牛Martin Fowler的强文http://martinfowler.com/articles/injection.html,借用Hollywood的名言:Don't call us, we'll call you,意即你呆着别动,到时我会找你。控制反转的核心是控制权的转移,从原有的应用程序转移到框架如IoC容器,从而实现模块间的解耦。

2、Unity是什么?

Unity是微软Patterns & Practices团队所开发的一个轻量级的,并且可扩展的依赖注入(Dependency Injection)容器,它支持常用的三种依赖注入方式:构造器注入(Constructor Injection)、属性注入(Property Injection),以及方法调用注入(Method Call Injection)。现在Unity最新的版本的3.5版,可以在微软的开源站点:https://github.com/unitycontainer/unity下载最新的发布版本和文档。

它有助于构建松耦合的应用程序和为开发者提供以下便利:

  • 简化对象的创建,特别在分层对象结构和依赖的情形下;
  • 它支持需求的抽象化,这允许开发人员在运行时或在配置文件中指定依赖,简化横切关注点(crosscutting concerns)的管理;
  • 它通过把组件配置推给容器来决定,增加了灵活性;
  • 服务定位能力; 这使客户端能够存储或缓存容器;
  • 轻松构建松耦合结构的程序,从而让整个程序框架变得清晰和易于维护。

3、如何使用Unity?

下面我们用一个简单的例子来演示如何使用Ioc框架:Unity。我们的大多数应用程序都是由两个或是更多的类通过彼此的合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。使用Unity就是要解决这个这个问题。

Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中将具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,习惯将其划分为一种(类型)匹配和三种注入:

  • 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务实现类型之间的匹配关系
  • 构造器注入(Constructor Injection):IoC容器会智能选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
  • 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性
  • 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

依赖翻转的核心原则:

1、高层模块不应该依赖底层模块,两个都应该依赖抽象(抽象类或接口)

2、抽象不应该依赖细节,细节应该依赖抽象。

下面我采用“属性注入(Property Injection)”的方式演示如何使用Unity框架。

3.1、定义接口类

一个简单日志记录接口类,用于记录请求的报文。

    public interfaceILogger
    {
        Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null);
        void Log(Tuple<string, string>logInfo);
    }

3.2、定义接口的实现类

定义2个接口的实现类,分别用来实现对请求的响应的报文进行日志记录。

internal classLogRequest : ILogger
{
    public Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null)
    {
        var reqType =request.GetType().Name;
        switch(reqType)
        {
            case "AtomQueryRequest":
            {
                var req =(AtomQueryRequest) request;
                return Tuple.Create(request.CollectionId, string.Format("流水号:{0} 交易类型:{1}", req.TransNo, req.TransType));
            }
            case "AtomSaleRequest":
            {
                var req =(AtomSaleRequest) request;
                return Tuple.Create(req.CollectionId, string.Format("流水号:{0} 订单号:{1} 金额:{2}", req.TransNo, req.OrderNo, req.LocalAmount));
            }
            default:
            {
                throw new CheckRequestException("无效的交易类型:".Contact(reqType));
            }
        }
    }
    public void Log(Tuple<string, string>logInfo)
    {
        LogManager.InfoRequest(logInfo.Item1, logInfo.Item2);
    }
}

3.3、定义容器并注册接口及接口实现类之间的映射关系

项目引用:Microsoft.Practices.Unity.dll

internal classServiceContainer
{
    //核心容器类型的定义,设置为静态的,公开的,可为其它任何类型调用。
    public staticUnityContainer RtpContainer;
    //初始化容器并在容器中注册项目程序中所有的依赖关系
    staticServiceContainer()
    {
        RtpContainer = newUnityContainer();
        //每次调用,容器都会生成一个新的对象实例
        RtpContainer.RegisterType<IResponseProcessor, ResponseProcessor>();
        //注册为单例,任何时候调用都使用同一个对象实例
        RtpContainer.RegisterType<ILogger, LogRequest>("Request", newContainerControlledLifetimeManager());
        RtpContainer.RegisterType<ILogger, LogResponse>("Response", newContainerControlledLifetimeManager());
    }
}

什么时候注册为单例,我的个人标准为:实现类,比如:LogRequest类,没有静态成员或者静态成员没有并发写的可能都可以用,使用单例可以减少频繁创建对象可能造成的开销。在注册类型时,如果要注册为单例模式,额外传入一个:new ContainerControlledLifetimeManager() 参数即可,表示创建对象的生命周期由容器来控制。另外如果一个接口由多个实现类,如上面的LogRequest和LogResponse都实现了ILogger接口。这样在注册map映射关系时,需要额外使用一个name参数(比如上面的“Request”,“Response”)来唯一标识map关系。使用XML也可以实现依赖关系的注册,但我更倾向于使用:约定优于配置(convention over configuration)的原则,也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。

3.4、依赖注入

internal abstract classAbstractRequest
{
    staticAbstractRequest()
    {
        JsConfig.EmitCamelCaseNames = true;
        JsConfig.IncludeNullValues = true;
    }
[Dependency("Request")]
    public ILogger Logger { get; set; }
    [Dependency]
    public IRequestCheck RequestCheck { get; set; }
    protected virtual voidCheckRequest(AtomRequest request)
    {
        if (null ==request)
        {
            throw new ArgumentNullException("request", "请求实体不能为NULL");
        }
    }
}

上面的代码我采用了“属性注入(Property Injection)”的方式注入了一个Logger属性,并且Dependency属性类的name参数为:Request,标识当AbstractRequest的实现类被实例化时,IoC容器自动初始化该Logger属性为一个LogRequest对象实例。

3.5、使用注入的属性

internal classProcessAtomSaleRequest : AbstractRequest, IAtomRequest
{
    [Dependency("AtomSale")]
    public MessageProviderFactory MessageProviderFactory { get; set; }
    public objectExecute(AtomRequest request)
    {
        CheckRequest(request);
        //使用AbstractRequest类中注入的属性Logger,ProcessAtomSaleRequest被实例化时Logger属性自动被初始化为LogRequest实例对象
Logger.Log(Logger.GetLogContent(request));
    }
}

3.6、使用容器来解析并创建对象实例

public classAtomSale : AtomTransaction
{
    protected override objectProcessRequest(AtomRequest request)
    {
        return ((IAtomRequest)ServiceContainer.RtpContainer.Resolve(typeof(IAtomRequest), "AtomSale")).Execute(request);
    }
}

ServiceContainer.RtpContainer就是我们前面定义的静态的、公共的容器类(类型为:UnityContainer),在第一次被调用时初始化。Resolve方法通过指定抽象类型及对应的name属性来确定唯一映射关系并创建对象,最后执行对象的Execute方法。

总结:

使用IoC框架后,使用相同架构模型的应用,其高层抽象可以完全移植,只须关注实现类的业务细节即可,业务逻辑修改时也只须改动实现的部分,这样就实现了抽象层和实现层的分离,同样对象创建职责也做了转移。系统解耦或松耦合也就顺理成章了。如果所有代码都揉合在一起,任何代码的修改都可能对其它代码造成影响,如果没能细致和全面的回归测试,线上故障也难免发生。

帮助到您了吗?

打赏作者(支付宝):

使用Microsoft的IoC框架:Unity来对.NET应用进行解耦第1张

免责声明:文章转载自《使用Microsoft的IoC框架:Unity来对.NET应用进行解耦》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇matplotlib 进阶之Tight Layout guidemtools-你可能没用过的mongodb神器下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Algorithm算法库

algorithm 是C++标准程式库中的一个头文件,定义了C++ STL标准中的基础性的算法(均为函数模板)。在C++98中,共计有70个算法模板函数;在C++11中,增加了20个算法模板函数。其中有5个算法模板函数定义在头文件numeric中。 下文所称的“序列”(sequence),是指可以用迭代器顺序访问的容器。 有返回值的函数,返回值都是迭代器,...

Unity用GUI绘制Debug/print窗口/控制台-打包后测试

Unity游戏视窗控制台输出 本文提供全流程,中文翻译。 Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例) Chinar —— 心分享、心创新!助力快速在 Game 视窗用 GUI 实现一个控制台的输出面板为新手节省宝贵的时间,避免采坑! Chinar 教程效果:(可打包后执...

【Unity插件】NGUI核心组件之UIPanel .

转自:http://blog.csdn.net/daiguangda/article/details/7840084   UIPanel负责创建实际的集合图形。你不需要手动的添加UIPanel-一旦你创建一个控件,它会自动被添加。如果你想将你的UI渲染拆分到不同的Draw Call中,你可以手动创建你自己的UIPanel,例如你要创建一个分屏的游戏,每个屏...

在Unity中使用Lua脚本

前言:为什么要用Lua首先要说,所有编程语言里面,我最喜欢的还是C#,VisualStudio+C#,只能说太舒服了。所以说,为什么非要在unity里面用Lua呢?可能主要是闲的蛋疼。。。。。另外还有一些次要原因:方便做功能的热更新;Lua语言的深度和广度都不大,易学易用,可以降低项目成本。C#与Lua互相调用的方案坦白来将,我并没有对现在C#与Lua互相...

Unity3D 物体移动方法总结

1. 简介     在Unity3D中,有多种方式可以改变物体的坐标,实现移动的目的,其本质是每帧修改物体的position。 2. 通过Transform组件移动物体     Transform 组件用于描述物体在空间中的状态,它包括 位置(position), 旋转(rotation)和 缩放(scale)。 其实所有的移动都会导致position的改...

应用程序-特定 权限设置并未向在应用程序容器 不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户 NT AUTHORITYSYSTEM SID (S-1-5-18)授予针对 CLSID 为 {D63B10C5-BB46-4990-A94F-E40B9D520

应用程序-特定 权限设置并未向在应用程序容器 不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户 NT AUTHORITYSYSTEM SID (S-1-5-18)授予针对 CLSID 为 {D63B10C5-BB46-4990-A94F-E40B9D520160}、APPID 为 {9CA88EE3-ACB7-47C...