CodeGym /Java 博客 /随机的 /SOLID:Java 类设计的五个基本原则
John Squirrels
第 41 级
San Francisco

SOLID:Java 类设计的五个基本原则

已在 随机的 群组中发布
类是应用程序的构建块。就像建筑物中的砖块一样。写得不好的类最终会导致问题。 SOLID:Java 类设计的五个基本原则 - 1要了解一个类是否编写得当,您可以检查它如何达到“质量标准”。在 Java 中,这些就是所谓的 SOLID 原则,我们将讨论它们。

Java 中的 SOLID 原则

SOLID 是由 OOP 和类设计的前五个原则的大写字母组成的首字母缩写词。这些原则是由 Robert Martin 在 2000 年代初期表达的,然后这个缩写后来由 Michael Feathers 引入。以下是 SOLID 原则:
  1. 单一职责原则
  2. 开闭原则
  3. 里氏替换原则
  4. 接口隔离原则
  5. 依赖倒置原则

单一职责原则 (SRP)

这一原则指出,改变类别的理由永远不应超过一个。 每个对象都有一个职责,完全封装在类中。班级的所有服务都旨在支持这一责任。如果需要,这样的类总是很容易修改,因为很清楚类负责什么,不负责什么。换句话说,我们将能够进行更改而不用担心后果,即对其他对象的影响。此外,这样的代码更容易测试,因为您的测试涵盖了与所有其他功能隔离的一项功能。想象一个处理订单的模块。如果订单形成正确,此模块将其保存在数据库中并发送电子邮件以确认订单:

public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
这个模块可能会因为三个原因而改变。首先,处理订单的逻辑可能会改变。其次,订单的保存方式(数据库类型)可能会发生变化。第三,发送确认的方式可能会改变(例如,假设我们需要发送短信而不是电子邮件)。单一职责原则意味着这个问题的三个方面实际上是三个不同的职责。这意味着它们应该在不同的类或模块中。将可能在不同时间和出于不同原因更改的多个实体组合在一起被认为是糟糕的设计决策。最好将一个模块拆分为三个独立的模块,每个模块执行一个功能:

public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

开闭原则(OCP)

这个原则描述如下:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着应该可以在不更改类的现有代码的情况下更改类的外部行为。根据这个原则,类的设计使得调整类以适应特定条件只需要扩展它并覆盖一些功能。这意味着系统必须灵活,能够在不更改源代码的情况下在不断变化的条件下工作。继续我们涉及订单处理的示例,假设我们需要在处理订单之前以及发送确认电子邮件之后执行一些操作。而不是改变OrderProcessor类本身,我们将扩展它以在不违反开闭原则的情况下实现我们的目标:

public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }
    
    private void beforeProcessing() {
        // Take some action before processing the order
    }
    
    private void afterProcessing() {
        // Take some action after processing the order
    }
}

里氏替换原则 (LSP)

这是我们前面提到的开放封闭原则的变体。它可以定义如下:对象可以被子类的对象替换而不改变程序的属性。这意味着通过扩展基类创建的类必须覆盖它的方法,这样从客户的角度来看,功能就不会受到损害。也就是说,如果开发人员扩展您的类并在应用程序中使用它,他或她不应更改任何重写方法的预期行为。子类必须覆盖基类的方法,以便从客户端的角度来看功能不会被破坏。我们可以在以下示例中详细探讨这一点。假设我们有一个类负责验证订单并检查订单中的所有商品是否有货。isValid()返回truefalse的方法:

public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (!item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
还假设某些订单需要与其他订单不同地进行验证,例如,对于某些订单,我们需要检查订单中的所有商品是否都有库存以及是否所有商品都已包装。为此,我们OrderStockValidator通过创建类来扩展类OrderStockAndPackValidator

public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
但是这里我们违反了 Liskov 替换原则,因为如果订单验证失败,我们的方法 不会返回falseIllegalStateException ,而是抛出一个. 使用此代码的客户并不期望这样:他们期望返回值为truefalse。这可能导致运行时错误。

接口隔离原则(ISP)

这个原则的特点是下面的陈述:客户不应该被强制实施他们不会使用的方法。接口隔离原则意味着必须将太“厚”的接口分成更小、更具体的接口,以便使用小接口的客户只知道他们工作所需的方法。因此,当接口方法改变时,任何不使用该方法的客户端都不应该改变。考虑这个例子:开发人员亚历克斯创建了一个“报告”界面并添加了两个方法:generateExcel()generatedPdf(). 现在有个客户想用这个接口,但是只打算用PDF格式的报表,不打算用Excel。这个功能会让这个客户满意吗?不。客户将必须实施两种方法,其中一种基本上不需要,并且仅由于设计该软件的亚历克斯而存在。客户端将使用不同的界面或不对 Excel 报告的方法进行任何操作。那么解决方案是什么?就是将现有的界面拆分成两个较小的界面。一个用于 PDF 报告,另一个用于 Excel 报告。这让客户只使用他们需要的功能。

依赖倒置原则(DIP)

在Java中,这个SOLID原则是这样描述的:系统内的依赖关系是基于抽象构建的. 高层模块不依赖于底层模块。抽象不应依赖于细节。细节应该依赖于抽象。软件需要设计成各个模块是独立的,并通过抽象相互连接。这一原则的一个经典应用是 Spring 框架。在 Spring Framework 中,所有模块都实现为可以协同工作的独立组件。它们非常自治,可以在除 Spring 框架之外的程序模块中轻松使用。这要归功于封闭和开放原则的依赖。所有模块都只提供对抽象的访问,抽象可以在另一个模块中使用。让我们试着用一个例子来说明这一点。说到单一职责原则,我们考虑了OrderProcessor班级。我们再看一下这个类的代码:

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
在这个例子中,我们的OrderProcessor类依赖于两个特定的类:MySQLOrderRepositoryConfirmationEmailSender。我们还将介绍这些类的代码:

public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
这些类远非我们所说的抽象。从依赖倒置原则的角度来看,最好从创建一些我们将来可以使用的抽象开始,而不是具体的实现。让我们创建两个接口:MailSenderOrderRepository)。这些将是我们的抽象:

public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
现在我们在已经为此准备好的类中实现这些接口:

public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}
我们做了准备工作,以便我们的OrderProcessor课程不依赖于具体细节,而是依赖于抽象。我们将通过将依赖项添加到类构造函数来更改它:

public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
现在我们的类依赖于抽象,而不是具体的实现。OrderProcessor我们可以通过在创建对象时添加所需的依赖项来轻松更改其行为。我们已经研究了 Java 中的 SOLID 设计原则。在 CodeGym 课程中,您将学到更多关于 OOP 的一般知识和 Java 编程的基础知识——没有什么枯燥乏味和数百小时的练习。是时候解决一些任务了:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION