项目开发-依赖倒置、里式替换、接口隔离的应用深入理解

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 项目开发-依赖倒置、里式替换、接口隔离的应用深入理解

前言


最近在做.net项目和学习这个设计模式中的依赖倒置和工厂方法,这个过程当中发现在开发这个.net项目中有很多不合理的地方,就是我们使用了接口,但是在前端开发的时候还是使用的new的方式去给接口实例化,这还是违背了依赖倒置的原则。因为项目中并没有使用spring这些相关的框架,只是一个简单的三层模式UBD,之前在java项目中因为直接使用了spring的框架而忽略了在这个问题。

这里声明一下本次的代码示例均为Java语言。.net实现思路和这个一模一样。


依赖倒置


定义


依赖倒置原则(Dependency Inversion Principle)是面向对象设计中的一个原则,它是SOLID原则中的一部分,由罗伯特·C·马丁(Robert C. Martin)提出。该原则强调了高层模块不应该依赖于低层模块的具体实现细节,而应该依赖于抽象接口。具体来说,依赖倒置原则有以下几个核心概念:


模块间的依赖关系应该通过抽象发生,而不是具体实现发生。这意味着高层模块和低层模块都应该依赖于抽象接口或抽象类,而不是具体的实现类。


抽象不应该依赖于具体实现。抽象接口应该定义在高层模块中,并且不应该受到具体实现类的影响。这样可以保持高层模块的稳定性和独立性。


具体实现应该依赖于抽象。具体实现类应该依赖于抽象接口或抽象类,通过实现抽象定义的方法来完成具体的功能。


通过遵循依赖倒置原则,可以提高系统的可维护性、扩展性和灵活性。它可以降低模块间的耦合度,使得系统更容易进行修改和测试。另外,依赖倒置原则也促进了面向接口编程(Interface-Oriented Programming)的实践,强调了定义良好的抽象接口和合理的接口设计。


需要注意的是,依赖倒置原则并不是要求完全避免依赖关系,而是通过合理的抽象和接口设计来管理和控制依赖关系,以提高系统的灵活性和可维护性。


不符合依赖倒置原则是什么样子


import com.example.onlystudent.DIPDemo.Dao.ItemDao;
import com.example.onlystudent.DIPDemo.Dao.ItemDao4MysqlImpl;
//B层实现类。
public class ItemManagerImpl {
    //作为B层,这里是直接依赖的D层的代码,可以看到使用的是D层的接口。
    // 但是还是在给接口实例化的时候使用到了具体的实现来,并且在这个B层的import中出现了实现类的相关信息。
    ItemDao itemDao=new ItemDao4MysqlImpl();
}
package com.example.onlystudent.DIPDemo.Dao;
//D层接口
public interface ItemDao {
  //仅做demo展示,并为声明任何方法 
}
package com.example.onlystudent.DIPDemo.Dao;
//D层接口的实现类,这里是一个MySQL语法的实现类
public class ItemDao4MysqlImpl implements ItemDao {
      //仅做demo展示,并为声明任何方法 
}
package com.example.onlystudent.DIPDemo.Dao;
//D层接口的实现类,这里是一个Oracle语法的实现类
public class ItemDao4OracleImpl implements ItemDao{
  //仅做demo展示,并为声明任何方法 
}


在这套代码中虽然也声明了接口,也有实现类,但是在具体使用过程中并没有遵循依赖倒置原则。体现在上述的ItemManagerImpl类中,因为按照依赖倒置原则在ItemManagerImpl类中只能出现D层的ItemDao的信息。它应该只知道ItemDao,如果现在需要更换数据库,那么就需要更改ItemManagerImpl中的代码,将new ItemDao4MysqlImpl()修改为new ItemDao4OracleImpl(),但是这并不符合开闭原则,所以这里使用的这个依赖倒置并不完善,或者说是仅仅有了一个形式上的依赖倒置,但是本质上并没有改变ItemManagerImpl有依赖于具体实现类。


😄完善


如何修改上面的代码,做到符合依赖倒置的原则呢?这就需要用到反射这个强大的功能,首先要添加一个配置文件。

在配置文件中加上需要反射的类路径,在这个demo中需要反射的类是mysql实现类。

添加完配置文件中的信息后,需要有一个读取配置文件的类,来获取配置文件中的类路径信息

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
 * @Description: 读取配置文件
 */
