前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身。关于设计模式,作为程序猿的我们肯定都不陌生。博主的理解,所谓设计模式就是前人总结下来的一些对于某些特定使用场景非常适用的优秀的设计思路,“前人栽树,后人乘凉”,作为后来者的我们就有福了,当我们遇到类似的应用场景的时候就可以直接使用了。关于设计模式的原则,博主将会在接下来的几篇里面根据自己的理解一一介绍,此篇就先来看看设计模式的设计原则之——依赖倒置原则。
软件设计原则系列文章索引
- 小话设计模式原则之:依赖倒置原则DIP
- 小话设计模式原则之:单一职责原则SRP
- 小话设计模式原则之:接口隔离原则ISP
- 小话设计模式原则之:开闭原则OCP
- 小话设计模式原则之:里氏替换原则LSP
一、原理介绍
1、官方定义
依赖倒置原则,英文缩写DIP,全称Dependence Inversion Principle。
原始定义:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。
官方翻译:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
2、自己理解
2.1、原理解释
上面的定义不难理解,主要包含两次意思:
1)高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。换言之,模块间的依赖是通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
2)接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。这一点其实不用多说,很好理解,“面向接口编程”思想正是这点的最好体现。
2.2、被“倒置”的依赖
相比传统的软件设计架构,比如我们常说的经典的三层架构,UI层依赖于BLL层,BLL层依赖于DAL层。由于每一层都是依赖于下层的实现,这样当某一层的结构发生变化时,它的上层就不得不也要发生改变,比如我们DAL里面逻辑发生了变化,可能会导致BLL和UI层都随之发生变化,这种架构是非常荒谬的!好,这个时候如果我们换一种设计思路,高层模块不直接依赖低层的实现,而是依赖于低层模块的抽象,具体表现为我们增加一个IBLL层,里面定义业务逻辑的接口,UI层依赖于IBLL层,BLL层实现IBLL里面的接口,所以具体的业务逻辑则定义在BLL里面,这个时候如果我们BLL里面的逻辑发生变化,只要接口的行为不变,上层UI里面就不用发生任何变化。
在经典的三层里面,高层模块直接依赖低层模块的实现,当我们将高层模块依赖于底层模块的抽象时,就好像依赖“倒置”了。这就是依赖倒置的由来。通过依赖倒置,可以使得架构更加稳定、更加灵活、更好应对需求变化。
2.3、依赖倒置的目的
上面说了,在三层架构里面增加一个接口层能实现依赖倒置,它的目的就是降低层与层之间的耦合,使得设计更加灵活。从这点上来说,依赖倒置原则也是“松耦合”设计的很好体现。
二、场景示例
文章最开始的时候说了,依赖倒置是设计模式的设计原则之一,那么在我们那么多的设计模式中,哪些设计模式遵循了依赖倒置的原则呢?这个就多了,比如我们常见的工厂方法模式。下面博主就结合一个使用场景来说说依赖倒置原则如何能够使得设计更加灵活。
场景描述:还记得在一场风花雪月的邂逅:接口和抽象类这篇里面介绍过设备的采集的例子,这篇继续以这个使用场景来说明。设备有很多类型,每种设备都有登录和采集两个方法,通过DeviceService这个服务去启动设备的采集,最开始我们只有MML和TL2这两种类型的设备,那么来看看我们的设计代码。
代码示例:
//MML类型的设备
public class DeviceMML
{ public void Login() { Console.WriteLine("MML设备登录"); } public bool Spider() { Console.WriteLine("MML设备采集"); return true; } } //TL2类型设备 public class DeviceTL2 { public void Login() { Console.WriteLine("TL2设备登录"); } public bool Spider() { Console.WriteLine("TL2设备采集"); return true; } } //设备采集的服务 public class DeviceService { private DeviceMML MML = null; private DeviceTL2 TL2 = null; private string m_type = null; //构造函数里面通过类型来判断是哪种类型的设备 public DeviceService(string type) { m_type = type; if (type == "0") { MML = new DeviceMML(); } else if (type == "1") { TL2 = new DeviceTL2(); } } public void LoginDevice() { if (m_type == "0") { MML.Login(); } else if (m_type == "1") { TL2.Login(); } } public bool DeviceSpider() { if (m_type == "0") { return MML.Spider(); } else if (m_type == "1") { return TL2.Spider(); } else { return true; } } }
在Main函数里面调用
class Program
{
static void Main(string[] args) { var oSpider = new DeviceService("1"); oSpider.LoginDevice(); var bRes = oSpider.DeviceSpider(); Console.ReadKey(); }
上述代码经过开发、调试、部署、上线。可以正常运行,貌似一切都OK。
日复一日、年复一年。后来公司又来两种新的设备TELNET和TL5类型设备。于是程序猿们又有得忙了,加班,赶进度!于是代码变成了这样:
//MML类型的设备
public class DeviceMML
{ public void Login() { Console.WriteLine("MML设备登录"); } public bool Spider() { Console.WriteLine("MML设备采集"); return true; } } //TL2类型设备 public class DeviceTL2 { public void Login() { Console.WriteLine("TL2设备登录"); } public bool Spider() { Console.WriteLine("TL2设备采集"); return true; } } //TELNET类型设备 public class DeviceTELNET { public void Login() { Console.WriteLine("TELNET设备登录"); } public bool Spider() { Console.WriteLine("TELNET设备采集"); return true; } } //TL5类型设备 public class DeviceTL5 { public void Login() { Console.WriteLine("TL5设备登录"); } public bool Spider() { Console.WriteLine("TL5设备采集"); return true; } } //设备采集的服务 public class DeviceService { private DeviceMML MML = null; private DeviceTL2 TL2 = null; private DeviceTELNET TELNET = null; private DeviceTL5 TL5 = null; private string m_type = null; //构造函数里面通过类型来判断是哪种类型的设备 public DeviceService(string type) { m_type = type; if (type == "0") { MML = new DeviceMML(); } else if (type == "1") { TL2 = new DeviceTL2(); } else if (type == "2") { TELNET = new DeviceTELNET(); } else if (type == "3") { TL5 = new DeviceTL5(); } } public void LoginDevice() { if (m_type