软件架构:依赖倒置原则的魅力

news2024/11/13 10:01:17

依赖倒置原则(Dependency Inversion Principle, DIP)是面向对象设计的基本原则之一,由罗伯特·C·马丁(Robert C. Martin)提出。这一原则旨在降低系统中各个组件之间的耦合度,提高系统的可维护性和可扩展性。
理解软件开发的黄金法则:依赖倒置原则

依赖倒置原则的核心思想包括:

  1. 高层次模块不应该依赖于低层次模块,二者都应该依赖于抽象
  2. 抽象不应该依赖于细节,细节应该依赖于抽象

解释:

  • 高层次模块指的是应用中负责业务逻辑的部分,这些模块更关注于“做什么”。
  • 低层次模块则负责实现具体的细节,如数据访问层等,这些模块更关注于“怎么做”。
  • 抽象通常是指接口(Interface)或抽象类(Abstract Class),它们定义了高层模块和低层次模块交互的方式。
  • 细节是指具体的实现类,这些类实现了抽象定义的行为。

🔍 核心思想与应用场景

  • 使用接口或抽象类来定义高层模块与低层模块之间通信的协议。
  • **通过依赖注入(Dependency Injection, DI)**机制将具体的实现传递给高层模块,这样可以在运行时动态改变实现,增加系统的灵活性。
  • 避免硬编码具体的类到高层模块中,而是使用抽象引用这些类。

示例:

假设我们有一个应用程序,其中有一个OrderService(高层次模块)用于处理订单逻辑,还有一个Database(低层次模块)用于数据存储。

传统方式:
public class OrderService {
    private Database database;

    public OrderService() {
        this.database = new Database();
    }

    public void placeOrder(Order order) {
        // 处理订单逻辑
        database.save(order);
    }
}
应用依赖倒置原则:
// 定义抽象层
public interface DataStore {
    void save(Order order);
}

public class OrderService {
    private DataStore dataStore;

    public OrderService(DataStore dataStore) {
        this.dataStore = dataStore;
    }

    public void placeOrder(Order order) {
        // 处理订单逻辑
        dataStore.save(order);
    }
}

public class Database implements DataStore {
    @Override
    public void save(Order order) {
        // 数据库保存逻辑
    }
}

在这个例子中,OrderService不再直接依赖于Database的具体实现,而是依赖于DataStore接口。这样做的好处是:

  • OrderService可以与任何实现了DataStore接口的对象进行交互,提高了灵活性。
  • 如果需要更换数据库实现,只需更改DataStore的实现即可,无需修改OrderService
  • 测试变得更容易,可以通过构造不同的DataStore实现来进行单元测试。

通过遵循依赖倒置原则,我们可以构建出更加灵活、可扩展和易于维护的软件系统。

案例分析:解耦登录功能与数据库操作

假设我们的应用需要支持多种不同的数据库系统(如MySQL、PostgreSQL等)。按照传统的做法,登录服务可能直接依赖于特定数据库的操作类。但如果我们采用依赖倒置原则,就可以定义一个通用的数据库接口,然后为每种数据库实现一个具体的操作类。这样,在切换数据库时,只需更换相应的操作类,并确保它实现了通用接口即可。这种方式大大增强了代码的复用性和维护性。这是一个很好的例子来说明如何应用依赖倒置原则(Dependency Inversion Principle, DIP)来解耦登录功能与数据库操作。下面我们详细分析一下这个案例,并给出具体的代码示例。

案例背景

假设我们的应用需要提供用户登录功能,而登录过程中需要查询数据库来验证用户的用户名和密码。为了支持多种数据库系统(例如MySQL、PostgreSQL等),我们需要确保登录功能不直接依赖于具体的数据库操作类。

解决方案

  1. 定义抽象层:创建一个通用的数据库接口,定义所有数据库操作所需的通用方法。
  2. 实现具体操作类:为每种数据库系统实现具体的数据库操作类,并确保它们实现了上述的通用接口。
  3. 使用依赖注入:在登录服务中通过依赖注入的方式引入数据库操作接口的实例。

代码示例

1. 定义抽象层

首先,我们定义一个通用的数据库接口DatabaseAccess,该接口定义了查询用户信息的方法。

public interface DatabaseAccess {
    User getUser(String username);
}
2. 实现具体操作类

接着,我们为两种不同的数据库系统实现具体的数据库操作类。

MySQLDatabaseAccess
public class MySQLDatabaseAccess implements DatabaseAccess {
    @Override
    public User getUser(String username) {
        // MySQL数据库查询逻辑
        System.out.println("Fetching user from MySQL database: " + username);
        return new User(username, "password123");
    }
}
PostgreSQLDatabaseAccess
public class PostgreSQLDatabaseAccess implements DatabaseAccess {
    @Override
    public User getUser(String username) {
        // PostgreSQL数据库查询逻辑
        System.out.println("Fetching user from PostgreSQL database: " + username);
        return new User(username, "password456");
    }
}
3. 使用依赖注入

