03-依赖倒置原则(DIP)
1. 背景
类A是高层代码,类A直接依赖B,如果要将类A改为还要依赖C,则必须修改类A的代码来实现。在实际场景中,类A是高层,负责业务逻辑,类B和类C是低层模块,负责基本的原子操作,假如修改A,会给程序带来不必要的风险。
2. 定义
高层模块不直接依赖低层模块,二者都应该依赖其抽象(抽象类或接口),抽象不应该依赖细节,细节应该依赖抽象。
3. 解决方案
类A修改为依赖接口I,而类B和类C各自实现接口I,类A通过接口I间接的同类B和类C发生联系,这样就大大降低了类的A的修改几率。
4. 依赖倒置原则的核心
面向接口编程。
5. 依赖倒置原则基于一个事实
相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在.Net中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
6. 从框架搭建的角度来体会依赖倒置原则的好处
三层:数据库访问层、业务逻辑层、UI调用层。
1. 数据库访问层中有一个 MySqlHelp类,提供链接MySQL数据增删改查的方法。
2. 业务逻辑层有一个登录业务 CheckLogin(MySqlHelp mysql,string userName,string pwd)。
3. UI调用层要调用CheckLogin方法,这时候实例化一个MySqlHelp对象,传到CheckLogin方法中即可。
有一个天,要求支持oracle数据,所以 数据库访问层中增加了一个oracleHelper类,UI调用层按照常规实例化了一个oracleHelper对象,传到CheckLogin方法中,发现我的天!!!!CheckLogin竟然不支持oracleHelper对象,同时发现类似的 所有业务层的方法都不支持oracleHelper类,这个时候悲剧就发生了,如果全部改业务层的方法,基本上完蛋。
所以解决方案:依赖倒置原则,即面向接口编程。
1. 数据库访问层声明一个接口IHelper,里面有增删改查方法,MySqlHelp和oracleHelper都实现IHelper接口
2. 业务逻辑层有一个登录业务改为依赖接口IHelper, CheckLogin(IHelper iHelper,string userName,string pwd)
3. UI调用层要调用CheckLogin方法,想连哪个数据,就实例化哪个 eg IHelper iHelper=new MySqlHelp(); 或者 IHelper iHelper=new oracleHelper()
然后调用CheckLogin即可
这种解决方案还有一个好处:先把接口约束定义出来了,写MySqlHelp和oracleHelper的人和 业务逻辑层的人可以同时开发了,不必等数据库访问层写完后,再写业务逻辑了。
7. 以日常生活为线索的案例
一个母亲给儿子讲书上的故事,所以有一个mother类,一个book类。
1 public class mother 2 { 3 public void readStorey(book bk) 4 { 5 Console.WriteLine("妈妈开始讲故事"); 6 Console.WriteLine(bk.GetContents()); 7 } 8 9 }
1 public class book 2 { 3 public string GetContents() 4 { 5 return "我是书上的故事"; 6 } 7 }
1 public static void show() 2 { 3 //下面以一个更贴切的例子说明一下这个问题 4 //一位母亲给儿子讲书上的故事,有一个mother类 ,一个book类 5 mother mt = new mother(); 6 book bk = new book(); 7 mt.readStorey(bk); 8 9 }
突然有一天,儿子要求妈妈给他将报纸上的故事,我的天,妈妈竟然不会讲。
解决方案:mother类中readStorey类不在直接依赖book类,而是依赖book和newpaper类共同实现的接口类。
1 public interface IGetContents 2 { 3 string GetContents(); 4 }
1 public class NewBook:IGetContents 2 { 3 public string GetContents() 4 { 5 return "我是书上的内容"; 6 } 7 }
1 public class NewsPaper:IGetContents 2 { 3 public string GetContents() 4 { 5 return "我是报纸上的内容"; 6 } 7 }
1 public class mother 2 { 3 public void readStorey2(IGetContents bk) 4 { 5 Console.WriteLine("妈妈升级了,会依赖倒置原则了,开始讲故事"); 6 Console.WriteLine(bk.GetContents()); 7 } 8 }
1 public static void show() 2 { 3 //有一天,儿子要求妈妈给他讲报纸上的故事 4 //有一个NewsPaper类,发现妈妈竟然不会讲报纸上的故事 5 //所以下面重构一下代码: 新的NewBook类,NewsPaper,新的讲故事的方法readStorey2,新的接口IGetContents 6 IGetContents ig1 = new NewBook(); 7 IGetContents ig2 = new NewsPaper(); 8 mt.readStorey2(ig1); 9 mt.readStorey2(ig2); 10 11 }
8. 个人感悟
低层模块尽量都要有抽象类或接口,或者两者都有。
变量的声明类型尽量是抽象类或接口。
使用继承时遵循里氏替换原则。