依赖倒置原则:构建灵活软件架构的基石 - 通过代码实例深入解析

1.引言

1.1为什么要学习依赖倒置原则

在这里插入图片描述

在软件开发过程中,我们经常需要对代码进行修改和扩展。如果代码之间的耦合度过高,那么在进行修改或扩展时,可能会对其他部分的代码产生影响,甚至引发错误。这就要求我们在编写代码时,尽量降低各个模块之间的耦合度,提高代码的可维护性和可扩展性。依赖倒置原则正是为了达到这个目的而提出的。

依赖倒置原则可以帮助我们构建灵活的软件架构,提高代码的可维护性和扩展性。它能够使我们的代码更加模块化,降低模块之间的耦合度,使得每个模块都可以独立地进行修改和扩展,从而提高整个软件的健壮性。

1.2依赖倒置原则在软件开发中的应用

依赖倒置原则在软件开发中有着广泛的应用。它不仅可以用于单个模块的编写,还可以用于整个软件架构的设计。在实际应用中,依赖倒置原则可以帮助我们更好地实现代码的复用,提高开发效率,降低维护成本。

例如,在编写一个网络应用程序时,我们可以使用依赖倒置原则来设计网络请求和响应的处理流程。通过将具体的网络请求和响应处理抽象为接口,然后在具体的实现类中注入这些接口,我们可以使得网络请求和响应的处理逻辑与具体的网络框架和协议相解耦,从而使得代码更加灵活,易于维护和扩展。

在实际开发中,依赖倒置原则的应用可以帮助我们构建出更加健壮、灵活和可维护的软件系统。通过合理地使用依赖倒置原则,我们可以使得代码的结构更加清晰,逻辑更加简洁,从而提高代码的可读性和可维护性。同时,依赖倒置原则还可以帮助我们更好地实现代码的复用,提高开发效率,降低维护成本。

2.依赖倒置原则概念解析

在这里插入图片描述

2.1低耦合与高内聚

耦合度和内聚度是衡量软件模块独立性的两个重要指标。耦合度指的是模块之间相互依赖的程度,而内聚度则指的是模块内部元素之间相关联的程度。依赖倒置原则追求的是低耦合和高内聚,这样可以使得模块更加独立,易于理解和修改。

2.2依赖倒置的定义与目的

依赖倒置原则(Dependency Inversion Principle, DIP)是由Robert C. Martin(又称Uncle Bob)提出的四个面向对象设计原则之一。它要求高层模块和低层模块都依赖于抽象,而不是直接依赖于具体实现。这样做的目的是为了提高模块的抽象层次,降低模块间的耦合度,从而使系统更加灵活和可维护。

2.3依赖倒置原则的三个关键层次

依赖倒置原则涉及三个关键层次:

  1. 具体依赖抽象:高层模块应该依赖于抽象层,而不是具体实现。这意味着高层模块应该只依赖于接口或抽象类,而不是具体的类。

  2. 抽象不依赖具体:抽象层不应该依赖于具体层。这意味着抽象层不应该知道具体层的实现细节,它们之间应该保持独立。

  3. 具体依赖抽象:具体层应该依赖于抽象层。这意味着具体层的实现应该实现抽象层定义的接口或继承抽象层定义的类。

3.依赖倒置原则的实现方式

3.1接口与抽象类

接口和抽象类是实现依赖倒置原则的基础。它们提供了一种契约,规定了具体类应该实现的方法。通过使用接口和抽象类,我们可以确保高层模块和低层模块之间的依赖关系是通过抽象来实现的。

代码实例:

假设我们有一个图形库,我们需要能够绘制不同类型的图形。我们可以创建一个抽象类Shape,它定义了所有图形都应有的方法,比如draw()。然后,我们可以创建具体的图形类,比如CircleRectangle,它们继承自Shape类并实现具体的方法。

// 抽象类
abstract class Shape {public abstract void draw();
}// 具体类
class Circle extends Shape {public void draw() {System.out.println("Drawing a circle");}
}class Rectangle extends Shape {public void draw() {System.out.println("Drawing a rectangle");}
}//高层次模块
class DrawingApplication {public void drawShape(Shape shape) {shape.draw();}
}

在这个例子中,DrawingApplication类不直接依赖于CircleRectangle类,而是依赖于Shape接口。这样,如果我们想要更换图形类型,只需要添加一个新的具体类并实现Shape接口,而不需要修改DrawingApplication类。

3.2依赖注入