最后,我们创建登录服务LoginService,并通过依赖注入的方式引入DatabaseAccess接口的实例。

public class LoginService {
    private final DatabaseAccess databaseAccess;

    public LoginService(DatabaseAccess databaseAccess) {
        this.databaseAccess = databaseAccess;
    }

    public boolean login(String username, String password) {
        User user = databaseAccess.getUser(username);
        if (user != null && user.getPassword().equals(password)) {
            System.out.println("Login successful for user: " + username);
            return true;
        } else {
            System.out.println("Invalid credentials for user: " + username);
            return false;
        }
    }
}

使用示例

现在,我们可以根据实际使用的数据库类型来选择不同的数据库操作类。

public class Main {
    public static void main(String[] args) {
        // 选择数据库操作类
        DatabaseAccess databaseAccess = new MySQLDatabaseAccess();

        // 创建登录服务实例
        LoginService loginService = new LoginService(databaseAccess);

        // 登录尝试
        boolean loginResult = loginService.login("testuser", "password123");

        // 输出结果
        System.out.println("Login result: " + loginResult);
    }
}

在这里插入图片描述

总结

通过这种方式,我们实现了登录功能与数据库操作的解耦。这意味着当需要切换数据库系统时,只需要更改databaseAccess变量的值即可,而无需修改LoginService的代码。这不仅降低了模块间的耦合度,还提高了代码的可维护性和可扩展性。

🌟 优点与缺点

虽然依赖倒置原则带来了诸多好处,比如提高了代码的复用性和系统的可维护性,但它也有其局限性。引入额外的抽象层可能会使得简单的调用关系变得复杂,对于一些稳定的调用关系来说,可能并不需要这样的复杂性。因此,在实际开发中,我们需要根据项目的具体需求和复杂度来合理运用这一原则。

📘 小结

依赖倒置原则是构建大型、易扩展、可重用框架或系统的核心原则之一。它强调了高层和低层模块应依赖于抽象而非具体实现,这有助于我们构建更加灵活和稳定的软件系统。当然,每种实现都有其适用场景,关键在于如何根据实际需求做出最合适的选择。希望这篇分享能帮助大家更好地理解和应用依赖倒置原则,优化自己的软件设计和提高代码的复用性及降低维护成本。如果喜欢这类内容,别忘了关注我哦!

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

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

相关文章

四川正信法律:借钱不还报警有没有用

在日常生活中,金钱往来是人际交往中不可或缺的一部分。然而,当借钱不还成为一种普遍现象时,人们往往会感到困惑和无奈。那么,面对借钱不还的情况,报警是否有用呢? 我们要明确一点,借钱不还属于民事纠纷&am…

注意力机制 — 它是什么以及它是如何工作的

一、说明 注意力机制是深度学习领域的一个突破。它们帮助模型专注于数据的重要部分,并提高语言处理和计算机视觉等任务的理解和性能。这篇文章将深入探讨深度学习中注意力的基础知识,并展示其背后的主要思想。 二、注意力机制回顾 在我们谈论注意力之前&…

TcpSocket在切后台后如何保活

1)TcpSocket在切后台后如何保活 2)Magica Clothes 2插件与Burst编译问题 3)粒子拖尾合批失败怎么办 4)如何让射线追踪跟随我FPS游戏的十字准星进行移动 这是第398篇UWA技术知识分享的推送,精选了UWA社区的热门话题&…

适合学生党的运动耳机都有哪些?五大适合学生党的运动耳机推荐

2024年春季,开放式蓝牙耳机就凭借“佩戴舒适、开放安全”等优势火热出圈,这让各大音频厂商更新迭代速度不断加快,新品层出不穷。而用户面对市场上琳琅满目的开放式蓝牙耳机,一时间也不知道如何选择。那么对于学生党来说&#xff0…

linux 基本指令讲解 下

基本指令 date 显示 date 指定格式显示时间:date%Y:%m:%d(冒号可以随便改) 在显示方面 %H : 小时(00..23) %M : 分钟(00..59) %S : 秒(00..61) %X : 相当于 %H:%M:%S %d : 日 (01..31) %m : 月份 (01..12) %Y : 完整年份 (0000..9999) %F : 相当于 %Y-%m-%d在时间设置方面 date…

依赖注入+中央事件总线:Vue 3组件通信新玩法

​🌈个人主页:前端青山 🔥系列专栏:Vue篇 🔖人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vue篇专栏内容:Vue-依赖注入-中央事件总线 大家好,依旧青山, 最近呢也随着需求的变更调优…

Prometheus部署和基本操作

1 项目目标 (1)对Prometheus有基本的了解 (2)能够部署出一套Prometheus看板系统 (3)对Prometheus界面熟悉 1.1 规划节点 主机名 主机IP 节点规划 prome-master01 10.0.1.10 服务端 prome-node01 …

