您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java编程中必知必会的5条SOLID原则

时间:2023-08-05 17:01:01  来源:微信公众号  作者: Java学研大本营

简介

在面向对象编程(OOP)领域,SOLID原则是类设计的指导准则。这五个原则形成了一套规则和最佳实践,开发人员在设计类结构时应遵循这些原则。通过理解和应用这些原则,我们可以发挥出设计模式的潜力,创建强大的软件架构

在本文中,我们介绍SOLID原则的核心内容,并帮助您理解这些原则如何应用到项目中。

图片

核心目标:编写可理解、无回归和可测试的代码

SOLID原则的核心目标是构建可理解、无回归、可读和可测试的代码。这些代码作为一个画布,多个开发人员可以轻松地进行协作,促进生产力和创新的环境。

现在,我们逐个深入讨论SOLID原则,揭示它们在塑造类设计中的重要性。

  • S — 单一责任原则
  • O — 开放封闭原则
  • L — 里氏替换原则
  • I — 接口隔离原则
  • D — 依赖反转原则

1 单一责任原则

SOLID的第一个支柱,单一责任原则(SRP),强调一个类应该只有一个变化的原因。遵循这个原则,我们确保每个类只负责一个任务,可使将来的维护和修改更容易。

/*手机类,具有属性和构造函数*/
Class MobilePhone{
    String brandName;
    Float price;
    Date manufactureDate;
  public MobilePhone(String brandName,Float price,Date manufactureDate){
      this.brandName=brandName;
      this.price=price;
      this.manufactureDate=manufactureDate;
  }
};

/*Invoice类拥有一个手机(mobile phone)和该手机的数量(quantity)*/
Class Invoice{
    private MobilePhone mPhone;
    int quantity;
  public Invoice(MobilePhone mPhone,quantity){
    this.mPhone=mPhone;
    this.quantity=quantity;
  }
  public float calculateTotalPrice(){
    return mPhone.price*this.quantity;//返回发票的总金额
  }
  public void printInvoice(){
    //打印发票的逻辑
  }
  public void sendNotification(){
    //发送通知的逻辑
  }

}

以上代码,与计算、打印或通知逻辑相关的任何更改都需要修改Invoice类。因此,发票类缺乏明确的关注点分离,对一个方面的修改会影响到其他功能。为了遵循SRP,关键是将Invoice类重构为更小、更专注的类, 每个类都独立处理特定的职责,比如计算、打印或通知逻辑。

根据职责将代码分离成独立的类是遵循单一责任原则(SRP)并促进可维护和灵活的代码库的正确方法。

在这种设计中,我们应该有:

  • Invoice类只包含计算逻辑。
  • InvoicePrint类只包含打印逻辑。
  • InvoiceNotify类只包含发送通知的逻辑。
/*手机类,具有属性和构造函数*/
Class MobilePhone{
    String brandName;
    Float price;
    Date manufactureDate;
  public MobilePhone(String brandName,Float price,Date manufactureDate){
      this.brandName=brandName;
      this.price=price;
      this.manufactureDate=manufactureDate;
  }
};

/*发票类,拥有手机和数量属性*/
Class Invoice{
    private MobilePhone mPhone;
    int quantity;
  public Invoice(MobilePhone mPhone,quantity){
    this.mPhone=mPhone;
    this.quantity=quantity;
  }
  public float calculateTotalPrice(){
    return mPhone.price*this.quantity;//返回发票的总金额
  }
}

