面向对象设计原则之依赖倒置原则

news2024/11/18 23:34:19

目录

  • 定义
    • 原始定义
    • 进一步的理解
  • 作用
  • 实现方法
  • 代码示例

定义

依赖倒置原则(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
翻译一下:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

进一步的理解

到底什么“倒置”,要理解什么是倒置,我们先理解一下“正置”,即正常的依赖是什么样子的。比如我们经典的三层架构,controller层调用BL层,BL层调用DAO层。由于每一层都是依赖于下层的实现,这样当某一层的结构发生变化时,它的上层就不得不也要发生改变,比如我们DAO里面逻辑发生了变化,可能会导致BL和Controller层都随之发生变化,这种架构是非常荒谬的!

好,这个时候如果我们换一种设计思路,高层模块不直接依赖低层的实现,而是依赖于低层模块的抽象,具体表现为我们增加一个IBL层,里面定义业务逻辑的接口,controller层依赖于IBL层,BL层实现IBL里面的接口,所有具体的业务逻辑则实现在BL里面,这个时候如果我们BL里面的逻辑发生变化,只要接口的行为不变,上层Controller里面就不用发生任何变化。

以上我们引入面向接口编程的概念,增加了接口IBL层,那倒置到底该如何理解,难道依赖抽象,面向接口就是倒置了?

我觉得可以从软件项目越来越大,开发团队人员越来越多的发展现状来理解,高层A依赖于低层B,即A要调用B提供的方法,那么在B开发完成之前,A层是没发开发,或者开发完没法编译通过和单元测试的。修改为高层A依赖于抽象层C,抽象层C是属于A层的,即由A层来规定抽象层C的接口规范,低层B也依赖于抽象层C来具体实现C中的接口,因此通过引入C层,来达到了“倒置”。通过该倒置,引入C层来规范,A和B 可以同时 来开发,不必相互等待(依赖)。这里的倒置,既有模块依赖上的倒置,更有在解决问题时,思考和规划上的倒置,即要先进行良好的顶层规划设计,约定好接口规范,而具体的逻辑编写都是基于规范的具体而已。

作用

  • 可以减少类间的耦合性、提高系统稳定性。
  • 提高代码可读性和可维护性,可降低修改程序所造成的风险。
  • 可以减少并行开发引起的风险。

实现方法

主要就是合理的抽象接口类并定义接口方法。以下代码示例中以 司机驾车为例,结合代码重构过程来具体说明。

代码示例

司机驾驶奔驰车的类图,起初 我们设计的都是具体实现类,司机类依赖于奔驰车类,Client类中有司机,有车,可以具体创建对象来使用了。

在这里插入图片描述

package com.will.tools.model.dip;
 
public class Benz {
 
    public void run(){
        System.out.println("奔驰汽车跑起来...");
    }
}

奔驰车可提供一个方法run,代表车辆运行。

package com.will.tools.model.dip;
 
public class Driver {
    public void drive(Benz benz){
        benz.run();
    }
}

开车,调用奔驰车的run方法。

package com.will.tools.model.dip;
 
public class Client {
    public static void main(String[] args) {
        Driver guojing = new Driver();
        Benz benz = new Benz();
        //郭靖开奔驰
        guojing.drive(benz);
    }
}

Client创建 司机郭靖和奔驰车,并让郭靖开奔驰车。

现在来了新需求:郭靖司机不仅要开奔驰车,还要开宝马车,又该怎么实现呢?

先把宝马车创建出来,如下

package com.will.tools.model.dip;

public class BMW {
    public void run(){
        System.out.println("宝马汽车跑起来...");
    }
}

宝马车产生了,但郭靖却没有办法开起来,为什么?
郭靖(Driver)没有开动宝马车的方法,一个拿有C1驾照的司机竟然只能开奔驰车而不能开宝马车,这太不合理了!在现实世界都不允许这样干,何况程序还是对现实世界的抽象呢。

这说明我们的设计出了问题,司机类和奔驰车类紧耦合了,导致系统可维护性和可读性降低。这里只是增加了一个车类,却要修改司机类,被依赖者变更了,却需要让依赖者来承担修改成本,这没有稳定性可言。

另外,对于并行开发的风险也很大,没有奔驰车类,司机类根本编译不过去。
因此,我们重构一下,引入DIP,如下:
在这里插入图片描述
建立两个接口:IDriver和ICar,分别定义了司机和汽车的各个职能,司机就是驾驶汽车,必须实现drive()方法。汽车就是能run。

package com.will.tools.model.dip;
 
public interface ICar {
    void run();
}
package com.will.tools.model.dip;
 
public interface IDriver {
    void drive(ICar car);
}
package com.will.tools.model.dip;
 
public class Driver implements IDriver {
    @Override
    public void drive(ICar car) {
        car.run();
    }
}

接口只是一个抽象化的概念,是对一类事物的最抽象描述,具体的实现代码由相应的实现类来完成。

IDriver通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,至于到底是哪个型号的Car,需要声明在高层模块。

宝马汽车和奔驰汽车都实现ICar接口,并各自实现run方法。

package com.will.tools.model.dip;
 
public class Benz implements ICar {
 
    @Override
    public void run() {
        System.out.println("奔驰汽车跑起来...");
    }
}
package com.will.tools.model.dip;
 
public class BMW implements ICar {
    @Override
    public void run() {
        System.out.println("宝马汽车跑起来...");
    }
}

业务场景应贯彻“抽象不应依赖细节”,即抽象(ICar接口)不依赖BMW和Benz两个实现类(细节),因此在高层次的模块中应用的都是抽象,传入的参数都是ICar,如下:

package com.will.tools.model.dip;
 
public class Client {
    public static void main(String[] args) {
//        Driver guojing = new Driver();
//        Benz benz = new Benz();
//        //郭靖开奔驰
//        guojing.drive(benz);
        IDriver guojing = new Driver();
        ICar benz = new Benz();
        guojing.drive(benz);
 
        ICar bmw = new BMW();
        guojing.drive(bmw);
    }
}

Client属于高层业务逻辑,它对低层模块的依赖都建立在抽象上。guojing的表面类型是IDriver,benz的表面类型是ICar。这时,guojing再要开宝马车的话,就只需要在高层业务类(Client)中直接调用即可,而不用修改Driver类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1109794.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker图形胡界面管理工具--Portainer可视化面板安装

1.安装运行Portainer docker run -d -p 8088:9000 \ > --restartalways -v /var/run/docker.sock:/var/run/docker.sock --privilegedtrue portainer/portainer--restartalways:Docker启动后容器自动启动 -p:端口映射 -v:路径映射2.通过…

RIAC-V架构开发——CSR指令访问控制与状态寄存器的两种方式(寄存器名字、寄存器编号)

1、CSR指令介绍 (1)CSR,即Control and Status Register,控制与状态寄存器,属于CPU自带的一类寄存器,csr寄存器采用12bit编码,共支持4096个csr寄存器,其中RISC-V架构规定占用了部分地…

坚果N1 Air、极米Z7X和NEW Z6X怎么选,这篇选购指南必看

许多朋友曾表示,一直想入手一款家用投影仪,来打造家庭影院的观影氛围,但市面上的产品太多了,看了一个礼拜的选购教程都没有想好到底买哪款,为了帮助消费者更“懂行”地挑选产品,我们今天就以2K价位段的投影…

使用Lychee搭建个人图片存储系统并进行远程访问设置实现公网访问本地私人图床

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站,可以看做是云存储的一部分,既可…

2023 年程序员必读的 27 本软件开发书籍

不断发展的软件开发领域需要不断学习和改进。现代开发实践要求软件工程师具备全面的知识,包括各个领域的理论见解和实践技术,从编程语言和数据库管理到质量保证、网页设计和 DevOps 实践。 这就是编程书籍可以提供帮助的地方。通过及时了解此类书籍并应…

2023年中国婚恋交友服务行业发展趋势分析:数字化、智能化将成必然趋势[图]

婚恋交友服务提供商是指围绕适婚人群的婚恋或亲密关系需求,提供恋爱交友、相亲匹配及情感咨询等服务的企业。常见的模式主要分为两种:第一,由人脸识别、身份验证、属性分析公有云及大数据分析等信息技术为驱动的线上平台。第二,由…

功能超全的微信小程序制作源码 含15大主流功能小程序 源码开源可二开 持续更新升级

给大家分享介绍一款功能超全的微信小程序制作源码,它包含了15大主流功能小程序,像微同城、电商类、在线报名、社区团购、外卖点餐、AI智能名片等小程序都有,根据需求可任意调用,自由DIY,开发属于你自己的小程序&#x…

C语言笔记之指针(二)

前言 本文主要介绍指针与内存和地址的关系,指针与内存关系深入解析,包括占用大小、动态分配,提醒使用sizeof()注意点。仅供大家参考学习,若有错换之处,欢迎交流指导~ 指针在不同位数电脑中的内存占用 在上图左边可以看出,不同位数的电脑中,指针本身所占…

重学前端-js类型

javascript目前位置一共有7种数据类型,分别为: Undefined;Null;Boolean;String;Number;Symbol;Object。 Undefined与Null Undefined 类型表示未定义,它的类型只有一个…

1455. 检查单词是否为句中其他单词的前缀

1455. 检查单词是否为句中其他单词的前缀 JS代码:js就很便捷!startsWith() /*** param {string} sentence* param {string} searchWord* return {number}*/ var isPrefixOfWord function(sentence, searchWord) {let arr sentence.split( );for (let …

JavaScript处理按键事件-模态窗口

上一节中我们基本实现了模态窗口的功能,本文将介绍我们可以通过用户的按键然后去执行一些事情 document.addEventListener(keydown,function() {console.log("我按下了一个按键"); })这段代码会添加一个事件监听器,当有键盘按键被按下时&#…

权威认证!海云安获得数据安全服务能力评定资格证书

为切实提升数据安全保障能力,加速推进国家数据安全建设,中国计算机行业协会数据安全专业委员会依托能力评价工作组从数据安全评估和数据安全建设两个方面开展了数据安全服务能力评定。 经过自主申报、专家评审等程序,深圳海云安网络安全技术…

TCP/IP(二十二)TCP 实战抓包分析(六)TCP 快速建立连接

一 TCP Fast Open 快速建立连接 说明: 之前讲解TCP 相关知识点遗漏了这个知识点,补充上 ① TFO简介 ② 请求 Fast Open Cookie过程 "原理图" ③ 真正开始 TCP Fast Open 重点: TFO 使 SYN包 可以包含payload 数据 ④ 抓包分析 1、…

使用单个mybatis框架进行mysql数据库的连接和操作?

使用单个mybatis框架进行mysql数据库的操作? 简述 先来说一下数据库连接的演变,刚开始的时候,最原生的连接并操作mysql数据库的办法是使用JDBC技术,这种技术相对来说写的源码比较多,比较繁杂; 后来就出现…

第十六届中国智慧城市大会 | 国产化三维重建技术服务智慧城市建设

2023年10月13日,由武汉大势智慧科技有限公司、飞燕航空遥感技术有限公司主办的第十六届智慧城市大会-实景三维技术创新与应用论坛在广州成功举办。 来自实景三维、自然资源、数字孪生、AI大数据、航空遥感等多个领域的专家,深度分享各自的智慧城市建设经…

【2023双十一新玩法】如何快速让数据达到预期目的,让销售额提升10倍,客群提升10倍,销量提升10倍

2023年的双十一即将到来,商家们依然在为销售目标而努力,而数据分析就成为了其中不可或缺的一环。大大小小的企业都纷纷采用了数据分析来提高销售效率,而老板们也都习惯了看着大屏幕上的实时数据。 然而,对于员工而言,做…

在线商城系统软件、源码、报价_OctShop

随着互联网、5G、人工智能的快速发展,人们在家购物已经是生活的重要方式。各种在线商城系统的不断涌现,同时,也给传统的企业商家销售带来了不小的压力,那么,如何调整,以适应时代的发展呢?经过不…

把Resources目录标记为Resource Root目录才能找到文件?

把Resources目录标记为Resource Root目录才能找到文件? mybatis配置文件中配置的mapper.xml映射文件找不到?这是为什么呢?因为你当resources目录不是根目录,重新标记成根目录就可以了,如下图:

Java项目-网页聊天程序

目录 项目介绍 项目功能简介 项目创建 用户管理模块 1.数据库设计及代码实现 2.前后端交互接口的设计 3.服务器代码开发 好友管理模块 数据库设计 好友表设计的两个重要问题 设计前后端交互接口 服务器代码 会话管理模块 会话的数据库设计 获取会话信息 约定前后…

VisualStudio 远程Debug

注意:纯靠记性,可能实际有出入。 1.连接本地电脑和远程电脑,我是通过ToDesk软件进行桌面连接的。 2.本地运行的项目,把项目打包后拷贝到远程电脑。 3.打开本地VisualStudio的安装位置,有个文件夹叫Remote Debugger。把…