java基础学习笔记1

Java编程规范 命名风格 1. 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 反例:_name / __name / $name / name_ / name$ / name__ 2. 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直…

社交媒体分析:如何利用Facebook的数据提升业务决

在数字化时代,社交媒体已经成为企业战略中不可或缺的一部分。Facebook,作为全球最大的社交平台之一,提供了丰富的数据资源,这些数据不仅能够帮助企业了解市场趋势,还能提升业务决策的精准度。本文将探讨如何有效利用Fa…

CV党福音:YOLOv8实现实例分割(一)

前面我们得知YOLOv8不但可以实现目标检测任务,还包揽了分类、分割、姿态估计等计算机视觉任务。在上一篇博文中,博主已经介绍了YOLOv8如何实现分类,在这篇博文里,博主将介绍其如何将实例分割给收入囊中。 YOLOv8实例分割架构图 …

Spring Boot3.3.X整合Mybatis-Plus

前提说明&#xff1a; 项目的springboot版本为&#xff1a;<version>3.3.2</version> 需要整合的mybatis-plus版本&#xff1a;<version>3.5.7</version> 废话不多说&#xff0c;开始造吧 1.准备好数据库和表 2.配置全局文件application.properti…

本地连接服务器redis

详细步骤 1.看一下服务器上redis实例的运行状态&#xff1a; [rootiZuf67k70ucx14s6zcv54dZ var]# ps aux | grep redis-server若显示&#xff1a; 则说明服务器上的redis已经启动了&#xff0c;若没有&#xff0c;则请重启一下&#xff1a; sudo systemctl restart redis…

原来,考证还可以领取补贴Money

武汉ZF真的对打工人太好了&#xff0c;只要社保交满 12 个月就可以参加职业技能考试&#xff0c;考试通过就能领 2K 的补贴。 而且证考了对找工作工资也能比别人高几百&#xff0c;真的太爽了&#xff0c;有空的姐妹都去给我考&#xff01;&#xff01;&#xff01; 没空的也给…

思科三层交换机实现EIGIP路由协议6

#路由协议实现# #任务六三层交换机实现EIGIP路由协议6# #1配置计算机的IP地址、子网掩码和网关 #2配置Switch-A的名称及其接口IP地址 Switch(config)#hostname Switch-A Switch-A(config)#ip routing Switch-A(config)#int g0/1 Switch-A(config-if)#no switchport Switc…

docker pull实现断点续传

问题背景 在使用Docker拉取DockerHub的镜像时&#xff0c;经常会出现网络不稳定的问题&#xff0c;这就导致拉取到一半的镜像会重新拉取&#xff0c;浪费时间。例如下面这种情况&#xff1a; 第二次拉取 这是一个网络中断的场景&#xff0c;第二次重新拉取的时候&#xff0c;同…

电子元件-潮湿敏感度MSL等级

目录&#xff1a; 1、什么是MSL 2、MSL测定的流程 3、MSL的分类★ 4、其他 1、什么是MSL MSL&#xff1a;MSL 是 Moisture Sensitivity Level 的缩写&#xff0c;是湿气敏感性等级的意思。 MSL 的提出就是为了给湿度敏感性 SMD 元件的封装提供一种分类标准&#xff0c;从而…

UE虚幻引擎可以云渲染吗?应用趋势与挑战了解

虚幻云渲染技术是基于虚幻引擎的云端渲染技术&#xff0c;将虚幻引擎的渲染计算任务通过云计算的方式进行处理和渲染、并将渲染结果传输到终端设备上进行展示。虚幻引擎云渲染技术在近年来得到了迅猛的发展&#xff0c;并在各个领域得到了广泛的应用&#xff0c;包括游戏、电影…

使用CompletableFuture遇到的一个小坑

在使用CompletableFuture时&#xff0c;发现获取数据时&#xff0c;有时候数据获取不到(值为null)。 代码如下&#xff1a; package com.example.mavendemo.completablefuture;import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j;import java.util.A…

STM32第十二节(中级篇):串口通信(第一节)——功能框图讲解

前言 我们在51单片机中就已经学习过了串口通信的相关知识点&#xff0c;那么我们现在在32单片机上进一步学习通信的原理。我们主要讲解串口功能框图以及串口初始化结构体以及固件库讲解。 STM32第十二节&#xff08;中级篇&#xff09;&#xff1a;串口通信&#xff08;第一节…

免费的泛域名SSL证书如何申请

申请免费泛域名SSL证书的新指南 1. 选择合适的证书颁发机构 首先&#xff0c;寻找一个提供免费泛域名SSL证书的证书颁发机构&#xff08;CA&#xff09;。JoySSL是目前最知名的免费证书提供商之一&#xff0c;它支持泛域名证书&#xff0c;允许您为单个域名及其所有子域名提供…