Class InvoicePrint{
  private Invoice invoice;
  
  public InvoicePrint(Invoice invoice){
  this.invoice=invoice
  }
  public void printInvoice(){
    //打印发票的逻辑
  }
}/*如果打印逻辑发生变化,只有InvoicePrint类会发生变化。*/
Class InvoiceNotify{
  private Invoice invoice;
  
  public InvoiceNotify(Invoice invoice){
  this.invoice=invoice
  public void sendNotification(){
    //发送通知给用户的逻辑
  }
}/*如果通知逻辑发生变化,只有InvoiceNotify类会发生变化。*/

2 开放-封闭原则

第二个原则是开放-封闭原则(OCP),它鼓励软件实体对扩展开放,但对修改封闭。换句话说,一旦一个类被建立,它应该能够轻松地进行扩展,而不需要修改其现有的代码。这促进了代码的重用和稳定性。

让我们以上面使用的InvoiceNotify类为例,InvoiceNotify类经过测试,并且当前在客户端中实际使用,通过电子邮件发送发票通知。

现在有一个客户需求,他们需要通过推送通知发送通知。

Class InvoiceNotify{
  private Invoice invoice;
  
  public InvoiceNotify(Invoice invoice){
  this.invoice=invoice
  public void sendNotification(){
    //发送通知给用户的逻辑
  }
  public void sendPushNotification(){
    //发送推送通知给用户的逻辑
  }
}

以上代码,通过在现有类中添加一个新方法,我们违反了开放/封闭原则

与其在现有类中添加一个新方法,我们应该设计一个接口并在各个类中实现。

Interface InvoiceNotification{
 public void sendNotification();
}
Class EmAIlNotification implements InvoiceNotification{
 private Invoice invoice;
  public EmailNotification(Invoice invoice){
     this.invoice=invoice;
  }
  @Override
  public void sendNotification(){
  //通过电子邮件发送通知的逻辑
  }
}
Class PushNotification implements InvoiceNotification{
 private Invoice invoice;
  public PushNotification(Invoice invoice){
     this.invoice=invoice;
  }
  @Override
  public void sendNotification(){
  //发送推送通知的逻辑
  }
}

如果进一步增强需求,需要通过短信发送通知,无需修改现有类。相反,我们可以创建一个名为TextNotification的新类,它实现了InvoiceNotification接口并重写了sendNotification()方法。这样,我们就能够顺利地集成新功能,而不会破坏现有的代码库。

3 里氏替换原则

里氏替换原则(LSP)定义了基类和派生类之间的契约。它规定派生类应该能够替代其基类,而不会影响程序的正确性。实质上,遵循这个原则可以确保继承被谨慎地使用,并保持类层次结构的完整性。

例如:在数学中,正方形可以被归类为矩形的一种特殊形式。它们之间的“是一个”关系可能会导致我们考虑在代码中使用继承来建模这种关系。然而,将正方形实现为矩形的派生类可能会导致意外的行为。

在数学中,正方形确实是矩形的一种特殊形式,正如“是一个”关系所暗示的那样。这往往会引诱我们在代码中使用继承来建模这种关系。然而,将正方形实现为矩形的派生类可能会导致意想不到和违反直觉的行为。

我们用一个简单的JAVA代码示例来说明这个问题:

class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int calculateArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 正方形的边长始终相等,所以两个维度都设置为相同的值。
    }

    @Override
    public void setHeight(int height) {
        this.height = height;
        this.width = height; // 正方形的边长始终相等,所以两个维度都设置为相同的值。
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Square();
        rectangle.setWidth(5);
        rectangle.setHeight(3);

        System.out.println("Area: " + rectangle.calculateArea());
    }
}

在这个例子中,我们有一个基类Rectangle,其中包含setWidth和setHeight方法,分别用于设置矩形的宽度和高度。Square类继承Rectangle类,并重写这些方法,以确保两个维度保持相等,以保持正方形的特性。

在主方法中,我们创建一个Rectangle引用,指向一个Square对象。当我们尝试为宽度和高度设置不同的值(分别为5和3)时,我们得到了一个边长为3的正方形,而不是实际宽度为5、高度为3的矩形。因此,计算得到的面积(9)与我们期望从宽度为5、高度为3的矩形得到的面积不符。

这个场景展示了里氏替换原则被违反的情况,通过Rectangle引用使用Square对象导致了意外的行为。

为了解决Square继承Rectangle的问题,我们需要重新评估继承关系和类设计。一种方法是在这种情况下避免使用继承,而是专注于公共接口或组合。我们用Java代码来说明解决方案:

interface Shape {
    int calculateArea();
}

class Rectangle implements Shape {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public int calculateArea() {
        return width * height;
    }
}

class Square implements Shape {
    protected int side;

    public void setSide(int side) {
        this.side = side;
    }

    @Override
    public int calculateArea() {
        return side * side;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape rectangle = new Rectangle();
        rectangle.setWidth(5);
        rectangle.setHeight(3);
        System.out.println("矩形面积: " + rectangle.calculateArea());

        Shape square = new Square();
        square.setSide(5);
        System.out.println("正方形面积: " + square.calculateArea());
    }
}

