SOLID 设计原则是编写干净代码时需要了解的最重要的设计原则。对于任何程序员来说,牢牢掌握 SOLID 原则是一项不可或缺的技能。它们是开发其他设计模式的基础。 在本文中,我们将使用一些现实生活中的示例来解决 SOLID 设计原则,并了解它们的重要性。 与多态性、抽象和继承一起,SOLID 原则对于擅长面向目标的编程非常重要。 为什么坚实的原则很重要? 坚实的原则之所以重要有多种原因: SOLID 原则使我们能够编写干净且可维护的代码:当我们开始一个新项目时,最初代码质量很好,因为我们实现的功能集有限。然而,随着我们合并更多的功能,代码开始变得混乱。 SOLID 原则建立在抽象、多态性和继承的基础上,并导致常见用例的设计模式。了解这些设计模式有助于在编程中实现常见用例。 SOLID 原则帮助我们编写干净的代码,从而提高代码的可测试性。这是因为代码是模块化且松散耦合的。每个模块都可以独立开发、独立测试。 现在让我们通过实际示例详细探讨每个 SOLID 原则。 1. 单一职责原则 SOLID原则中的S代表单一责任原则。单一职责原则规定,一个类应该只有一个改变的理由。这限制了我们在项目中纳入额外要求时需要更改的地方的数量。 每个类都应该有一个确切的改变理由。 例如,假设我们正在用 Java 设计一个银行应用程序,其中有一个 类,该类允许借记、贷记和 等基本操作。 方法采用名为 的枚举(如电子邮件、SMS 等)并使用适当的介质发送更新。我们将为此编写代码,如下所示。 SavingsAccount sendUpdates sendUpdate NotificationMedium public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void sendNotification(NotificationMedium medium) { if (medium == NotificationMedium.SMS) { // Send SMS here } else if (medium == NotificationMedium.EMAIL) { // Send Email here } } } public enum NotificationMedium { SMS, EMAIL } 现在,如果您查看上面的 类,您会发现它可能由于多种原因而发生更改: SavingsAccount 类的核心逻辑是否有任何变化(如 、 等)。 SavingsAccount debit credit 如果银行决定引入新的通知媒介(比如 WhatsApp)。 这违反了 SOLID 原则中的单一职责原则。为了解决这个问题,我们将创建一个单独的类来发送通知。 我们按照SOLID原则重构上面的代码 public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void printBalance() { System.out.println("Name: " + name+ " Account Balance: " + balance); } public void sendNotification(Medium medium) { Sender.sendNotification(medium, this); } } public enum NotificationMedium { SMS, EMAIL } public class Sender { public static void sendNotification(NotificationMedium medium, SavingsAccount account) { // extract account data from the account object if (medium == NotificationMedium.SMS) { //logic to send SMS here } else if (medium == NotificationMedium.EMAIL) { // logic to send Email here } } } 现在,由于我们已经重构了代码,如果 或格式有任何变化,我们将更改 类。但是,如果 的核心逻辑发生变化, 类也会发生变化。 NotificationMedium Sender SavingsAccount SavingsAccount 这修复了我们在第一个示例中观察到的违规行为。 2. 开闭原理 开放关闭原则指出,我们应该以这样的方式设计类,以便它们对扩展开放(在添加附加功能的情况下),但对修改封闭。关闭修改对我们有两个好处: 很多时候,原始的类源甚至可能不可用。它可能是您的项目消耗的依赖项。 保持原始类不变可以减少出现错误的机会。因为可能还有其他类依赖于我们要修改的类。 要查看开闭原则的示例,让我们看一下购物车的示例(例如在电子商务网站上实现的购物车)。 我将创建一个名为 的类,其中包含您可以添加到其中的 列表。根据商品的类型及其税收,我们希望创建一个方法来计算 类中的购物车总价值。 Cart Item Cart 类应该对扩展开放,对修改关闭 import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart() { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { if (item.getItemType() == GIFT) { // 8% tax on gift + 2% gift wrap cost value += (item.getValue()*1.08) + item.getValue()*0.02 } else if (item.getItemType() == ItemType.ELECTRONIC_ITEM) { value += (item.getValue()*1.11); } else { value += item.getValue()*1.10; } } return value; } } @Getter @Setter public abstract class Item { protected double price; private ItemType itemType; public double getValue() { return price; } } public enum ItemType { ELECTRONIC, GIFT } 在上面的示例中, 方法通过迭代购物车内的所有商品并根据商品类型调用逻辑来计算购物车价值。 calculateCartValue 虽然这段代码看起来是正确的,但它违反了 SOLID 原则。 假设我们需要在计算购物车价值时为不同类型的商品(例如杂货)添加新规则。在这种情况下,我们必须修改原始类 并在其中编写另一个 条件来检查 类型的项目。 Cart else if Grocery 然而,只需很少的重构,我们就可以使代码遵循开闭原则。让我们看看如何。 首先,我们将 类抽象化,并为不同类型的 创建具体类,如下所示。 Item Item public abstract class Item { protected double price; public double getValue() { return price; } } public class ElectronicItem extends Item { public ElectronicItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.11; } } public class GiftItem extends Item { public GiftItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.08 + super.getValue()*0.02; } } public class GroceryItem extends Item { public GroceryItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.03; } } 在每个具体的 Item 类(如 、 和 中实现 方法,该方法包含税收和价值计算的业务逻辑。 GroceryItem GiftItem ElectronicItem getValue() 现在,我们将使 类依赖于抽象类 并为每个项目调用 方法,如下所示。 Cart Item getValue() import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } } 现在,在这段重构的代码中,即使引入了新类型的 , 类也保持不变。由于多态性,无论 中的 的实际类型是什么,都会调用该类的 方法。 Item Cart items Item ArrayList getValue() 3.里氏替换原理 里氏替换原则指出,在给定的代码中,即使我们用子类的对象替换超类的对象,代码也不应该崩溃。换句话说,当子类继承超类并重写其方法时,应该与超类中方法的行为保持一致。 例如,如果我们创建以下类 和两个类 和 类。现在,假设我们在 Vehicle 类中创建一个名为 的方法,它可以在 类中重写,但在 类中将不受支持,因为 没有引擎(请参见下面的代码示例) Vehicle Car Bicycle startEngine() Car Bicycle Bicycle 子类在重写方法时应与超类的行为保持一致。 class Vehicle { public void startEngine() { // start engine of the vehicle } } class Car extends Vehicle { @Override public void startEngine() { // Start Engine } } class Bicycle extends Vehicle { @Override public void startEngine(){ throw new UnsupportedOperationException("Bicycle doesn't have engine"); } } 现在,假设有一些代码需要车辆类型的对象并依赖于 方法。如果在调用该代码段时我们传递 对象而不是传递 类型的对象,则会导致代码中出现问题。由于 (s) 类的方法在调用 方法时会抛出异常。这将违反 SOLID 原则(里氏替换原则) startEngine() Bicycle Vehicle Bicycle startEngine() 为了解决这个问题,我们可以创建两个类 和 ,让 继承自 类,让 继承自 MotorizedVehicle NonMotorizedVehicle Car MotorizedVehicle Bicycle NonMotorizedVehicle class Vehicle { } class MotorizedVehicle extends Vehicle { public void startEngine() { // start engine here } } class Car extends MotorizedVehicle { @Override public void startEngine() { // Start Engine } } class NonMotorizedVehicle extends Vehicle { public void startRiding() { // Start without engine } } class Bicycle extends NonMotorizedVehicle { @Override public void startRiding(){ // Start riding without the engine. } } 4. 接口隔离原则 SOLID 原则中的“I”代表接口隔离原则。 接口隔离原则指出,我们不应该拥有更大的接口来强制实现类来实现未使用的方法,而应该拥有更小的接口并实现类。这样,类仅实现相关方法并保持干净。 将您的接口划分为多个较小的接口,而不是一个大接口。 例如,让我们看一下 Java 中内置的 Collections 框架。在其他数据结构中,Java还提供了 和 数据结构, LinkedList ArrayList 类实现以下接口: 、 、 、 、 和 。 ArrayList Serializable Cloneable Iterable Collection List RandomAccess 类实现 、 、 、 、 、 和 。 LinkedList Serializable Cloneable Iterable Collection Deque List Queue 接口实在是太多了! Java 开发人员可以将 、 、 、 、 和 合并到一个接口中,比如 接口,而不是拥有这么多接口。现在, 和 类都可以实现这个新的 接口。 Serializable Cloneable Iterable Collecton List RandomAccess IList ArrayList LinkedList IList 但是,由于 不支持随机访问,因此它可能实现了 接口中的方法,并且当有人尝试调用它时可能会抛出 。 LinkedList RandomAccess UnsupportedOperationException 然而,这将违反 SOLID 原则中的接口隔离原则,因为它会“强制”LinkedList 类实现 接口内的方法,即使不是必需的。 RandomAccess 因此,最好根据公共行为来拆分接口,让每个类实现多个接口,而不是一个大接口。 依赖倒置原则 依赖倒置原则指出上层的类不应该直接依赖于下层的类。这会导致两个级别之间的紧密耦合。 相反,下层类应该提供上层类所依赖的接口。 依赖接口而不是类 例如,让我们继续上面看到的 示例,并对其进行增强以添加一些付款选项。假设我们有两种付款方式 和 。现在,在 类中,我们要向 添加一个方法,该方法将计算购物车价值并根据提供的付款启动付款。方法。 Cart DebitCard Paypal Cart placeOrder 为此,我们可以通过将两个付款选项添加为 类中的字段,在上面的 示例中添加依赖项。然而,这会将 类与 和 类紧密耦合。 Cart Cart Cart DebitCard Paypal 相反,我们将创建一个 接口,并让 和 类都实现 接口。现在, 类将依赖于 接口,而不是各个支付类型。这使类保持松散耦合。 Payment DebitCard Paypal Payment Cart Payment 请参阅下面的代码。 public interface Payment { void doPayment(double amount); } public class PaypalPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate paypal payment } } public class DebitCardPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate payment via debit card } } import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; private Payment paymentOption; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); this.paymentOption = paymentOption; } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } public void placeOrder() { this.paymentOption.doPayment(calculateCartValue()); } } 如果您有兴趣了解有关面向对象编程、GoF 设计模式和底层设计面试的更多信息,请查看我评价很高的课程 《面向对象编程 + Java 设计模式》