依赖注入是实现依赖倒置原则的一种流行方法。它通过构造函数、方法或属性来注入依赖,从而实现了高层模块对低层模块的依赖关系的解耦。

代码实例:

假设我们有一个日志记录器,我们需要在不同的模块中使用它。我们可以创建一个Logger接口,然后创建具体的日志记录器实现,比如FileLoggerDatabaseLogger

// 接口
interface Logger {void log(String message);
}// 具体类
class FileLogger implements Logger {public void log(String message) {System.out.println("File Log: " + message);}
}class DatabaseLogger implements Logger {public void log(String message) {System.out.println("Database Log: " + message);}
}// 高层次模块
class Application {private Logger logger;public Application(Logger logger) {this.logger = logger;}public void run() {logger.log("Application is running");}
}

在这个例子中,Application类通过构造函数注入Logger接口的实现,这样Application类就不直接依赖于具体的日志记录器类,而是依赖于Logger接口。

3.3虚函数与多态

在C++等语言中,虚函数和多态是实现依赖倒置原则的关键特性。通过使用虚函数,我们可以确保基类的方法在派生类中被重写,从而实现多态。

代码实例:

假设我们有一个动物基类Animal,它有一个虚函数makeSound()。然后我们可以创建具体的派生类,比如DogCat

// 基类
class Animal {
public:virtual void makeSound() {cout << "Animal sound" << endl;}
};// 派生类
class Dog : public Animal {
public:void makeSound() override {cout << "Dog bark" << endl;}
};class Cat : public Animal {
public:void makeSound() override {cout << "Cat meow" << endl;}
};// 高层次模块
class Zoo {
public:void exhibitAnimal(Animal& animal) {animal.makeSound();}
};

在这个例子中,Zoo类中的exhibitAnimal方法可以接受任何Animal的派生类对象,并调用相应的makeSound()方法。这样,Zoo类就不依赖于具体的DogCat类,而是依赖于Animal基类,实现了依赖倒置。

4.实战案例分析

4.1案例一:不使用依赖倒置原则

在这个案例中,我们将创建一个简单的购物车系统,但不遵循依赖倒置原则。

代码实例:

// 商品类
class Product {private String name;private double price;public Product(String name, double price) {this.name = name;this.price = price;}public double getPrice() {return price;}
}// 购物车类
class ShoppingCart {private List<Product> products;public ShoppingCart() {products = new ArrayList<>();}public void addProduct(Product product) {products.add(product);}public double calculateTotal() {double total = 0;for (Product product : products) {total += product.getPrice();}return total;}
}// 主类
public class Main {public static void main(String[] args) {ShoppingCart cart = new ShoppingCart();cart.addProduct(new Product("Book", 15.99));cart.addProduct(new Product("Notebook", 2.99));cart.addProduct(new Product("Pen", 1.49));double total = cart.calculateTotal();System.out.println("Total cost: " + total);}
}

在这个例子中,ShoppingCart类直接依赖于Product类,并且直接使用了Product类的具体实现。这种直接依赖具体类的做法导致了ShoppingCart类与Product类之间的耦合度较高,不利于后续的维护和扩展。

4.2案例二:引入依赖倒置原则的改进

为了改进上一个案例,我们可以引入依赖倒置原则,通过使用接口来降低耦合度。

代码实例:

首先,我们创建一个Product接口:

// 商品接口
interface Product {double getPrice();
}

然后,ShoppingCart类不再直接依赖于Product类,而是依赖于Product接口:

// 购物车类(改进后)
class ShoppingCart {private List<Product> products;public ShoppingCart() {products = new ArrayList<>();}public void addProduct(Product product) {products.add(product);}public double calculateTotal() {double total = 0;for (Product product : products) {total += product.getPrice();}return total;}
}

Product类实现Product接口:

// 商品类(改进后)
class Book implements Product {private String name;private double price;public Book(String name, double price) {this.name = name;this.price = price;}public double getPrice() {return price;}
}

主类使用改进后的ShoppingCartBook类:

