0x03、设计模式原则 —— 依赖倒置(倒转)原则
概念
先理解下面两个概念:
- 抽象:接口或抽象类
- 细节:具体的实现类
依赖倒置有如下5条概念:
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
先了解什么意思,后必须记熟这5条概念,很重要的!! (我觉得这5条很白话文了,如果你还是不理解,建议做一个 Asp.Net Core Web Api 仓储模式 项目,这仓储模式里面用到的就是:面向接口编程的,里面用的这个模式太明显了)
不理解的,先看下面演示 !
演示
第一版, Person 接收消息的功能,我们很传统的写法方式:
Persion _persion = new Persion(); _persion.receive(new Email()); class Email{ public string getInfo(){ return $"电子邮件信息:hello,world."; } } // 完成 Persion 接收消息的功能 class Persion{ // 这里依赖了Email这个类(即:细节、实现) public void receive(Email email) { Console.WriteLine(email.getInfo()); } }
上面代码中,Persion类中强依赖了 Email 类(细节),如果下次我换成 接收WeChat类 呢?肯定是 需要在 Persion 类中增加相应的方法了吧;
我们思考下,依赖倒置原则是:面向接口编程,依赖的是抽象,而不是具体的实现,上面代码中,我们可以把依赖的细节(),改为依赖的是一个抽象;
即:新增一个抽象接口 IReceiver,表示接收者,这样Persion 类与接口发生了依赖;
因为 Email, WeiXin 等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符合了依赖倒转原则;
Persion _persion = new Persion(); _persion.receive(new Email()); _persion.receive(new WeChat()); interface IReceiver { public string getInfo(); } class Email : IReceiver { public string getInfo() { return $"电子邮件信息:hello,world."; } } class WeChat : IReceiver { public string getInfo() { return $"微信信息:hello,kiki."; } } class Persion { // 这里,我们是第接口的依赖了 public void receive(IReceiver receiver) { Console.WriteLine(receiver.getInfo()); } }
依赖关系传递的三种方式和应用案例
接口传递
class Changhong : ITV { public void play() { Console.WriteLine("打开长虹电视"); } } public static void Main(string[] args) { OpenAndClose openAndClose =new OpenAndClose(); openAndClose.open(new Changhong()); } // 方式1:通过接口传递实现依赖 // 开关的接口 interface IOpenAddClose { public void open(ITV tv); // 抽象方法、接收接口 } interface ITV { public void play(); } class OpenAndClose : IOpenAddClose { // 实现接口 public void open(ITV tv) { tv.play(); } }
构造方法传递(超常见)
class Changhong : ITV { public void play() { Console.WriteLine("打开长虹电视"); } } public static void Main(string[] args) { OpenAndClose openAndClose =new OpenAndClose(new Changhong()); openAndClose.open(); } // 方式1:通过接口传递实现依赖 // 开关的接口 interface IOpenAddClose { public void open(); // 抽象方法、接收接口 } interface ITV { public void play(); } class OpenAndClose : IOpenAddClose { private readonly ITV _tv; public OpenAndClose(ITV tv) { _tv = tv; } // 实现接口 public void open() { _tv.play(); } }
Setter传递(不常见)
class Changhong : ITV { public void play() { Console.WriteLine("打开长虹电视"); } } public static void Main(string[] args) { OpenAndClose openAndClose =new OpenAndClose(); openAndClose.setTv(new Changhong()); openAndClose.open(); } // 方式1:通过接口传递实现依赖 // 开关的接口 interface IOpenAddClose { public void open(); // 抽象方法、接收接口 public void setTv(ITV tv); } interface ITV { public void play(); } class OpenAndClose : IOpenAddClose { private ITV _tv; public void setTv(ITV tv) { this._tv = tv; } // 实现接口 public void open() { _tv.play(); } }
注意和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
- 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化;(这句话不太好理解,看下面解释)
- 继承时遵循里氏替换原则 (这个下一节就说到了)
第2点 解释:假设,有 类A 继承 抽象接口B,那么存在:B b = new A(); b变量作为缓冲层,如果有 类C 继承至 抽象接口B,那么也可以: B b = new C(),即:
interface B{} class A: B{} class C: B{} B b = new A(); b = new C(); // b变量就是一个缓冲层的意思,在 B 和 A 或者 B 和 C 之间
还是不懂的话,建议先放一放,做一些 面向接口编程 的项目后,在来这里回顾一下就行了;
这章其实有一点点难以理解,但是你迟早会看懂这个的,因为目前web的后端开发的的项目中,都用到了这个原则,比如 Asp.NET Core 的仓储模式 ;
好了,下一章,里氏替换原则 ,走起~