前些天,参与了公司内部小组的一次技术交流,主要是针对《IOC与AOP》,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世此生”,以方便初学者能更直观的来学习与理解IOC!也做抛砖引玉之用。微信
(虽然说故事中的需求有点小,但看客可在脑海中尽可能把他放大,想象成一个很大的应用系统)网络
1、IOC雏形架构
1、程序V1.0app
话说,多年之前UT公司提出一个需求,要提供一个系统,其中有个功能能够在新春佳节之际给公司员工发送一封邮件。邮件中给你们以新春祝福,并告知发放必定数额的过节费。框架
经分析,决定由张3、李四和王五来负责此系统的开发。函数
其中:由张三负责业逻辑控制模块 LogicController的开发,此处简化为UT.LogicController.exe ;由李四负责祝福消息管理类(GreetMessageService),并集成到组件 UT.MessageService.dll中;由王五负责邮件功能帮助类(EmailHelper),并提供组件 UT.Email.dll。学习
类依赖关系以下:测试
王五邮件功能模块核心代码以下: 优化
public class EmailHelper { public void Send(string message) { Console.Write("Frome email: " + message); } }
李四消息管理模块核心代码以下:spa
public class GreetMessageService { EmailHelper greetTool; public GreetMessageService() { greetTool = new EmailHelper(); } public void Greet(string message) { greetTool.Send(message); } }
张三业务集成模块核心代码以下:
string message = "新年快乐!过节费5000."; MessageService.GreetMessageService service = new MessageService.GreetMessageService(); service.Greet(message);
三人通过一个月的艰苦奋战,终于大功告成,系统也在春节其间成功发出问候信。企业如此关怀,给员工带来无比的温暖,所以深受全体员工好评!
春节事后,相应的功能也移植到了与“UT公司”相关的“UT编辑部”和“UT房产”相似的应用当中,并在后继的“元宵”、“端午”、“中秋”等节日中得以普遍应用。
2、程序V2.0
又是一个年关将至……
说真的,过节费的多少,有时可能直接影响整个假日的行程安排、从而影响假日的总体质量,所以部门领导高度重视。而邮件通知的方式,在边远山区经常由于受网络环境的影响而没法正常收取,许多在外过年的同事对此很有微词。后经多方考证,决得采用当下很是主流的电话语言播报的方式进行通知。
因而乎,张3、李4、王五又忙起来了。但李四,却有点头疼了,由于他的模块如今不只在“UT公司”内部使用,并且还在“UT编辑部”和“UT房产”也都有独立运行。如何让此处变化影响最小,就得费点脑筋。为了达到较好的效果,李四决定按如下方式进行整改。
①、初始设计方案以下:
首先为了能让不一样“祝福方式”能有效替换,决定以“面向接口”的方式来进行分离。同时,让EmailHelper的邮件通知类和TelephoneHelper的语音播报类都实现此接口。核心代码以下:
public interface ISendable { void Send(string message); }
public class EmailHelper : ISendable { public void Send(string message) { Console.Write("Frome email: " + message); } }
public class TelephoneHelper : ISendable { public void Send(string message) { Console.Write("Frome telephone: " + message); } }
再者,为了方便兼容新旧产品,要求Controller决定当前采用什么方式进行通讯,并以参数方式传给消息管理模块,核心代码以下:
public enum SendToolType { Email, Telephone, }
【备注】:上述代码,并非一个优秀的设计,在后继的优化方案当中将被去除。
public class GreetMessageService { ISendable greetTool; public GreetMessageService(SendToolType sendToolType) { if (sendToolType == SendToolType.Email) { greetTool = new UT.EmailV20.EmailHelper(); } else if (sendToolType == SendToolType.Telephone) { greetTool = new UT.TelephoneV20.TelephoneHelper(); } } public void Greet(string message) { greetTool.Send(message); } }
【备注】:上述代码,并非一个优秀的设计,在后继的优化方案当中将被优化。
最后,业务集成模块结合具体业务需求进行适当的调整,核心代码以下:
string message = "新年快乐!过节费5000."; GreetMessageService service = new GreetMessageService(SendTool.Telephone); service.Greet(message);
眼看即将完工,但李四却越看越不顺眼,由于考虑到之后可能再添加新的祝福方式,这种将来的不肯定性,必定会让李四现有的枚举SendToolType和 GreetMessageService中的构造函数不断的进行更改,这将会是一个没完没了工做。
再说了,既然张三要传SendToolType给我,也就是说在具体产品应用时,张三的模块确定是知道要采用什么方式进行祝福,那么何不让他直接把祝福方式的实例而不是简单的方式类型给我呢?这样,我不就省事了吗,因而乎把设计进行了优化。
②、优化后设计方案:
又是一个月的苦战……
王五的代码不受影响。
李四删除 SendToolType枚举,同进把GreetMessageService改为以下:
public class GreetMessageService { ISendable greetTool; public GreetMessageService(ISendable sendtool) { greetTool = sendtool; } public void Greet(string message) { greetTool.Send(message); } }
张三,也把业务逻辑控制部分改为以下:
string message = "新年快乐! 过节费5000."; ISendable greetTool = new TelephoneHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
最终:张三更新UT.LogicController.exe中的实现;李四更新了UT.MessageSevice.dll,王五提供新的组件:UT.Telephone.dll,并把接口集成到一个叫UT.Core.dll的库中。经多方集成测试后系统运行良好!
【点评】:
李四此处成功的利用“接口分离”、并结合“依赖倒置”的方式,使得本身负责的模块初步具有了应对新增祝福方式的扩展要求。同时因为其采用的“依赖注入”方式要求李四的业务逻辑控制模块对其所需的 “ISendable”实例进行注入,理论上已经初步具体了“IOC反转控制”的雏形。
对“IOC反转控制”此时带来的优点就是:确保了“红色框”内的模块是具备应对变化的能力,在后继新增新祝福方式时,UT.MessageService.dll组件能够彻底不作任何修改。
3、V2.1
因为电话语言播报必须接听、事后不便留底查询等不足也常被人们诟病,所以短信通知的方式被提上议程。
在此要求下,王五提供了新的组件:UT.GSN.dll。核心代码以下:
public class SMSHelper : ISendable { public void Send(string message) { Console.WriteLine("Frome SMS: " + message); } }
张三也把代码改为了以下,
string message = "新年快乐! 过节费5000."; ISendable greetTool = new SMSHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
李四不劳而获!
4、V2.2
祝福方式突飞猛进人们的要求也是不断发展,没过多久短信方式太呆板、信息量不足等缺陷也暴露出来,微信深受大伙青睐。
在此要求下,王五提供了新的组件:UT.Wechat.dll。核心代码以下:
public class WechatHelper : ISendable { public void Send(string message) { Console.WriteLine("Frome wechat: " + message); } }
张三也把代码改为了以下:
string message = "新年快乐! 过节费5000."; ISendable greetTool = new WechatHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
李四再次不劳而获!!
2、IOC扩展
1、李四的逍遥自在与张三的焦头烂额
因为采用了IOC反转控制的思想,如今无论系统如何变化,李四负责的模块总的来讲仍是至关稳定,所以这些年李四过的可谓逍遥自在。然而,相比之下张三却由于产品在UT公司、UT编辑部、UT房产等都有独立应用,且各自使用的版本又不尽相同,所以要同时维护三个版本,可谓是焦头烂额。
固然张三曾经也想统一各个版本,从而实现代码的统一维护。为此还专门与各相关主管沟经过、协调过,然而由为UT编辑部与电信服务商早有合做全部短信免费,所以短信方式最得人心;而UT房产基于对信息接收者身份的特殊性考虑,邮件通知被认为是不二选择。所以,张三统一版本的梦想最终仍是无果而终。
咱们来看看此时的张三同时维护着三个系统,其中各自核心代码基本以下:
UT公司(微信方式)
string message = "新年快乐! 过节费5000."; ISendable greetTool = new WechatHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
UT编辑部(短信方式)
string message = "新年快乐! 过节费5000."; ISendable greetTool = new SMSHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
UT房产(邮件方式)
string message = "新年快乐! 过节费5000."; ISendable greetTool = new EmailHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
这些年,本着对工做和客户的认真负责,张三长时间在这些“版本维护”、“产品兼容”等脏活累活中摸爬滚打,如今是心力憔悴……
2、张三的出路
某日张三与李四觥筹交错、把酒言欢……
酒过三巡,张三对李四说:当年你的模块因“IOC反转控制”而脱身,却把“变化点”反转到我模块,由我来生成特定的对象,而后再向你注入。这样你是轻松了,但我却深陷泥潭……
面对张三的吐槽,李四只能给张三进行细心分析:
首先、MessageService消息管理模块做为一个消息专用服务,其实对“是采用邮件仍是微信方式进行祝福”这样的功能性把控自己是不具主动权,由这个模块来负责实在是有点鞭长莫及,即使强扭到一块儿,这瓜也铁定甜不了。
还有,本着单一职责的原则本消息服务实际上是不方便过多地去处理本应该是业务逻辑处理的相似“选择祝福方式”这种事情。理论上,做为业务集成方的“LogicController”负责处理这类业务应该是责无傍代。
再者,做为新增需求,王五为此而新增组件(dll)那是必不可少;张三做为业务的总集成方也是难以脱身;因为新增需求而引发的变化,对张三和王五产生影响也是情理之中。即使退一万步来讲,就算没有“反转控制”张三也是要面对变化的(就像V2.0初始方案中的传入SendToolType参数),所以有无“反转控制”对张三而言该变的始终仍是要变化。那么如今采用“IOC反转控制”而成全了李四的稳定,对张三来讲这是个“利人不损已”的买卖。
最后,无论从架构设计仍是开发效率上来讲,“IOC反转控制”虽然说把变化点从李四的“MessageService”模块反转到了张三的“LogicController”模块当中,但这符合“SOLID面向对象设计”的原则,能够说是一个好的设计,本无可厚非!
听完李四的论述,张三以为甚是有理,酒难免醒了三分!因为两人都是这个行业打拼多年的老鸟,争论也是点到即止。立刻把交流的重点转移到“如何解决张三同时维护三个产品”的尴尬处境上来。
通过深刻分析,两人以为要脱困必须解决好以下两个问题:
①:如何有效建立“ISendable”实例,减小因为新增祝福方式对实例建立的影响?
②:如何减小新增祝福方式而对“LogicController”模块的冲击,以减小维护成本?
【备注】
SOLID面向对象的五个设计原则对于开发人员很是重要,其身影在任何大中型软件项目中随处可见,建议必须掌握并灵活应用。此五原则分别为:
单一职责原则(Single Resposibility Principle)
开放封闭原则(Open Closed principle)
里氏替换原则(Liskov Substitution Principle)
接口分离原则(Interface Segregation Principle)
依赖倒置原则 (Dependency Inversion Principle)
3、解决方案
为了实现“如何有效建立ISendable实例”的问题,张三引入了“工厂模式”,因为不一样的祝福方式而产生的变化,封装在一个独立的“SendToolFactory”类中,这样就算之后再有变化,只要更改此类中部分代码便可,而不影响程序中其余全部用到ISendable的地方。
【点评】:
以工厂模式来实现“ISendable”对象实例的建立,是一种典型的“高内聚”与“松耦合”的设计方式,它有效的使得应用程序核心部分并不用去关心系统到底采用了什么样的“祝福方式”,而具体的“祝福方式”则在工厂模式内部进行建立。若是之后需求有变更,那也只需在工厂作少量修改便可,程序其余代码都将不受影响。
当成功解决完第一个问题后,咱们当即拉开针对“如何能实如今新增祝福方式以后,有效的控制对“LogicController”模块的冲击”这们问题上来。从目前程序的结构来看,在新增祝福方式以后的主要冲击有两方面:首先是更改工厂类中的代码用以建立新的实例;再者是引入新的动态库。
最后咱们决定采用“工厂模式+反射机制”的方式来解决上述难题,并在工厂模式中依靠配置文件的节点信息,而后采用“反射机制”来动态建立相应的实例;如此一来,之后就算再有新的祝福方式采用,也只需把王五新增的动态库拷贝过来,而后再更改一下配置文件中的节点信息就行,再也不须要更改任何程序源代码,也再也不须要从新编译生成程序。
4、程序V3.0
采用工厂模式建立实例
string message = "新年快乐! 过节费5000."; ISendable greetTool = SendToolFactory.GetInstance(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
工厂中的实现
public abstract class SendToolFactory { public static ISendable GetInstance() { try { Assembly assembly = Assembly.LoadFile(GetAssembly()); // 加载程序集 object obj = assembly.CreateInstance(GetObjectType()); // 建立类的实例 return obj as ISendable; } catch { return null; } } static string GetAssembly() { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["AssemblyString"]); } static string GetObjectType() { return ConfigurationManager.AppSettings["TypeString"]; } }
配置文件节点信息
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!--<add key="AssemblyString" value="UT.EmailV20.dll" /> <add key="TypeString" value="UT.EmailV20.EmailHelper" />--> <!--<add key="AssemblyString" value="UT.SMSV21.dll" /> <add key="TypeString" value="UT.SMSV21.SMSHelper" />--> <add key="AssemblyString" value="UT.WechatV22.dll" /> <add key="TypeString" value="UT.WechatV22.WechatHelper" /> </appSettings> </configuration>
自从V3.0推出后,基于“IOC反转控制”的思想也算小有收获,多年来产品运行良好,就算不断有新的“祝福方式”出现,张三和李四也都没必要再为之操心,同时也能适用“UT公司”、“UT编辑部”和“UT房产”等不一样的场景要求,可谓皆大欢喜。
【点评】:
①:IOC反转控制常见的实现手段之一就是DI依赖注入,而依赖注入的方式一般有:接口注入、Setter注入和构造函数注入。本次示例给出的代码具有“接口注入”的特征,并经过构造函数来实现。
②:IOC反转控制还有一种手段就是依赖查找,这种方式通常先进行类型注册,使用时进行查找;对这种方式有兴趣的朋友能够参考微软企业库中Microsoft.Practices.Unity.dll中的源码(https://entlib.codeplex.com/)和详细的示例说明整理(如:Enterprise Library 4.1 HOL)。
③:依赖注入通常由调用者(LogicController)依赖IOC框架生成好实例对象,而后直接注入到被调用者(GreetMessageService)当中,被者用者内部直接使用此实例,代码流程清晰明了;而依赖查找通常由调用者(LogicController)前期进行类型注册,被调用者(GreetMessageService)内部依赖IOC框架获取到想要的对象实例,而后再使用此实例。
④:二者生成实例的目的都是为了能动态建立实例,只不过建立的时机不同。我我的认为依赖注入分离了逻辑控制相对来讲层次性更清晰明了,但在须要注入多个对象时,却不及查找注入方式方便简洁。
3、IOC框架
1、模式的复用
自从张三在上述产品开发过程当中成功地总结出“IOC思想”后,在后继的其余产品中进行了推广与实践。在使用的过程当中,张三发现这样的模式是能够很好的在模块间、产品间进行有效的复用,不只大大提升了开发效率,对产品后继的扩展和维护都带来很多方便。
2、对象容器
固然,在对“IOC思想”的实践中,张三还发现有些地方须要完善。好比,有时咱们可能要建立单一对象实例,有时却要要建立多个对象的实例,甚至有时要建立一系列实例;有时要建立一个本地的对象实例,有时却要建立一个远端的服务对象实例;等等…..
为了应对复杂的对象应用,张三把原来的“对象工厂”这样的小做坊升级成了一个功能强大的、具备必定智能水平的“IOC对象容器”,这个容器能够动态的依据参数设定或配置文件来进行有策略性的对象建立与管理,使得整个框架对对象集的管理上升到了一个更高的层次。
3、IOC基础框架
张三经过前期的“接口分离”及“依赖倒置”达到了“反转控制”的效果,并结合有效的“依赖注入”方式,实现了系统的“松耦合”架构;再经过“工厂模式 + 反射机制”有效实现了对象的动态建立,并在后期升级成“对象容器”,大大减小新增需求对程序带来的冲击。经过以上方式,张三成功地摸索出一套行这有效且复用性高的“IOC基础框架”。
4、IOC思想
后来,张三把摸索总结出的“IOC基础框架”在公司各产品中进行了普遍实践,获得一致好评,而且被做为一个公共组件集成在一个叫“UT企业库”的组件集中。今后,在张三的朋友圈中,IOC思想广为流传。
若干年后,咱们发现EJB、Spring、Struts、Asp.netMVC等框架中都能看到IOC思想的影子,这些框架都对张三最初IOC的思想做了进一步的发扬、光大。
如今,IOC的思想在软件设计与系统架构中大放异彩,然而很是遗憾中国人口中的那个神秘的张三至今也不知究竟是谁。
四:源代码
1、开发环境为:VS2010 + NET4.0 + Windos7
2、下载示例源代码(IOCDemo),代码很简单都没写注释。
(以上故事纯属虚构,如有雷同纯属巧合。你若喜欢,请点“推荐”!)