// 主类
public class Main {public static void main(String[] args) {ShoppingCart cart = new ShoppingCart();cart.addProduct(new Book("Book", 15.99));// ... 可以添加更多商品double total = cart.calculateTotal();System.out.println("Total cost: " + total);}
}

在这个改进的例子中,ShoppingCart类与Product接口之间是通过抽象进行依赖的,这样就降低了它们之间的耦合度。如果我们需要添加新的商品类型,只需要新增一个实现Product接口的类,而不需要修改ShoppingCart类的代码。这样,我们就可以更加灵活地扩展系统,同时保持了代码的可维护性。

5.代码解析与重构

5.1识别耦合点

在软件开发中,耦合点是指模块之间相互依赖的接口。识别耦合点是实现依赖倒置原则的第一步。耦合点可以是方法的调用、属性赋值或事件的监听等。通过分析代码,我们可以发现哪些模块之间存在着直接的依赖关系。

5.2使用接口和抽象类重构

一旦我们识别出了耦合点,就可以通过引入接口和抽象类来重构代码,降低耦合度。

代码实例:

假设我们有一个简单的文本编辑器,其中包含了字体设置和文本显示的功能。

// 直接依赖的具体类
class TextEditor {private Font font;public void setFont(Font font) {this.font = font;}public void displayText(String text) {System.out.println(font.apply(text));}
}class Font {public String apply(String text) {return text;}
}// 重构后的代码
interface TextDisplay {String apply(String text);
}class TextEditor {private TextDisplay display;public void setDisplay(TextDisplay display) {this.display = display;}public void displayText(String text) {System.out.println(display.apply(text));}
}class Font implements TextDisplay {public String apply(String text) {return text;}
}class BoldFont implements TextDisplay {public String apply(String text) {return "<b>" + text + "</b>";}
}// 使用重构后的代码
TextEditor editor = new TextEditor();
editor.setDisplay(new Font());
editor.displayText("Hello, world!");editor.setDisplay(new BoldFont());
editor.displayText("Hello, world!");

在这个例子中,我们通过引入TextDisplay接口,将TextEditor类与Font类之间的直接依赖关系转变为通过TextDisplay接口的依赖关系。这样,我们就可以在不修改TextEditor类的情况下,添加新的显示方式,如粗体显示。

5.3应用依赖注入

依赖注入是另一种降低耦合度的方法。它通过外部容器来提供依赖,而不是在类内部直接创建依赖对象。

代码实例:

假设我们有一个简单的计算器类,它依赖于一个Operator接口来执行运算。

// 接口
interface Operator {double apply(double a, double b);
}// 具体类
class AddOperator implements Operator {public double apply(double a, double b) {return a + b;}
}class SubtractOperator implements Operator {public double apply(double a, double b) {return a - b;}
}// 计算器类
class Calculator {private Operator operator;public Calculator(Operator operator) {this.operator = operator;}public double calculate(double a, double b) {return operator.apply(a, b);}
}// 使用依赖注入
Operator add = new AddOperator();
Calculator calculator = new Calculator(add);
double result = calculator.calculate(10, 5);
System.out.println("Result: " + result);Operator subtract = new SubtractOperator();
calculator.setOperator(subtract);
result = calculator.calculate(10, 5);
System.out.println("Result: " + result);

在这个例子中,Calculator类依赖于Operator接口,而不是具体的运算类。我们通过构造函数注入Operator对象,这样就可以轻松地更换运算方式,而不需要修改Calculator类的代码。

6.依赖倒置原则的优点与挑战

6.1优点分析

依赖倒置原则的优点主要体现在以下几个方面:

  1. 提高代码的可维护性和可扩展性:通过依赖倒置,高层模块和低层模块之间的耦合度降低,使得代码更加模块化,易于理解和修改。

  2. 促进代码的复用:依赖倒置原则鼓励我们使用接口和抽象类,这样可以在不同的上下文中重用相同的接口或抽象类。

  3. 提升系统架构的灵活性:依赖倒置原则使得系统更加灵活,能够更好地适应变化和扩展。

6.2可能面临的挑战与解决方案

依赖倒置原则虽然有很多优点,但在实际应用中也可能会遇到一些挑战。

  1. 接口和抽象类的复杂性:过度使用接口和抽象类可能会导致代码变得复杂和难以理解。为了克服这个问题,我们应该尽量保持接口和抽象类的简洁,避免过度设计。

  2. 依赖注入的侵入性:依赖注入虽然是一种强大的技术,但它可能会对代码的整洁性产生负面影响。为了减少这种侵入性,我们应该尽量使用构造函数注入,而不是字段注入或方法注入。

  3. 测试的复杂性:依赖倒置原则可能会使得单元测试变得更加复杂,因为我们需要为不同的测试场景提供不同的依赖实现。为了简化测试,我们可以使用模拟框架来创建测试所需的依赖。

  4. 学习和理解成本:依赖倒置原则是一种高级的设计原则,需要开发者有一定的设计能力和经验。为了降低学习和理解成本,我们可以通过培训和教育来提高团队的整体技能水平。

7.总结

7.1依赖倒置原则的核心价值

依赖倒置原则是面向对象设计中的一个重要原则,它通过将高层模块和低层模块之间的依赖关系抽象化,从而提高代码的可维护性、可扩展性和灵活性。它的核心价值体现在以下几个方面:

  1. 降低耦合度:通过依赖倒置,我们可以将高层模块和低层模块之间的耦合度降低,使得代码更加模块化,易于理解和修改。

  2. 促进代码复用:依赖倒置鼓励我们使用接口和抽象类,这样可以在不同的上下文中重用相同的接口或抽象类。

  3. 提升系统架构的灵活性:依赖倒置原则使得系统更加灵活,能够更好地适应变化和扩展。

7.2如何将依赖倒置原则融入日常开发

要将依赖倒置原则融入日常开发,我们可以遵循以下几个步骤:

  1. 识别耦合点:在编写代码时,我们应该时刻关注模块之间的依赖关系,识别出耦合点。

  2. 使用接口和抽象类:在设计类时,我们应该尽可能地使用接口和抽象类来定义依赖关系,而不是直接依赖于具体类。

  3. 应用依赖注入:在创建对象时,我们应该使用构造函数、方法或属性注入的方式来提供依赖,而不是在类内部直接创建依赖对象。

