I walk very slowly, but I never walk backwards
设计模式原则 - 依赖倒转原则
寂然
大家好,我是寂然~,本节课呢,我来给大家介绍设计模式原则之依赖倒转原则,话不多说,我们直接进入正题,老规矩,首先带大家了解一下依赖倒转原则的官方定义,并作一个解释,然后我们通过案例代码来具体分析
官方定义
依赖倒转原则,又称依赖倒置原则(Dependence Inversion Principle),又称DIP原则,官方定义为:
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.
(抽象不应该依赖于细节,细节应该依赖于抽象)
基本介绍
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构要比以细节为基础的架构稳定的多,在Java中,抽象指的是接口或抽象类,细节指具体的实现类,即要求程序要依赖于抽象接口,不要依赖于具体实现
使用接口或者抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
换句话说,接口或者抽象类的价值就在于设计
案例演示 - 钉钉消息
OK,那我们假设有这样一个场景,有一个工作人员,每天要接收钉钉工作消息,简易代码如下图所示
//工作人员
class Worker {
public void getMessage(Dingding dingding){
dingding.sendMessage();
}
}
//钉钉类
class Dingding{
public void sendMessage(){
System.out.println("钉钉上老板喊你加班呢");
}
}
//测试代码
public class InversionDemo{
public static void main(String[] args) {
new Worker().getMessage(new Dingding());
}
}
案例分析
OK,可以看到上面的代码,对案例场景进行了简易的实现,那我们就对上面的代码进行分析
现在是获取钉钉消息,如果我们获取的对象是微信,短信等,那根据上面的实现,我们需要新增类,同时Worker类也要增加相应的接收方法,这样显然维护性很差的同时,频繁修改代码也会带来意想不到的风险
那我们就想到了上面依赖倒转原则的定义,既然接口或者抽象类的价值就在于设计,我们可以引入一个接口来指定规范,表示信息的接收者,因为微信,钉钉都属于消息接收的途径,那就可以让他们实现该接口,来完成具体的细节,然后Worker与该接口发生依赖,使用依赖倒转的思想,完成代码改造
解决方案
OK,那根据上面的思路,我们对案例代码进行改造,改造后的代码示例如下:
//定义接口,指定接收消息的规范
interface IMessage{
public void sendMassage();
}
//钉钉消息
class Dingding implements IMessage{
@Override
public void sendMassage() {
System.out.println("钉钉上老板喊你开会啦");
}
}
//微信消息
class Weixin implements IMessage{
@Override
public void sendMassage() {
System.out.println("微信上老板喊你加班啦");
}
}
//工作人员
class Worker {
//这里是我们对于接口的依赖
public void getMessage(IMessage message){
message.sendMassage();
}
}
//简易测试代码
public class Inversion01Demo{
public static void main(String[] args) {
new Worker().getMessage(new Dingding());
new Worker().getMessage(new Weixin());
}
}
案例总结
可以看到,通过上面改造过的代码,我们定义了接口作为模板,让各种消息接收的途径去实现,这样的话如果业务要求还需要接收短信的消息,添加短信类实现接口即可,工作人员端不需要进行变动和维护,其实依赖倒置原则的本质上就是通过抽象(抽象类或接口)使各个类或模块实现彼此独立,不互相影响,实现模块间的松耦合
一句话:依赖倒置原则的核心就是面向抽象(抽象类或者接口)编程,大家要明白,以抽象为基准搭建起来的架构比以细节为基准搭建的架构要稳定的多,因此,在拿到需求之后,要先顶层再细节的方式来进行代码设计
依赖关系传递方式
当我们进行依赖倒转的时候,一定要进行依赖关系的传递,接着,我们简单聊聊依赖关系的三种传递方式
一、通过接口传递
使用接口传递,示例代码如下
//通过接口方式传递
interface IMessage{
public void sendMassage(Produce produce); //抽象方法,接收接口
}
interface Produce{
void produceMessage();
}
//工作人员
class Worker implements IMessage{
@Override
public void sendMassage(Produce produce) {
produce.produceMessage();
}
}
可以看到,Worker实现了接口 IMessage,实现了里面的方法,该方法参数接收了 Produce 接口类型,这就是一个典型的使用接口传递的案例,抽象方法sendMassage() 里面传的是一个接口
二、通过构造方法传递
使用构造方法传递,示例代码如下
//通过构造器方式传递
interface IMessage{
public void sendMassage(); //这里没有接收接口
}
interface Produce{
void produceMessage();
}
class Worker implements IMessage{
public Produce produce; //属性为Produce类型
public Worker(Produce produce) { //构造器来传递
this.produce = produce;
}
@Override
public void sendMassage() {
this.produce.produceMessage();
}
}
可以看到,这种方式,将Produce置为 Worker类的属性,值通过构造器来传递,赋给 this.produce ,然后在sendMassage()方法里通过 this.produce 来调用目标方法
三、通过setter()方法传递
使用setter()方法传递,实例代码如下
//通过setter方式传递
interface IMessage{
public void sendMassage();
}
interface Produce{
void produceMessage();
}
class Worker implements IMessage{
public Produce produce; //属性为Produce类型
public void setProduce(Produce produce) {
this.produce = produce;
}
@Override
public void sendMassage() {
this.produce.produceMessage();
}
}
可以看到,这种方式,将Produce置为 Worker类的属性,值通过setter()方法来传递,赋给 this.produce ,然后在sendMassage()方法里通过 this.produce 来调用目标方法produceMessage()
注意事项&细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或者接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里式替换原则(下章内容来详细介绍)
下节预告
OK,下一节,我们正式进入设计模式原则之里式替换原则的学习,我会为大家用多个案例分析,来解读设计模式原则之里式替换原则,以及它的注意事项和细节,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~