包工头接活网站app,和易企秀类似的软件免费的,哈尔滨网站快速排名,oa系统手机端个人主页#xff1a;金鳞踏雨 个人简介#xff1a;大家好#xff0c;我是金鳞#xff0c;一个初出茅庐的Java小白 目前状况#xff1a;22届普通本科毕业生#xff0c;几经波折了#xff0c;现在任职于一家国内大型知名日化公司#xff0c;从事Java开发工作 我的博客金鳞踏雨 个人简介大家好我是金鳞一个初出茅庐的Java小白 目前状况22届普通本科毕业生几经波折了现在任职于一家国内大型知名日化公司从事Java开发工作 我的博客这里是CSDN是我学习技术总结知识的地方。希望和各位大佬交流共同进步 ~ 要依赖于抽象而不是具体实现。
依赖倒置的目的是低层模块可以随时替换以提高代码的可扩展性。
一、原理
要依赖于抽象而不是具体实现。遵循这个原则可以使系统的设计更加灵活、可扩展和可维护。
高层模块不应该依赖于低层模块它们都应该依赖于抽象。抽象不应该依赖于具体实现具体实现应该依赖于抽象。
倒置在这里的确是指反过来的意思。在依赖倒置原则中我们需要改变依赖关系的方向使得高层模块和低层模块都依赖于抽象而不是高层模块直接依赖于低层模块。这样一来依赖关系就从直接依赖具体实现反过来依赖抽象了。
这种倒置的依赖关系使得系统的耦合度降低提高了系统的可维护性和可扩展性。因为当低层模块的具体实现发生变化时只要不改变抽象高层模块就不需要进行调整。所以这个原则叫做依赖倒置原则。
二、如何理解抽象
当我们在讨论依赖倒置原则中的抽象时绝对不能仅仅把他理解为一个接口。抽象的目的是将关注点从具体实现转移到概念和行为使得我们在设计和编写代码时能够更加关注问题的本质。通过使用抽象我们可以创建更加灵活、可扩展和可维护的系统。
事实上抽象是一个很广泛的概念它可以包括接口、抽象类以及由大量接口抽象类和实现组成的更高层次的模块。通过将系统分解为更小的、可复用的组件我们可以实现更高层次的抽象。这些组件可以独立地进行替换和扩展从而使整个系统更加灵活。
1. 接口
接口是 Java 中实现抽象的一种常见方式。接口定义了一组方法签名表示实现该接口的类应具备哪些行为。接口本身并不包含具体实现所以它强调了行为的抽象。
假设我们正在开发一个在线购物系统其中有一个订单处理模块。订单处理模块需要与不同的支付服务提供商如 PayPal、Stripe 等进行交互。如果我们直接依赖于支付服务提供商的具体实现那么在更换支付服务提供商或添加新的支付服务提供商时我们可能需要对订单处理模块进行大量修改。为了避免这种情况我们应该依赖于接口而不是具体实现。
首先我们定义一个支付服务接口
public interface PaymentService {boolean processPayment(Order order);
}
然后为每个支付服务提供商实现该接口
public class PayPalPaymentService implements PaymentService {Overridepublic boolean processPayment(Order order) {// 实现 PayPal 支付逻辑}
}public class StripePaymentService implements PaymentService {Overridepublic boolean processPayment(Order order) {// 实现 Stripe 支付逻辑}
}
现在我们可以在订单处理模块中依赖 PaymentService 接口而不是具体的实现
public class OrderProcessor {private PaymentService paymentService;public OrderProcessor(PaymentService paymentService) {this.paymentService paymentService;}public void processOrder(Order order) {// 其他订单处理逻辑...boolean paymentResult paymentService.processPayment(order);// 根据 paymentResult 处理支付结果}
}
通过这种方式当我们需要更换支付服务提供商或添加新的支付服务提供商时只需要提供一个新的实现类而不需要修改 OrderProcessor 类。我们可以在运行时通过构造函数注入不同的支付服务实现使得系统更加灵活和可扩展。
2. 抽象类
抽象类是另一种实现抽象的方式。与接口类似抽象类也可以定义抽象方法表示子类应该具备哪些行为。不过抽象类还可以包含部分具体实现这使得它们比接口更加灵活。
abstract class Shape {abstract double getArea();void displayArea() {System.out.println(面积为: getArea());}
}class Circle extends Shape {private final double radius;Circle(double radius) {this.radius radius;}Overridedouble getArea() {return Math.PI * Math.pow(radius, 2);}
}class Square extends Shape {private final double side;Square(double side) {this.side side;}Overridedouble getArea() {return Math.pow(side, 2);}
}
在这个示例中我们定义了一个抽象类 Shape它具有一个抽象方法 getArea用于计算形状的面积。同时它还包含了一个具体方法 displayArea用于打印面积。
Circle 和 Square 类继承了 Shape分别实现了 getArea 方法。在其他类中我们可以依赖抽象Shape而非 Square和Circle。
三、如何理解高层模块和底层模块
所谓高层模块和低层模块的划分简单来说就是在调用链上调用者属于高层被调用者属于低层。在平时的业务代码开发中高层模块依赖底层模块是没有任何问题的。实际上这条原则主要还是用来指导框架层面的设计跟前面讲到的控制反转类似。
用 Tomcat 这个 Servlet 容器作为例子来解释一下。从业务代码上讲举一个简单的例子就是controller要依赖service的接口而不是实现service实现要依赖dao层的接口而不是实现调用者要依赖被调用者的接口而不是实现。
以一个简单的音频播放器为例高层模块 AudioPlayer 负责播放音频而音频文件的解码由低层模块 Decoder 实现。为了遵循依赖倒置原则我们可以引入一个抽象的解码器接口
interface AudioDecoder {AudioData decode(String filePath);
}class AudioPlayer {private final AudioDecoder decoder;public AudioPlayer(AudioDecoder decoder) {this.decoder decoder;}public void play(String filePath) {AudioData audioData decoder.decode(filePath);// 使用解码后的音频数据进行播放}
}class MP3Decoder implements AudioDecoder {Overridepublic AudioData decode(String filePath) {// 实现 MP3 文件解码}
}
在这个例子中我们将高层模块 AudioPlayer 和低层模块 MP3Decoder 解耦使它们都依赖于抽象接口 AudioDecoder。这样我们可以根据需要轻松地更换音频解码器例如支持不同的音频格式而不影响音频播放器的逻辑。为了支持新的音频格式我们只需要实现新的解码器类并将其传递给 AudioPlayer。
假设我们现在要支持 WAV 格式的音频文件我们可以创建一个实现 AudioDecoder 接口的新类
class WAVDecoder implements AudioDecoder {Overridepublic AudioData decode(String filePath) {// 实现 WAV 文件解码}
}
然后在创建 AudioPlayer 对象时我们可以根据需要选择使用 MP3Decoder 或 WAVDecoder
public static void main(String[] args) {AudioDecoder mp3Decoder new MP3Decoder();AudioPlayer mp3Player new AudioPlayer(mp3Decoder);mp3Player.play(example.mp3);AudioDecoder wavDecoder new WAVDecoder();AudioPlayer wavPlayer new AudioPlayer(wavDecoder);wavPlayer.play(example.wav);
}
通过遵循依赖倒置原则我们将高层模块 AudioPlayer 与低层模块 MP3Decoder 和 WAVDecoder 解耦使它们都依赖于抽象接口 AudioDecoder。这样的设计使得我们可以轻松地为音频播放器添加新的音频格式支持同时保持整个系统的灵活性和可维护性。
Tomcat
Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下便可以被 Tomcat 容器调用执行。
按照之前的划分原则Tomcat 就是高层模块我们编写的 Web 应用程序代码就是低层模块。
Tomcat 和应用程序代码之间并没有直接的依赖关系两者都依赖同一个“抽象”也就是 Sevlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节而 Tomcat 容器和应用程序依赖 Servlet 规范。这样做的好处就是tomcat中可以运行任何实现了servlet规范的应用程序同时我们编写的servlet实现web工程也可以运行在不同的web服务器中。 四、IOC容器
依赖倒置的目的是低层模块可以随时替换以提高代码的可扩展性。
其实我们学过spring的同学应该都清楚在spring中实现这个很简单的我们只需要向容器中注入特定的bean就能切换具体实现。同时我们在编写日常代码时有意无意的都会遵循设计原则。
控制反转是一种软件设计原则它将传统的控制流程颠倒过来将控制权交给一个中心化的容器或框架。
依赖注入是指不通过 new() 的方式在类内部创建依赖类对象而是将依赖的类对象在外部创建好之后通过构造函数、函数参数等方式传递或注入给类使用。
通过控制翻转和依赖注入结合我们只要保证依赖抽象而不是实现就能很轻松的替换实现。如给容器注入一个MySQL的数据则所有依赖数据源的部分会自动使用MySQL如果想替换数据源则仅仅需要给容器注入一个新的数据源就好了不需要修改一行代码。 文章到这里就结束了如果有什么疑问的地方可以在评论区指出~ 希望能和大佬们一起努力诸君顶峰相见 再次感谢各位小伙伴儿们的支持