设计原则SOLID看这一篇就够了

文章目录


在这里插入图片描述
图片来源 《SOLID Principles — Concise and brief explanation》一文 https://medium.com/front-end-weekly/solid-principles-concise-and-brief-explanation-96790dc63b63
代码示例借鉴 《SOLID Principles Java》https://www.javatpoint.com/solid-principles-java

1.引言

1.1. 背景

有同学反馈最近面试被问到solid 原则。我在想什么样niubility的公司,面试已经卷到了问里氏替换原则(LSP)和依赖倒置原则(DIP)这种设计思想层面的问题了。不管咋样既然面试被问到了,说明它还是有很大的存在价值的,所以趁着周末查阅了大量文章和电子书整理了一下,从四个方面包含定义和原理、好处与目标、实现思路、以及局限性和注意事项。详细讲解一下每种原则的情况。为了方便理解我整理了一个思维导图
在这里插入图片描述

1.2. 简要介绍 SOLID 原则

SOLID 是一组面向对象编程和设计的原则,它们旨在引导开发者构建高质量的、可扩展的和可维护的软件。SOLID 原则包括以下五个部分:

  1. 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一个职责,这样可以提高类的内聚性。
  2. 开闭原则(Open/Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在添加新功能时,应尽量不修改现有代码,而是通过扩展的方式实现。
  3. 里氏替换原则(Liskov Substitution Principle, LSP):子类应该能够替换其基类,并且在替换后的代码中不会出现错误。这意味着子类应该遵循基类的行为规范,以确保在使用继承时保持代码的正确性。
  4. 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该被迫依赖于它不使用的接口。这意味着应该将大型接口拆分为多个小型、专用接口,以减少不必要的依赖关系。
  5. 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这意味着在设计软件时,应该优先考虑抽象类和接口,而不是具体实现。

1.1. 面向对象编程和设计的重要性

面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它将软件系统组织为相互交互的对象,每个对象都包含数据和对这些数据进行操作的方法。面向对象编程的主要目标是提高代码的可维护性、可扩展性和重用性。

面向对象设计(Object-Oriented Design, OOD)是在软件开发过程中定义和组织对象及其相互关系的过程。良好的面向对象设计可以帮助开发人员更好地理解问题领域,减少代码的复杂性,提高系统的可靠性和可维护性。

2. 单一职责原则(SRP)

2.1. 定义和原理

单一职责原则(Single Responsibility Principle,SRP)是SOLID原则中的一项,它指出一个类应该只有一个引起它变化的原因。换句话说,一个类应该只负责一个职责或关注点。

SRP的原理是将一个类的功能划分为若干个职责,每个职责都应该由一个单独的类来承担。这样做的好处是使类的职责更加清晰明确,提高了代码的可读性、可维护性和可测试性。

2.2. SRP 的好处与目标

SRP的使用有以下好处和目标:

  • 高内聚性:单一职责原则使得类的功能集中于一个特定的职责,从而提高了类的内聚性。每个类都应该专注于完成自己的职责,这样代码更容易理解和维护。

  • 低耦合性:通过将不同的职责分离到不同的类中,可以降低类之间的耦合性。当一个职责发生变化时,只需修改与之相关的类,而不会对其他类产生影响。

  • 可测试性:每个类只负责一个职责,使得单元测试更加容易编写。测试可以针对单个职责进行,从而提高测试的精确性和可靠性。

  • 易于扩展和重用:当需要添加新的职责时,可以创建新的类来承担该职责,而不必修改已有的类。这使得系统更加灵活,支持易于扩展和重用的设计。

2.3. 例子和代码展示

下面是一个简单的例子来说明单一职责原则:

class FileManager:
    def read_file(self, file_path):
        # 读取文件逻辑

    def write_file(self, file_path, data):
        # 写入文件逻辑

    def process_data(self, data):
        # 处理数据逻辑

    def generate_report(self, report_data):
        # 生成报告逻辑

在上述代码中,FileManager 类同时承担了文件的读写操作和数据处理、报告生成等功能。这违反了SRP原则,因为它包含了多个不同的职责。

为了遵循SRP原则,可以将不同的职责分离到不同的类中:

class FileReader:
    def read_file(self, file_path):
        # 读取文件逻辑

class FileWriter:
    def write_file(self, file_path, data):
        # 写入文件逻辑

class DataProcessor:
    def process_data(self, data):
        # 处理数据逻辑

class ReportGenerator:
    def generate_report(self, report_data):
        # 生成报告逻辑

通过将不同的职责分离到不同的类中,每个类都负责一个特定的职责,使得代码更加清晰、可维护和可扩展。

2.4. 如何识别和解决 SRP 原则的违反

以下是一些识别和解决SRP原则违反的方法:

  • 职责不明确:当一个类承担了多个职责时,可以通过审查类的方法和属性来判断违反了SRP原则。如果一个类具有多个不相关的方法或属性,可能意味着它承担了多个职责,需要进行重构。

  • 类的大小和复杂性:如果一个类变得过大和复杂,很可能是因为它承担了过多的职责。可以通过将不同的职责分离到不同的类中,将大类拆分为更小的类,从而解决SRP违反的问题。

  • 高耦合性:如果修改一个职责导致其他职责的代码也需要修改,说明类之间存在高耦合性,违反了SRP原则。可以通过将不同职责的代码分离到独立的类中,降低耦合性。

  • 单一职责的测试:如果编写测试时需要关注多个不相关的功能,可能是因为类承担了多个职责。可以通过将职责拆分到不同的类中,使得单元测试更加简单和专注。

  • 违反开放封闭原则:如果为了添加新的功能而修改一个类,可能是因为它承担了多个职责。可以通过将新功能的代码放入一个新的类中,遵循开放封闭原则。

解决SRP原则违反的方法是将不同的职责分离到不同的类中。每个类应该专注于一个职责,这样可以提高代码的可读性、可维护性和可测试性。

2.5. 注意事项和局限性

尽管SRP原则有很多优点,但在实践中也需要注意以下事项和局限性:

  • 抽象的平衡:将类的职责划分得过于细化可能会导致类过多,增加了代码的复杂性。需要在职责划分时找到合适的抽象级别,以确保代码的可理解性和维护性。

  • 上下文相关性:有时候,类的职责可能在特定的上下文中是相关的。在这种情况下,严格遵循SRP原则可能会导致过多的类和间接性的调用。应该根据具体情况进行权衡和设计。

  • 实践和经验:理解和应用SRP原则需要实践和经验。通过实际的开发项目和代码复审,开发人员可以逐渐熟悉并掌握如何划分职责和设计高内聚、低耦合的类。

综上所述,单一职责原则(SRP)是一项重要的设计原则,它鼓励将类的功能划分为单一的职责。遵循SRP可以提高代码的可读性、可维护性和可测试性,并促进代码的扩展和重用。然而,在应用SRP时需要注意抽象的平衡、上下文相关性和实践经验等因素。

3. 开闭原则(OCP)

3.1. 定义和原理

开闭原则(Open-Closed Principle,OCP) 是面向对象设计中的一个重要原则,由Bertrand Meyer在《面向对象软件构造》一书中提出。该原则指导我们设计软件实体(类、模块、函数等)时,应该对扩展开放,对修改关闭。

具体而言,开闭原则要求我们应该通过扩展现有的代码来实现新功能,而不是通过修改已有的代码。这意味着我们应该设计出易于扩展的架构,使得在添加新功能时,不需要修改已有的代码,而只需添加新的代码。

3.2. OCP 的好处与目标

开闭原则的主要目标是提高代码的可维护性、可扩展性和可复用性。以下是应用开闭原则的好处:

  1. 可维护性:通过遵循开闭原则,我们可以将修改的影响范围限制在扩展代码上,而不是已有的稳定代码。这样可以降低引入错误的风险,并更容易进行测试和调试。

  2. 可扩展性:通过添加新的代码来实现新功能,我们可以在不修改现有代码的情况下扩展系统。这样可以减少系统的耦合度,使得扩展变得更加简单和灵活。

  3. 可复用性:通过遵循开闭原则,我们可以将通用的功能封装成可复用的组件或模块。这样可以在不同的系统中重复使用,提高代码的复用率和开发效率。

3.3. 例子和代码展示

下面是一个简单的例子,展示如何遵循开闭原则:

class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

在上述例子中,Shape 是一个抽象类,定义了一个 area 方法。RectangleCircle 是具体的形状类,它们都继承自 Shape 并实现了 area 方法。

如果要添加一个新的形状,比如 Triangle,只需创建一个新的类并继承自 Shape,然后实现 area 方法即可,而不需要修改已有的代码。

3.4. 如何实现开闭原则:扩展、抽象、多态等

实现开闭原则的关键在于以下几个概念和技术:

  1. 扩展:通过添加新的代码来实现新功能,而不是修改已有的代码。新功能的实现应该以一种模块化的方式进行,以便于添加、修改和移除功能。

  2. 抽象:使用抽象类、接口或基类来定义稳定的公共接口。这样可以将实现细节与接口分离,让扩展代码针对接口编程,而不是具体的实现。

  3. 多态:通过多态性,我们可以在不同的对象上调用相同的方法,实现不同的行为。多态性使得我们可以通过接口来操作对象,而不需要关心具体的对象类型。

  4. 依赖倒置原则:依赖倒置原则要求我们依赖于抽象而不是具体的实现。通过依赖倒置,我们可以通过接口或抽象类来定义依赖关系,从而使得系统更加灵活和可扩展5. 设计模式:设计模式是一种常见的实现开闭原则的方法。例如,使用策略模式可以将可变的行为封装成独立的策略类,通过替换不同的策略类来实现功能的扩展,而不需要修改原有的代码。

3.5. 注意事项和局限性

在应用开闭原则时,需要注意以下事项和局限性:

  1. 适度的抽象:过度的抽象可能会引入不必要的复杂性。在设计时要根据实际需求和系统复杂性,找到合适的抽象级别。

  2. 预留扩展点:在设计时要预留一些扩展点,以便未来能够方便地添加新功能。这需要一定的设计和规划,以避免对现有代码的大规模修改。

  3. 不可避免的修改:尽管开闭原则鼓励我们通过扩展来实现新功能,但有些情况下仍然需要对已有的代码进行修改。在某些情况下,修改可能是合理和必要的,但应该尽量限制修改的范围和影响。

  4. 平衡:开闭原则需要在可扩展性和可维护性之间进行权衡。有时为了满足某种扩展需求,可能需要引入一定的复杂性和抽象层次,这可能会增加维护的难度。

总之,开闭原则是面向对象设计中的重要原则,通过遵循该原则可以提高代码的可维护性、可扩展性和可复用性。然而,实际的软件开发中需要权衡不同的因素,并根据具体情况进行灵活的设计和调整。

4.里氏替换原则(LSP)

LSP是一项重要的原则,它强调子类的替换能力,保证了代码的可靠性和可扩展性。通过合理地使用继承、重写、抽象和多态等特性,可以更好地遵循LSP原则,并设计出高质量的面向对象的代码。

4.1. 定义和原理

里氏替换原则(Liskov Substitution Principle,简称LSP)是面向对象设计的原则之一,由Barbara Liskov提出。LSP原则定义如下:

“如果S是T的子类型,那么T类型的对象可以被S类型的对象替换(即可以使用S类型的对象代替T类型的对象),而程序的逻辑行为不会受到影响。”

简而言之,LSP原则要求子类能够完全替代父类,而不会导致程序出错或产生意外的结果。子类在继承父类的同时,应该保持父类的行为规范,并且可以通过方法重写或方法扩展来增加新的行为,但不能修改父类原有的行为。

4.2. LSP的好处与目标

LSP的主要目标是增强代码的可扩展性、可维护性和可复用性。遵循LSP原则可以带来以下好处:

  1. 代码的可替换性:子类可以无缝替换父类,而不会影响程序的正确性。
  2. 代码的可扩展性:通过添加新的子类,可以扩展系统的功能,而不需要修改现有的代码。
  3. 代码的可维护性:由于子类保持了父类的行为规范,因此对父类的修改不会影响到已有的子类。
  4. 代码的可复用性:通过继承和多态,可以重用已有的代码,并通过子类的特定实现来满足不同的需求。

4.3. 例子和代码展示

以下是一个使用Java实现的简单例子,展示了违反LSP和修复后遵循LSP的情况:
我们有一个矩形类和一个正方形类。初始实现中,正方形类继承自矩形类,但违反了LSP,因为对正方形类的设置宽度和高度的方法会导致宽度和高度不相等。

// 违反LSP的例子
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.width = height;
        this.height = height;
    }
}

为了修复违反LSP的问题,我们进行了重构。我们引入了一个抽象的Shape类,并让RectangleSquare都继承该抽象类。这样,每个类都有自己的calculateArea()方法来计算面积,而不再依赖于父类的方法。

// 遵循LSP的修复
abstract class Shape {
    
    
    public abstract int calculateArea();
}

class Rectangle extends Shape {
    
    
    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 Shape {
    
    
    private int side;

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

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

4.4. 如何遵循 LSP:子类与基类的关系、重写与重载、抽象和多态等

遵循LSP的指导原则:

  • 子类与基类的关系:子类应该能够替换基类,并且不会破坏程序的正确性。子类可以扩展基类的功能,但不能修改基类已有的行为。
  • 重写与重载:子类可以重写基类的方法,但在重写时应该保持相同的前置条件、后置条件和约束条件,以确保替换的正确性。不应该缩小方法的前置条件,也不应该扩大方法的后置条件。
  • 抽象和多态:通过使用抽象类或接口来定义通用的行为,然后通过多态性来实现在运行时选择不同的实现。这样可以使代码更灵活,可以根据需要替换具体的实现类,而不需要修改调用方的代码。

4.5. 注意事项和局限性

遵循LSP需要注意以下事项:

  • 父类和子类之间的关系应该是"is-a"的关系,即子类应该是父类的一种特殊情况。如果子类不满足父类的行为约定,那么它可能不适合作为父类的替代品。
  • 强制要求遵循LSP可能会导致代码中出现过度的抽象和层次结构,增加了系统的复杂性。在设计时需要权衡好可维护性和扩展性之间的平衡。
  • LSP只是面向对象设计中的一个原则,它并不能解决所有设计问题。在实际应用中,还需要考虑其他原则和设计模式来达到更好的设计效果。

5. 接口隔离原则(ISP)

一句话通俗理解: ISP要求将臃肿的接口拆分为更小、更专注的接口,以避免客户端依赖于不需要的接口。

5.1. 定义和原理

接口隔离原则是面向对象设计中的一个原则,由Robert C. Martin提出。它定义为:客户端不应该强制依赖于它们不使用的接口。接口应该被划分为更小的、更具体的部分,以便客户端只需知道和使用它们所需的接口。

在这里插入图片描述

5.2. ISP 的好处与目标

ISP的主要目标是促进代码的灵活性、可维护性和可重用性。遵循ISP有以下好处:

  • 解耦和减少依赖:通过将接口划分为更小的部分,可以减少客户端与不相关接口的依赖,从而降低耦合度。
  • 提高可维护性:当需要修改接口时,只需修改与其相关的部分,而不会影响到其他部分的代码。
  • 促进代码重用:通过定义更小的、更具体的接口,可以提高接口的可重用性,使其更适用于不同的场景和客户端需求。

5.3. 例子和代码展示

使用Java实现的简单例子,展示了违反ISP和修复后遵循ISP的情况:

我们有一个机器接口(Machine),定义了打印(print)、扫描(scan)和传真(fax)三个方法。然后我们有一个全能机器类(AllInOneMachine),实现了机器接口。

然而,这种设计违反了ISP,因为并非所有客户端都需要使用全部的功能。修复违反ISP的问题,我们将机器接口拆分为打印机接口(Printer)、扫描仪接口(Scanner)和传真机接口(FaxMachine)。然后我们的全能机器类实现这三个接口,这样客户端只需依赖于需要的接口,而不会强制依赖于不需要的接口。

违反ISP的例子

// 违反ISP的例子
interface Machine {
    
    
    void print(); // 打印
    void scan(); // 扫描
    void fax(); // 传真
}

class AllInOneMachine implements Machine {
    
    
    @Override
    public void print() {
    
    
        System.out.println("Printing...");
    }

    @Override
    public void scan() {
    
    
        System.out.println("Scanning...");
    }

    @Override
    public void fax() {
    
    
        System.out.println("Faxing...");
    }
}

遵循ISP的修复

// 遵循ISP的修复
interface Printer {
    
    
    void print();
}

interface Scanner {
    
    
    void scan();
}

interface FaxMachine {
    
    
    void fax();
}

class AllInOneMachine implements Printer, Scanner, FaxMachine {
    
    
    @Override
    public void print() {
    
    
        System.out.println("Printing...");
    }

    @Override
    public void scan() {
    
    
        System.out.println("Scanning...");
    }

    @Override
    public void fax() {
    
    
        System.out.println("Faxing...");
    }
}

5.4. 如何遵循 ISP:划分接口的原则、多接口与单接口的权衡等

以下是一些遵循ISP的指导原则:

  • 划分接口的原则:将接口划分为更小、更具体的部分,每个接口应该代表一个单一的职责或角色。这样可以使客户端只需依赖于其需要的接口,而不会依赖于其他不需要的接口。
  • 多接口与单接口的权衡:在划分接口时,需要权衡单一接口的简洁性和多接口的灵活性之间的平衡。如果单一接口足够满足大多数客户端的需求,并且没有过多的冗余方法,那么可以选择使用单一接口。但是,如果存在不同的客户端需要不同的功能子集,或者未来可能有新的功能扩展需求,那么使用多个小接口更合适。

5.5. 注意事项和局限性

在应用ISP时,需要注意以下事项和局限性:

  • 避免过分细化接口:划分接口时要注意不要过分细化,导致接口数量过多或接口之间的耦合度增加。接口的划分应该有意义,并且符合系统的特定需求。
  • 接口的稳定性:划分接口后,需要确保接口的稳定性和一致性。如果接口频繁变动,可能会导致客户端的改动和维护成本增加。
  • 需要适度的抽象:接口应该抽象出通用的行为和功能,而不是过于具体化。过度具体化的接口可能导致客户端的依赖过于细节,降低了代码的灵活性和可重用性。

6.依赖倒置原则(DIP)

一句话通俗理解 DIP(依赖倒置原则)是指高层模块不应该依赖于低层模块,而应该依赖于抽象接口,从而使得系统各部分之间的依赖关系更加灵活,降低耦合度。DIP就是让高层和低层模块都依赖于一个统一的标准,这样就避免了它们之间的紧密联系,整个系统更容易改动和适应变化。

切记 应用依赖倒置时需要谨慎权衡,避免过度设计和引入不必要的复杂性。不要为了使用设计模式 而生搬硬套。导致画蛇添足。给原本简单的系统搞的很庞大,本来一个工具类解决的问题,制造出了8个接口和实现类。

6.1. 定义和原理

依赖倒置原则是面向对象设计中的一个原则,由Robert C. Martin提出。它定义为:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。DIP要求通过抽象来解耦高层模块和低层模块之间的依赖关系,并且依赖关系应该倒置,即高层模块和低层模块都应该依赖于抽象。

6.2. DIP 的好处与目标

DIP的主要目标是降低模块之间的耦合度,提高代码的灵活性、可扩展性和可维护性。遵循DIP有以下好处:

  • 解耦模块间的依赖关系:通过引入抽象接口或抽象类,高层模块和低层模块之间的依赖关系通过抽象进行连接,而不是直接依赖于具体的实现细节。
  • 提高代码的可扩展性:由于模块之间的依赖关系是通过抽象进行定义的,当需要增加新的实现时,只需实现抽象接口或继承抽象类,并注入到高层模块中,而无需修改高层模块的代码。
  • 促进单元测试和模块的独立性:通过依赖抽象,可以更容易地对模块进行单元测试,因为可以使用模拟对象替代具体实现。

6.3. 例子和代码展示

我们有一个前端开发者类(FrontendDeveloper),它包含一个写JavaScript代码的方法。然后我们有一个项目类(Project),它在构造函数中创建了一个前端开发者对象,并在实现功能的方法中调用了开发者的方法。

然而,这种设计违反了DIP,因为项目类直接依赖于具体的前端开发者类。修复违反DIP的问题,我们引入了一个开发者接口(Developer),其中包含一个开发方法。我们的前端开发者类实现了该接口,并且项目类通过接口来依赖开发者,而不是具体的实现类。

违反DIP的例子

// 违反DIP的例子
class FrontendDeveloper {
    
    
    void writeJavaScript() {
    
    
        System.out.println("Writing JavaScript code...");
    }
}

class Project {
    
    
    private FrontendDeveloper developer;

    Project() {
    
    
        this.developer = new FrontendDeveloper();
    }

    void implementFeature() {
    
    
        developer.writeJavaScript();
    }
}

遵循DIP的修复

// 遵循DIP的修复
interface Developer {
    
    
    void develop();
}

class FrontendDeveloper implements Developer {
    
    
    @Override
    public void develop() {
    
    
        System.out.println("Writing JavaScript code...");
    }
}

class Project {
    
    
    private Developer developer;

    Project(Developer developer) {
    
    
        this.developer = developer;
    }

    void implementFeature() {
    
    
        developer.develop();
    }
}

6.4. 如何实现依赖倒置:依赖抽象、依赖注入、控制反转等

  • 依赖抽象:定义抽象接口或抽象类来描述模块之间的依赖关系,高层模块和低层模块都应该依赖于抽象。通过依赖抽象,可以将模块之间的依赖关系与具体的实现细节解耦。

  • 依赖注入(Dependency Injection):通过将依赖对象注入到需要它们的对象中,实现对依赖关系的解耦。依赖注入可以通过构造函数、方法参数、属性注入等方式进行。

  • 控制反转(Inversion of Control):控制反转是一种实现依赖注入的方式,它将对象的创建和依赖关系的管理交给外部容器或框架来完成。通过控制反转,对象不再负责自己的依赖关系的获取和管理,而是交给外部来处理。

具体的实现方法和工具库可能因编程语言和框架而异,常见的依赖注入框架包括Spring(Java)、Dagger(Java/Kotlin)和Angular(JavaScript/TypeScript)等。

6.5. 注意事项和局限性

在实践中,使用依赖倒置原则时需要注意以下事项和局限性

  • 抽象的设计需要考虑合理性和可维护性,过度抽象可能导致代码复杂化和不必要的抽象层次。

  • 应该权衡依赖倒置的成本和收益。对于简单的应用程序或小规模项目,可能不需要引入复杂的依赖注入框架。

  • 依赖注入容器的配置和管理可能需要一些额外的学习和开发成本。

  • 依赖注入可能会增加代码的复杂性和理解难度,特别是在大型项目中,需要合理地组织和管理依赖关系。

  • 依赖倒置并非适用于所有情况,某些特定场景下可能需要根据实际情况进行权衡和取舍。

8. 参考资料

https://zh.wikipedia.org/wiki/SOLID_(面向对象设计)
https://blog.csdn.net/zhengzhb/article/details/7296921

https://medium.com/front-end-weekly/solid-principles-concise-and-brief-explanation-96790dc63b63

SOLID原则

1. 单一职责原则(Single Responsibility Principle)

  • 一个类只应该有一个引起它变化的原因。换句话说,一个类应该只有一个职责。

2. 开放封闭原则(Open-Closed Principle)

  • 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即软件实体应该通过新增代码来扩展功能,而不是直接修改现有代码。

3. 里氏替换原则(Liskov Substitution Principle)

  • 子类应该能够替换掉其父类并且不产生任何错误或异常。换句话说,父类中定义的行为在子类中应该保持一致。

4. 接口隔离原则(Interface Segregation Principle)

  • 客户端不应该强迫依赖它们不需要的接口。应该将接口细分为更小的接口,以便客户端只需知道他们需要使用的方法。

5. 依赖倒置原则(Dependency Inversion Principle)

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体细节,具体细节应该依赖于抽象。

猜你喜欢

转载自blog.csdn.net/wangshuai6707/article/details/132915608