其中:由张三负责业逻辑控制模块 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);
}
}
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);
2、程序V2.0
又是一个年关将至……
说真的,过节费的多少,有时可能直接影响整个假日的行程安排、从而影响假日的整体质量,因此部门领导高度重视。而邮件通知的方式,在边远山区常常因为受网络环境的影
响而无法正常收取,许多在外过年的同事对此颇有微词。后经多方考证,决得采用当下非常主流的电话语言播报的方式进行通知。
于是乎,张三、李四、王五又忙起来了。但李四,却有点头疼了,因为他的模块现在不仅在“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);
}
}
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改成如下:
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);
【点评】:
李四此处成功的利用“接口分离”、并结合“依赖倒置”的方式,使得自己负责的模块初步具备了应对新增祝福方式的扩展要求。同时由于其采用的“依赖注入”方式要求李四的业务逻辑控制模块对其所需的 “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);
祝福方式日新月异人们的要求也是不断发展,没过多久短信方式太呆板、信息量不足等缺陷也暴露出来,微信深受大伙青睐。
在此要求下,王五提供了新的组件: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);
二、IOC扩展
由于采用了IOC反转控制的思想,现在不管系统如何变化,李四负责的模块总的来说还是相当稳定,因此这些年李四过的可谓逍遥自在。然而,相比之下张三却因为产品在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);
这些年,本着对工作和客户的认真负责,张三长时间在这些“版本维护”、“产品兼容”等脏活累活中摸爬滚打,现在是心力憔悴……
采用工厂模式创建实例
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"];
}
}
配置文件节点信息
【点评】:
①:IOC反转控制常见的实现手段之一就是DI依赖注入,而依赖注入的方式通常有:接口注入、Setter注入和构造函数注入。本次示例给出的代码具备“接口注入”的特征,并通过构造函数来实现。
②:IOC反转控制还有一种手段就是依赖查找,这种方式一般先进行类型注册,使用时进行查找;对这种方式有兴趣的朋友可以参考微软企业库中Microsoft.Practices.Unity.dll中的源码(https://entlib.codeplex.com/)和详细的示例说明整理(如:Enterprise Library 4.1 HOL)。
③:依赖注入一般由调用者(LogicController)依赖IOC框架生成好实例对象,然后直接注入到被调用者(GreetMessageService)当中,被者用者内部直接使用此实例,代码流程清晰明了;而依赖查找一般由调用者(LogicController)前期进行类型注册,被调用者(GreetMessageService)内部依赖IOC框架获取到想要的对象实例,然后再使用此实例。
④:两者生成实例的目的都是为了能动态创建实例,只不过创建的时机不一样。我个人认为依赖注入分离了逻辑控制相对来说层次性更清晰明了,但在需要注入多个对象时,却不及查找注入方式方便简洁。
三、IOC框架
1、模式的复用
自从张三在上述产品开发过程中成功地总结出“IOC思想”后,在后继的其他产品中进行了推广与实践。在使用的过程中,张三发现这样的模式是可以很好的在模块间、产品间进行有效的复用,不仅大大提高了开发效率,对产品后继的扩展和维护都带来不少方便。
2、对象容器
当然,在对“IOC思想”的实践中,张三还发现有些地方需要完善。比如,有时我们可能要创建单一对象实例,有时却要要创建多个对象的实例,甚至有时要创建一系列实例;有时要创建一个本地的对象实例,有时却要创建一个远端的服务对象实例;等等…..
为了应对复杂的对象应用,张三把原来的“对象工厂”这样的小作坊升级成了一个功能强大的、具有一定智能水平的“IOC对象容器”,这个容器可以动态的依据参数设定或配置文件来进行有策略性的对象创建与管理,使得整个框架对对象集的管理上升到了一个更高的层次。
3、IOC基础框架
张三通过前期的“接口分离”及“依赖倒置”达到了“反转控制”的效果,并结合有效的“依赖注入”方式,实现了系统的“松耦合”架构;再通过“工厂模式 + 反射机制”有效实现了对象的动态创建,并在后期升级成“对象容器”,大大减少新增需求对程序带来的冲击。通过以上方式,张三成功地摸索出一套行这有效且复用性高的“IOC基础框架”。
4、IOC思想
后来,张三把摸索总结出的“IOC基础框架”在公司各产品中进行了广泛实践,得到一致好评,并且被作为一个公共组件集成在一个叫“UT企业库”的组件集中。从此,在张三的朋友圈中,IOC思想广为流传。
若干年后,我们发现EJB、Spring、Struts、Asp.netMVC等框架中都能看到IOC思想的影子,这些框架都对张三最初IOC的思想作了进一步的发扬、光大。
现在,IOC的思想在软件设计与系统架构中大放异彩,然而非常遗憾中国人口中的那个神秘的张三至今也不知到底是谁。