首页 > 代码库 > 细说IOC演变之路

细说IOC演变之路

前言

在上篇文章我的权限设计实现中提及到了Spring.NET这个IOC框架,不少园友通过QQ群向我咨询了IOC的概念,感觉有必要写一遍博文来简单介绍下IOC的演变过程,以及在实际项目中我们如何使用第三方成熟的IOC容器,希望能够起到抛砖引玉之用,也方便各位园友能更直观的来学习与理解IOC。

简单三层

做过OA系统的朋友都会面临这样一个问题:OA流程提醒。假设系统设计初期,采用的提醒方式是站内私信。通常情况下我们会定义这么一种结构来完成这个功能。

程序依赖关系图

 

站内私信核心代码如下:

 1 /// <summary> 2 /// 站内私信 3 /// </summary> 4 public class PrivilegeMessageBll 5 { 6         /// <summary> 7         /// 发送私信 8         /// </summary> 9         /// <param name="message"></param>10         public void SendMessage(string message)11         {12             //一些业务处理13         }14 }

 

程序调用核心代码如下:

1 PrivilegeMessageBll privilegeMessageBll = new PrivilegeMessageBll ();2 string message = "你有新到达的流程";3 privilegeMessageBll.SendMessage(message);

当你程序完成之时,沾沾自喜的时候,领导过来了。小李,人力资源部那边刚打电话过来要能支持短信提醒。带着纠结的情绪,小李在思考如何修改程序代码让变化影响最小,最后小李决定按照以下方案进行修改。

接口分离

 

于是经过数个小时的奋斗,程序代码改成下面

消息发送接口层核心代码:

 1 /// <summary> 2 /// 消息发送接口 3 /// </summary> 4 public interface ISendMessageBll 5 { 6         /// <summary> 7         /// 发送消息接口 8         /// </summary> 9         /// <param name="message"></param>10         void SendMessage(string message);11 }

站内私信核心代码:

 1 /// <summary> 2 /// 站内私信 3 /// </summary> 4 public class PrivilegeMessageBll : ISendMessageBll 5 { 6         /// <summary> 7         /// 发送私信 8         /// </summary> 9         /// <param name="message"></param>10         public void SendMessage(string message)11         {12             //一些业务处理13         }14 }

短信发送核心代码:

 1 /// <summary> 2 ///短信发送 3 /// </summary> 4 public class SMSBll: ISendMessageBll 5 { 6         /// <summary> 7         ///发送短信 8         /// </summary> 9         /// <param name="message"></param>10         public void SendMessage(string message)11         {12             //一些业务处理13         }14 }

消息发送核心代码如下:

/// <summary>/// 消息发送/// </summary>public class SendMessageBll{        ISendMessageBll ismBll;       //接口作为参数屏蔽了子类区别,这是面向对象中的里氏替换原则       //这里不管你传入的是站内私信、还是短信发送        public SendMessageBll(ISendMessageBll paraBll)        {            ismBll = paraBll;        }        /// <summary>        /// 发送消息        /// </summary>        /// <param name="message"></param>        public void SendMessage(string message)        {            ismBll.SendMessage(message);        } }

主程序调用:

1  ISendMessageBll ismBll = new PrivilegeMessageBll();2   SendMessageBll smBLL = new SendMessageBll(ismBll);3   string message = "你有新到达的流程";4 smBLL.SendMessage(message);

在这里小李使用了接口做为参数,作用是用接口指向子类,屏蔽了子类的区别。可以理解为面向对象多态,这个应该不难理解吧。

在这里还涉及到了另外一个面向对象原则:依赖倒置。

什么是依赖倒置呢?依赖倒置可以简单理解为高层模块定义接口,低层模块负责实现。

反过来就是依赖非倒置,可以简单理解为:低层模块定义接口,高层模块负责实现。

我们这里使用的是依赖倒置原则,高层模块定义接口(ISendMessageBll),低层模块负责实现(PrivilegeMessageBll、SMSBll)。

通过定义接口约束子类实现,尽可能的减少了日后新增提醒方式对程序代码的影响。程序总算完成, OA流程提醒稳定了运行了很长一段时间。

过了很长一段时间,人力资源又打电话过来了,小李,流程提醒还需要加上RTX消息提醒,这样可以方便员工更快更直观的接收到消息。这是小李发现了一个问题:每加入新的提醒方式,我们又要实例化一个新的对象,这是如此的痛苦,小李越看越不顺眼,因为考虑到以后可能还会再添加新的提醒方式,这种未来的不确定性,会不断的产生实例。

 1 //站内私信提醒方式 2 ISendMessageBll privilegeMessageBll = new PrivilegeMessageBll(); 3 SendMessageBll smBLL = new SendMessageBll(privilegeMessageBll); 4  string message = "你有新到达的流程"; 5 //短信提醒方式 6 ISendMessageBll smsBll = new SMSBll(); 7 SendMessageBll smBLL = new SendMessageBll(smsBll); 8 string message = "你有新到达的流程"; 9 //RTX提醒方式10 ISendMessageBll rtxBll = new RTXBll();11 SendMessageBll smBLL = new SendMessageBll(rtxBll);12 string message = "你有新到达的流程";

此时,小李得出一个结论:如何控制实例化ISendMessageBll的次数?最好能够只实例化一次。

经过深入分析,关键问题在于如何获取ISendMessageBll实例。小李经过不断的查询资料,得知有三种方式可以得到ISendMessageBll实例

 

简单工厂