  4. 持续重构:在开发过程中,我们应该不断地对代码进行重构,以降低耦合度,提高代码质量。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【rh】rh项目部署

【fastadmin】 1、项目先clone到本地&#xff0c;其中web为h5前端使用(gitclone后&#xff0c;把web内容放进去再提交)&#xff0c;其余为项目后端使用 2、安装本地环境&#xff0c;项目跑起来&#xff0c;步骤如下&#xff1a; 1&#xff09;查春.git 和 composer,json 版本信…

ubuntu22.04不生成core文件

一、检查服务器的core文件是否打开 ulimit -a //查看时候打开core 如果core file size 是0就是关闭的&#xff01;服务一般都是关闭的&#xff01;将他打开即可&#xff01; ulimit -c size//设置core file的大小&#xff0c;自定义 此时打开就可以生成了core文件了&#xff…

工业互联网边缘计算实训室解决方案

一、引言 随着物联网&#xff08;IoT&#xff09;、5G通信技术的快速发展&#xff0c;工业互联网已成为推动制造业转型升级的重要力量。边缘计算作为云计算的延伸和补充&#xff0c;在实时数据分析、降低数据传输延迟、提升处理效率及增强数据安全性方面展现出巨大潜力。在此背…

【安卓】多线程编程

文章目录 线程的简单应用解析异步消息处理机制使用AsyncTask 线程的简单应用 新建一个AndroidThreadTest项目&#xff0c;然后修改activity_main.xml中的代码。 <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width…

Vue 3+Vite+Eectron从入门到实战系列之(六)一工作台界面开发及实现响应式

工作台&#xff0c;是每个后台系统必备的&#xff0c;也是管理系统的首页。这次我们来实现一个工作台&#xff0c;并适配他的响应性 实现效果 代码实现 <template><div class"dashboard"><el-row :gutter"20"><el-col class"mb…

flink车联网项目前篇:建模设计(第65天)

系列文章目录 2.2 维度建模 2.2.1 维度表 2.2.2 事实表 2.2.3 维度建模的三种模型 3. 建模设计 3.1 ODS层 3.2 DWD层 3.3 DWS层 3.4 ADS层 文章目录 系列文章目录前言2.2 维度建模2.2.1 维度表2.2.2 事实表2.2.3 维度建模的三种模型 3. 建模设计3.1 ODS层3.2 DWD层3.3 DWS层3.…

基于ssm+vue+uniapp的停车场小程序的设计与实现

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

sql语句优化(mysql select语句)-索引方式

1、上图是mysql user表的一小部分数据,一共有: 100万条数据 2、假如现在需要查询name列的某一条数据&#xff08;name‘user3’&#xff09; 3、下图是查询优化器评估的最佳方案&#xff0c;索引我删除了还走索引&#xff0c;还他呀的在&#xff0c;奇怪&#xff0c;就先不研究…

Review Learning : 推进一体化超高清图像恢复训练方法

Review Learning: Advancing All-in-One Ultra-High-Definition Image Restoration Training Method 摘要 一体化图像恢复任务变得越来越重要&#xff0c;特别是对于超高清&#xff08;UHD&#xff09;图像。 现有的一体机UHD图像恢复方法通常通过引入针对不同退化类型的即时…

Python,Spire.Doc模块,处理word、docx文件,极致丝滑

Python处理word文件&#xff0c;一般都是推荐的Python-docx&#xff0c;但是只写出一个&#xff0c;一句话的文件&#xff0c;也没有什么样式&#xff0c;就是36K。 再打开word在另存一下&#xff0c;就可以到7-8k&#xff0c;我想一定是python-docx的问题&#xff0c;但一直没…

加和分数、训练、测试

一、加和所有alignment的分数 1、路线图中 2、l_i只与token有关&#xff0c;有一个专门训练的网络&#xff1b;h_i变化只与null有关 3、distribution生成的概率不受路径影响&#xff0c;只要到达位置概率就是一样的 4、计算alignment分数的总和 &#xff08;1&#xff09;αi…

C 语言结构体赋值分析和其优点(使用方式和汇编分析)

1. 例子 #include <stdio.h> #include <stdlib.h> #include <string.h>struct Foo {char a;int b;double c; };int main() {// 定义结构体对象struct Foo foo1;struct Foo foo2;// 初始化为 0memset(&foo1, 0, sizeof(foo1));memset(&foo2, 0, size…

Kubernetes/K8s集群1.23.6搭建

1 集群规划 HostnameIP角色centos702192.168.131.102mastercentos704192.168.131.104nodecentos705192.168.131.105node 2 安装步骤 初始操作和安装基础软件每个节点都要执行。 2.1 初始操作 2.1.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld2.1.…

Science Robotics 受鳞片启发的可编程机器人结构,可同时进行形状变形和刚度变化

一、前言速览 生物有机体通常凭借复杂的结构表现出显著的多功能性&#xff0c;例如章鱼具有可以同时改变形状和刚度的能力。现有的仿生软体机器人要想实现这样的能力&#xff0c;往往需要繁琐的结构和复杂的控制系统。为此&#xff0c;来自新加坡南洋理工大学的研究人员从覆盖…

ARM高性能计算(HPC)处理器Neoverse介绍

思考: Neoverse系列中的N、V、E有什么区别? 这三个字母的缩写又是什么? ARM Neoverse架构是ARM专为服务器、数据中心、高性能计算(HPC)和网络基础设施设计的一系列处理器架构。Neoverse架构分为N系列、V系列和E系列,这些系列面向不同的应用场景,各自有不同的设计目标和…

44 个 React 前端面试问题

1.你知道哪些React hooks&#xff1f; useState&#xff1a;用于管理功能组件中的状态。useEffect&#xff1a;用于在功能组件中执行副作用&#xff0c;例如获取数据或订阅事件。useContext&#xff1a;用于访问功能组件内的 React 上下文的值。useRef&#xff1a;用于创建对跨…

人工智能新纪元

人类社会正式从信息科技时代步入了人工智能时代&#xff0c;相比信息科技革命&#xff0c;人工智能科技革命的影响要深远的多&#xff0c;在这新旧交替剧烈变革期&#xff0c;绝大多数人都有机会。 为了更好的理解人工智能科技革命&#xff0c;首先我们首先梳理一下技术的发展…

全网最最最详细的haproxy详解!!!

1 什么是负载均衡 负载均衡&#xff08;Load Balancing&#xff09;是一种将网络请求或工作负载分散到多个服务器或计算机资源上的技术&#xff0c;以实现优化资源使用、提高系统吞吐量、增强数据冗余和故障容错能力、以及减少响应时间的目的。在分布式系统、云计算环境、Web服…

ArkUI---Swiper、Grid、List组件简单介绍

前言&#xff1a;ForEach ForEach语法如下&#xff1a; ForEach(arr: Array,itemGenerator: (item: Array, index?: number) > void,keyGenerator?: (item: Array, index?: number) : string > string ) 参数1&#xff1a;数据源&#xff0c;为Array的数组 参数2&am…

KOLA: CAREFULLY BENCHMARKING WORLD KNOWLEDGE OF LARGE LANGUAGE MODELS

文章目录 题目摘要简介KOLA 基准实验评估结论和未来工作道德声明 题目 KOLA&#xff1a;仔细对大型语言模型的世界知识进行基准测试 论文地址:https://arxiv.org/abs/2306.09296 项目地址:https://github.com/ranahaani/GNews 摘要 大型语言模型 (LLM) 的卓越性能要求评估方法…