在这个解决方案中,我们引入了一个名为Shape的公共接口,定义了calculateArea()方法。现在,Rectangle和Square都实现了这个接口。Rectangle类保留了setWidth和setHeight方法,而Square类有一个setSide方法。每个类根据自己特定的属性计算面积。

现在,在main方法中,我们为Rectangle和Square对象分别创建了不同的Shape引用。我们可以适当设置尺寸而不会遇到任何问题。

通过使用组合和共同接口,我们确保每个形状都能独立运作,并且按预期运行,而不违反里氏替换原则。这种设计使我们能够优雅地处理不同的形状,促进了更清晰和可维护的代码库。

4 接口隔离原则

接口隔离原则(ISP)建议客户端不应被强迫依赖于它们不使用的接口。与其拥有庞大而笨重的接口,更好的做法是创建小而专注的接口,以满足客户端的特定需求。

让我们通过一个简单的Java代码示例来说明ISP:

假设我们有一个名为Printer的接口,提供打印功能:

interface DocumentProcessor {
    void print();
    void fax();
}

class LaserPrinter implements DocumentProcessor {
    @Override
    public void print() {
        System.out.println("Printing with a laser printer.");
    }

    @Override
    public void fax() {
        System.out.println("Sending a fax with a laser printer.");
    }
}

class Faxmachine implements DocumentProcessor {
    @Override
    public void print() {
        // 传真机无法打印,所以将这个方法保持为空。
    }

    @Override
    public void fax() {
        System.out.println("Sending a fax with a fax machine.");
    }
}

这个设计的问题在于FaxMachine类对于print()方法没有有意义的实现,因为传真机无法打印文档。尽管如此,FaxMachine类仍然被强制实现print()方法,这是因为DocumentProcessor接口的设计。

这种对接口隔离原则的违反显而易见,因为FaxMachine类现在需要实现它不需要或使用的方法。

5 依赖反转原则

SOLID原则的最后一块拼图是依赖反转原则(Dependency Inversion Principle,DIP)。该原则主张高层模块不应依赖于低层模块,而应依赖于抽象。通过遵循这一原则,我们实现了解耦,从而增强了灵活性、可维护性和测试的便捷性。

让我们通过一个小的Java代码示例来说明违反依赖反转原则的情况:

假设我们有一个ReportGenerator类,它直接依赖于一个DatabaseConnection类:

class DatabaseConnection {
    public void connect() {
        System.out.println("Connected to the database.");
    }

    public void executeQuery(String query) {
        System.out.println("Executing query: " + query);
    }

    public void close() {
        System.out.println("Connection closed.");
    }
}

class ReportGenerator {
    private DatabaseConnection databaseConnection;

    public ReportGenerator() {
        this.databaseConnection = new DatabaseConnection();
    }

    public void generateReport() {
        databaseConnection.connect();
        databaseConnection.executeQuery("SELECT * FROM data_table");
        databaseConnection.close();
        System.out.println("Report generated successfully.");
    }
}

在这段代码中,ReportGenerator类在其构造函数中直接创建了一个DatabaseConnection实例。结果,ReportGenerator与DatabaseConnection紧密耦合。对DatabaseConnection类的任何更改都可能会影响到ReportGenerator。

为了解决这个问题,我们需要应用依赖反转原则,引入一个两个类都依赖的接口:

interface Connection {
    void connect();
    void executeQuery(String query);
    void close();
}

class DatabaseConnection implements Connection {
    @Override
    public void connect() {
        System.out.println("Connected to the database.");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing query: " + query);
    }

    @Override
    public void close() {
        System.out.println("Connection closed.");
    }
}

class ReportGenerator {
    private Connection connection;

    public ReportGenerator(Connection connection) {
        this.connection = connection;
    }

    public void generateReport() {
        connection.connect();
        connection.executeQuery("SELECT * FROM data_table");
        connection.close();
        System.out.println("Report generated successfully.");
    }
}

public class Main {
    public static void main(String[] args) {
        Connection databaseConnection = new DatabaseConnection();
        ReportGenerator reportGenerator = new ReportGenerator(databaseConnection);

        reportGenerator.generateReport();
    }
}

 

通过遵循依赖反转原则,我们通过Connection接口解耦了ReportGenerator和DatabaseConnection类。这种方法允许我们在不修改ReportGenerator的情况下轻松切换和扩展Connection接口的实现。现在的代码符合原则,更易于维护和灵活。