工厂模式的作用在于很优雅的解决了应用程序使用对象时的无限new()的操作,同时降低了系统应用之间的耦合性,提高了系统的可维护性和适应性。就是将不同提醒方式产生的变化,封装在一个独立的工厂类中,将其命名为SendMessageFactory类中,这样就算以后再有变化,只要更改此类中部分代码即可,而不影响程序中其他所有用到ISendMessageBll的地方。博客园关于简单工厂的介绍太多了,这里推荐一篇文章:http://www.cnblogs.com/hegezhou_hot/archive/2010/11/30/1892227.html

抽象工厂

抽象工厂的作用在于在工厂模式中依靠配置文件的节点信息,然后采用反射来动态创建相应的实例。好处在于当需求发生变化时我们只需要修改配置文件节点,无需修改代码和重新编译程序。例如所有流程提醒最后都采用站内私信方式提醒,那么我们只需要修改对应的XML值。博客园关于抽象工厂的介绍也非常多,我就不重复写了,这里也推荐一篇文章:http://www.cnblogs.com/hegezhou_hot/archive/2010/12/01/1893388.html

IOC容器

这里我们来重点说下IOC容器,以及使用Spring.NET实现依赖注入。

首先我们要明白下面概念:

控制反转(IOC):就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。

依赖注入(DI):它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。常用方式有: 构造函数注入和属性注入

其实在上面的例子中,我们已经不知不觉的过程中接触了依赖注入(DI)和控制反转(IOC)这个两个概念,请看下面的例子。

 1 /// <summary> 2 /// 消息发送  构造函数注入方式 3 /// </summary> 4 public class SendMessageBll 5     { 6         ISendMessageBll ismBll; 7        //通过DI,我们可以在SendMessageBll类的外部将PrivilegeMessageBll、SMSMessageBll中任意一个对象的引用传递给SendMessageBll类对象 8        //这里采用的是构造函数注入 9         public SendMessageBll(ISendMessageBll paraBll)10         {11             ismBll = paraBll;12         }13    }14 15 16 /// <summary>17 /// 消息发送  属性注入方式 18 /// </summary>19 public class SendMessageBll20 {21     //定义一个私有变量保存抽象22     private ISendMessageBll _ismBll; 23        //通过DI,我们可以在SendMessageBll类的外部将PrivilegeMessageBll、SMSMessageBll中任意一个对象的引用传递给SendMessageBll类对象24        //这里采用的是属性注入25     public ISendMessageBll ismBll26         {27            set { _ismBll = value; }28            get { return _ismBll; }29         }30     31         public SendMessageBll(ISendMessageBll paraBll)32         {33             ismBll = paraBll;34         }35    }

主程序调用方式:

 1 //构造函数注入方式调用 2 ISendMessageBll ismBll = new PrivilegeMessageBll(); 3 SendMessageBll smBLL = new SendMessageBll(ismBll); 4 string message = "你有新到达的流程"; 5 smBLL.SendMessage(message); 6  7 //属性注入方式调用 8 PrivilegeMessageBll privilegeMessageBll=new PrivilegeMessageBll(); 9 SendMessageBll smBLL = new SendMessageBll();10 smBLL.ismBll= privilegeMessageBll;11  string message = "你有新到达的流程";12 smBLL.SendMessage(message);

其实上面就是依赖注入(DI)的概念,大家应该有个初步认识了。

最后在简单介绍下Spring.NET是如何在上篇我的权限设计实现文章中注入不同数据库

详细解决方案

 

1.在WebConfig中配置Spring.NET节点,具体可查看官方帮助手册

 1 <!--配置spring--> 2 <sectionGroup name="spring"> 3    <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> 4    <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/> 5 </sectionGroup> 6  7 <!--配置spring路径--> 8 <spring> 9     <context>10       <resource uri="~/Config/Objects.xml"/>11     </context>12 </spring>

2.新建Objects.xml文件,配置节点

1 <?xml version="1.0" encoding="utf-8" ?>2 <objects xmlns="http://www.springframework.net">3   <object id="BaseAccessDal" type="Murphy.Data.SQLServer.BaseAccessDal, Murphy.Data.SQLServer" />4 </objects>

其中type=“x,y”的x表示程序集名称,y表示程序集所在的命名空间

3.定义BaseBll基类

 1 /// <summary> 2 /// 业务逻辑层基类 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5  public abstract class BaseBll<T> where T : class 6 { 7         /// <summary> 8         /// 数据接口层对象 等待被实例化 9         /// </summary>10         protected IBaseDal<T> idal = null;11 }

4.BaseAccessBll继承BaseBLL 

 1 /// <summary> 2 /// 登陆日志 3 /// </summary> 4 public class BaseAccessBll : BaseBll<BaseAccess> 5 { 6         protected IBaseAccessDal baseAccessDal = null; 7        //构造函数注入 8         public BaseAccessBll() 9         {10             IApplicationContext springContext = ContextRegistry.GetContext();11             baseAccessDal = springContext.GetObject("BaseAccessDal") as IBaseAccessDal;12             base.idal = baseAccessDal;13     }14 }

通过以上四步就很简单的通过Spring.NET的依赖注入(DI)来实现注入不同数据库的需求,其实Spring.NET是帮我们做了反射那些事情。

如果大家感兴趣,就在右下角帮我【推荐】一下吧,在这里谢谢大家了。我接下来就按照模块列表一篇一篇的来写。最后我创建了一个技术交流群:263169088,欢迎大家来交流。

细说IOC演变之路