关于控制反转(Inversion of Control),在具体实现上也有许多其它的叫法,如依赖倒置(Dependency Inversion Principles, DIP)、依赖注入(Dependency Injection)等等,现在自己就本人的理解,来说一下这里的反转及倒置的讲究。
就总的原则来说,控制反转(依赖倒置)是:
高层模块不应该依赖于低层模块,二者都要依赖于抽象
抽象不应该依赖于具体,具体应该依赖于抽象。
从现实社会生活中,我们知道:官职越大的人,负责的工作越抽象,官职越低的人,负责的工作越具体;所以说,上面动动嘴皮子,下面跑断腿。也就是说,正常状态下,应该是高层调用(控制)低层办理具体的事务,我们估且说这是正置吧;如果低层调用(控制)高层进行工作,这种情况可以说是倒置了。
软件设计中,确实存在有正置和倒置。正置是结构化设计时的情况,自上而下,由高层调用低层完成软件设计,低层的具体工作向高层提供服务,而且IT中许多设计思想也是正置的,比如计算机网络的七层(或五层)架构,物理层向上层数据链路层提供服务(下层向上层提供服务,上层调用或控制或依赖下层),数据链路层向上层网络层提供服务,等等。
但在面向对象的设计中,为了降低模块间的耦合,实际低耦合,高内聚的总原则,控制反转就成为面向对象设计的主要原则。
看下例:
public class EmailServie { public void SendMessage() {....} } public class NotificationSystem { private EmailServie svc; public NotificationSystem() { svc = new EmailServie(); } public void InterestingEventHappened() { svc.SendMessage(); } }
这是典型的正置,类EmailService属于具体类(低层模块),发出邮件;而NotificationSystem类则是调用类(高层模块),它调用(控制)具体类EmailService中的具体方法完成任务,这在面向对象设计中,是一种紧耦合。
这种紧耦合可能会造成如下问题:
1、当两个模块其中之一产生修改时,另外一个模块就会受到影响。如果多个模块紧耦合,这个影响就有可能造成软件系统修改量巨大,如果是封装出售的商业模块,对购买者今后的平滑升级与维护将造成极大困难。
2、如果这时NotificationSystem要发出的消息不是电邮,而是短信或者存到数据库中以后再看,这个类就需要再进行修改。
为解决这个问题,就需要把紧耦合变为松耦合,办法就是:加入一个抽象层,大家都依赖于抽象层,因为抽象层是最不容易变化的,从某种程序上来讲,设计完成之后,抽象层应该永远不变,如果需要,可以再添加其它抽象层。
这个抽象层可以是接口(Interface)或者抽象类(Abstract Class),但一般建议使用接口完成。
public interface IMessagingService { void SendMessage(); } public class EmailServie : IMessagingService { public void SendMessage() {....} } public class NotificationSystem { private IMessagingService svc; public NotificationSystem() { svc = new EmailServie(); } public void InterestingEventHappened() { svc.SendMessage(); } }
看上面的代码,添加了一个接口IMessagingService。然后高层模块NotificationSytem及低层模块EmailService都依赖于抽象的接口。低层模块EmailService通过实现接口完成对它的依赖,而高层模块NotificationSystem则通过声明接口IMessagingService(抽象层)实现了对它的调用(依赖),而这个调用实际是调用的更高层,因为抽象层是最高层,这种调用,就是控制反转或者依赖倒置。实际调用的是它的具体实现,而这个具体实现由EmailService中的SendMessage方法完成。
这里只是谈了控制反转或者依赖倒置的总原则。对于依赖倒置的具体办法,其它文章再谈。