结论

SOLID原则是面向对象类设计的基石,对于每个寻求创建高效、可维护和协作的软件的开发人员来说至关重要。当你踏上编码之旅时,请记住SOLID运用原则!



Tags:SOLID   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java编程中必知必会的5条SOLID原则
简介在面向对象编程(OOP)领域,SOLID原则是类设计的指导准则。这五个原则形成了一套规则和最佳实践,开发人员在设计类结构时应遵循这些原则。通过理解和应用这些原则,我们可以发挥...【详细内容】
2023-08-05  Search: SOLID  点击:(283)  评论:(0)  加入收藏
Youtuber实测成功,ChatGPT+SOLIDWORKS帮你自动画图!
国外Youtuber实测,运用ChatGPT指挥SOLIDWORKS绘图成功,他要求ChatGPT编写SOLIDWORKS的Macro(宏),让SOLIDWORKS读取后,成功画出想要的图样。而这是自今年年初达索系统SOLIDWORKS在...【详细内容】
2023-04-28  Search: SOLID  点击:(270)  评论:(0)  加入收藏
再谈Java中的SOLID编程原则
在软件开发领域,创建易于理解、更改和重用的代码至关重要。随着软件系统变得越来越复杂,遵循既定原则和设计模式以确保代码可靠和可维护变得更加重要。这就是 SOLID 原则的用...【详细内容】
2023-04-19  Search: SOLID  点击:(83)  评论:(0)  加入收藏
如何使用 Solidity 构建 CRUD 应用程序
你有没有想过如何创建你的区块链应用程序?谈到以太坊,它从智能合约[1]开始。在本文中,我们将学习如何在以太坊上构建一个简单的智能合约,并使用 Truffle 框架对其进行测试。我们...【详细内容】
2022-09-24  Search: SOLID  点击:(296)  评论:(0)  加入收藏
面向对象的SOLID五大原则
前序做C语言开发的应该都知道,C是面向过程开发的,而c++是面向对象开发的。而封装、继承与多态是面向对象开发的三大特征。但你可能不知道OOD(Object-Oriented Design)还有五大...【详细内容】
2022-07-29  Search: SOLID  点击:(434)  评论:(0)  加入收藏
想入门区块链?最强solidity学习攻略来了
在区块链世界中,智能合约是不可缺少的一部分。而作为一种真正意义上运行在去中心化网络上的合约,Solidity在智能合约的编写中占据了非常大的份额,学习solidity属于入门区块链的...【详细内容】
2022-07-14  Search: SOLID  点击:(353)  评论:(0)  加入收藏
SolidWorks装配体中Toolbox标准件怎么变中文名字
建模步骤1.【上视基准面】画一个中心矩形。 2.【拉伸凸台】给定深度:10 。 2-1.【圆角】半径:10 。 3.【装配体】插入立方体,添加配合:轮廓中心。 4.【装配体特征】-【异型孔...【详细内容】
2022-06-14  Search: SOLID  点击:(592)  评论:(0)  加入收藏
深入设计原则-SOLID
介绍SOLID是什么,它是如何帮助我们写更好的代码的?SOLID原则由以下5个概念组成: Single Responsibility(单一职责) Open/Closed(开闭) Liskov Substitution(里氏替换) Interface Segr...【详细内容】
2021-03-08  Search: SOLID  点击:(410)  评论:(0)  加入收藏
微服务设计的原则:IDEALS,而不是SOLID
微服务架构作者:DevOps亮哥来自:DevOps探路者一、关键点:对于面向对象的设计,我们遵循SOLID原则。对于微服务设计,我们建议开发人员遵循IDEALS原则:接口分离(Interface segregation...【详细内容】
2020-09-11  Search: SOLID  点击:(380)  评论:(0)  加入收藏
SOLID原则-简化插图
S.O.L.I.D原则的重要性> S.O.L.I.D principles 介绍作为开发人员,我们一直在处理遗留代码库。 大多数传统代码库具有紧密耦合的类,冗余代码和较少的测试范围。 快速浏览代码时...【详细内容】
2020-08-02  Search: SOLID  点击:(496)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(21)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(24)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(56)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(88)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(105)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(95)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(74)  评论:(0)  加入收藏
站内最新
站内热门
站内头条