public class PropertiesReader {
    public static String getValue(String key) {
        Properties properties = new Properties();
        FileInputStream fis = null;
        String value="";
        try {
            fis = new FileInputStream("application.properties");
            properties.load(fis);
            // 读取配置项
            value= properties.getProperty(key);
            return value;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return value;
    }
}


有了以上的这些准备后,现在开始修改ItemManagerImpl这个类中的依赖问题


将new这个操作删除,修改为通过反射来获取对象。

通过使用反射加配置文件在构造函数中将itemDao给实例化出对象来。这样就符合了依赖倒置,这个时候在看ItemManagerImpl类中它的依赖中就只有ItemDao这个接口了。



后期项目中如果需要更换数据库,那这里去操作出数据库的类,就可以在配置文件中进行更换,只需将类路径更换即可。以上这些操作只是为了简单的描述出来,依赖倒置具体的实现和应用。实际开发过程中要考虑的还是很多的,比如对象怎么管理啊这些工作。


里式替换


定义


里氏替换原则的内容可以描述为: “派生类(子类)对象可以在程式中代替其基类(超类)对象。


具体应用


在上述的代码中其实就是基于里式替换实现的,虽然在定义中说是基类和派生类,但是接口同样适用里式替换的原则,根据里式替换原则,如果一个类通过实现一个接口来表达其行为,那么在使用接口时,任何实现该接口的类都应该能够替换该接口,而不会破坏程序的正确性。

具体体现在ItemDao itemDao=new ItemDao4MysqlImpl();虽然后面修改为了反射获取,通过类型转换为了ItemDao

itemDao=(ItemDao)Class.forName(PropertiesReader.getValue("itemDao")).newInstance();

但是这依然符合。


接口隔离


定义


接口隔离原则的核心思想是将庞大而臃肿的接口拆分为更小、更具体的接口,使得每个接口只包含客户端所需的方法。这样可以降低类和模块之间的耦合度,减少对不相关方法的依赖,提高代码的灵活性、可维护性和可复用性。

以下是接口隔离原则的几个要点:


接口应该精简而专注于特定的功能领域。不应该设计臃肿的接口,包含大量不相关的方法。


客户端不应该强制依赖于它不需要的接口。一个类或模块只应该依赖于它需要使用的接口,而不是依赖于多余的接口。


接口隔离原则鼓励根据不同的客户端需求定义多个小接口,而不是一个大而全的接口。这样可以提供更高的灵活性和可扩展性。


接口设计应该符合单一职责原则,即一个接口只负责定义一个单一的功能或角色。


具体应用


接口隔离原则在我们上述的依赖倒置这里其实就已经体现了,访问数据库的操作行为抽象为一个接口,这个接口包含了数据库操作的所有行为,而作为B层的ItemManagerImpl来说,它只依赖于接口,而具体的实现类单独写,如果后期需要添加信息的数据库,直接添加一个实现类即可,搭配着依赖倒置的原则和里式替换,通过反射去创建对象。程序的灵活性就大大提高了。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
7月前
|
数据采集 Web App开发 调度
Headless Chrome 优化:减少内存占用与提速技巧
在数据驱动的时代,爬虫技术至关重要。本文聚焦 Headless Chrome 优化方案,解决传统爬虫内存占用高、效率低等问题。通过无界面模式、代理 IP等配置,显著降低资源消耗并提升速度。实际案例中,该方案用于采集汽车点评数据,性能提升明显:内存占用降低 30%-50%,页面加载提速 40%-60%。结合技术架构图与演化树,全面解析爬虫技术演进,助力高效数据采集。
312 0
Headless Chrome 优化:减少内存占用与提速技巧
|
10月前
|
机器学习/深度学习 人工智能 数据处理
《C++与 Python 人工智能框架的无缝对接:开启数据处理新境界》
在数字化时代,C++和Python分别在数据处理和人工智能领域展现独特优势。C++以其高效能和底层资源控制能力,适用于数据的初步处理;Python则因简洁灵活及丰富的AI库,擅长复杂算法的实现。两者结合,不仅强化了数据处理与分析能力,还为解决实际问题提供了新途径,成为技术领域的热点。本文探讨了这种集成的重要性和应用场景,如图像识别、金融分析等,并讨论了集成过程中可能遇到的挑战及解决方案,最后分享了成功案例与未来展望。
169 10
|
12月前
|
存储
二维数组在物理上以及逻辑上的数组维度理解
C 语言中,二维数组在物理上按行优先连续存储,可视为一维数组的数组;逻辑上呈现行和列的结构,支持通过双下标访问元素,适用于矩阵和表格等数据结构的表示与操作。
|
12月前
|
缓存 监控 固态存储
如何优化磁盘性能?
【10月更文挑战第4天】如何优化磁盘性能?
575 4
|
JavaScript Java 测试技术
大学生体质测试|基于Springboot+vue的大学生体质测试管理系统设计与实现(源码+数据库+文档)
大学生体质测试|基于Springboot+vue的大学生体质测试管理系统设计与实现(源码+数据库+文档)
287 0
|
12月前
|
Web App开发 前端开发 JavaScript
JavaScript动态渲染页爬取——Playwright的使用(一)
JavaScript动态渲染页爬取——Playwright的使用(一)
1090 2
|
机器学习/深度学习 人工智能 算法框架/工具
使用Python实现深度学习模型:智能家电控制与优化
使用Python实现深度学习模型:智能家电控制与优化
367 22
使用Python实现深度学习模型:智能家电控制与优化
|
机器学习/深度学习 人工智能 算法
首个像人类一样思考的网络!Nature子刊:AI模拟人类感知决策
【9月更文挑战第8天】近日,《自然》子刊发表的一篇关于RTNet神经网络的论文引起广泛关注。RTNet能模拟人类感知决策思维,其表现与人类相近,在反应时间和准确率上表现出色。这项研究证明了神经网络可模拟人类思维方式,为人工智能发展带来新启示。尽管存在争议,如是否真正理解人类思维机制以及潜在的伦理问题,但RTNet为人工智能技术突破及理解人类思维机制提供了新途径。论文详细内容见《自然》官网。
255 3
|
Kubernetes 安全 数据安全/隐私保护
在k8S中,如何保证集群的安全性?
在k8S中,如何保证集群的安全性?
|
JSON NoSQL Redis
Redis 作为向量数据库快速入门指南
Redis 作为向量数据库快速入门指南
1000 1