悉数六大设计原则

news2026/4/2 9:49:50

悉数六大设计原则

目录

  • 悉数六大设计原则
    • 前言☕
    • 谁发明了设计模式
    • 设计原则
      • 设计原则与设计模式的关系
    • 单一职责
      • 什么是单一职责
      • 不遵循单一职责原则的设计
      • 遵循单一职责原则的设计
      • 单一职责的优点
      • 示例代码:
    • 里氏替换原则
      • 什么是里氏替换原则
      • 示例代码:
        • 违反里氏替换原则的代码
        • 遵循里氏替换原则的代码
      • 里氏替换原则的优点
    • 依赖倒置原则
      • 什么是依赖倒置原则
      • 依赖倒置原则的核心思想
      • 依赖倒置原则的优点
      • 示例代码:
        • 不遵循依赖倒置原则的设计
        • 遵循依赖倒置原则的设计
      • 实际应用中的优点
    • 接口隔离原则
      • 什么是接口隔离原则
      • 示例代码:
        • 不遵循接口隔离原则的设计
        • 遵循接口隔离原则的设计
      • 实际应用中的好处
      • 示例代码:
    • 迪米特原则
      • 什么是迪米特法则
      • 迪米特法则的规则
      • 示例代码:
        • 违反迪米特法则的代码
        • 遵循迪米特法则的代码
      • 迪米特法则的优点
    • 开闭原则
      • 什么是开闭原则
      • 如何实现开闭原则
      • 示例代码:
        • 场景描述
        • 违反开闭原则的设计
        • 遵循开闭原则的设计
      • 开闭原则的优点
    • 总结🍭

前言☕

大家好,我是Leo哥🫣🫣🫣,今天开始我们来学习一下关于设计模式的内容。提起设计模式,大家肯定不陌生,可能在此之前你也多少了了解过设计模式,但在实际的业务开发中使⽤用却不不多,多数时候都是⼤大⾯面积堆积ifelse 组装业务流程,对于⼀一次次的需求迭代和逻辑补充,只能东拼⻄西凑 Ctrl+C 、 Ctrl+V 。作为一名优秀的程序员,设计模式可谓是必修课,接下来就跟着Leo哥一起来了解了解设计模式吧。

谁发明了设计模式

设计模式的概念最早是由 克⾥里里斯托佛·亚历⼭山⼤大 在其著作 《建筑模式语⾔言》 中⾸首次提出的。 本书介绍了了城市设计的 语⾔言,提供了了253个描述城镇、邻⾥里里、住宅、花园、房间及⻄西部构造的模式, ⽽而此类 语⾔言 的基本单元就是模式。后来, 埃⾥里里希·伽玛 、 约翰·弗利利赛德斯 、 拉尔夫·约翰逊 和 理理查德·赫尔姆 这四位作者接受了了模式的概念。 1994 年年, 他们出版了了 《设计模式: 可复⽤用⾯面向对象软件的基础》 ⼀一书, 将设计模式的概念应⽤用到程序开发领域中。

设计原则

在学习设计模式之前,我们应该先了解一下设计原则,那么什么是设计原则呢。设计原则是指导软件设计的一系列准则和规范,旨在帮助开发人员创建高质量的代码。这些原则强调代码的可维护性、可扩展性和灵活性,减少系统的复杂性和提高代码的可理解性。

设计原则与设计模式的关系

  • 设计原则:设计原则是高层次的指导方针,提供了软件设计的基本框架和标准。这些原则可以应用于任何软件开发项目,以确保代码的高质量和长期可维护性。
  • 设计模式:设计模式是针对特定问题的具体解决方案,是对设计原则的具体应用和实现。设计模式提供了可以复用的代码结构和模板,帮助开发人员解决常见的设计问题。

话不多说,下面我们首先来学习一下经典的六大设计原则吧。

单一职责

首先, 我们来看单一职责的定义。

单一职责原则,全称Single Responsibility Principle, 简称SRP. A class should have only one reason to change 类发生更改的原因应该只有一个 。

什么是单一职责

单一职责原则(Single Responsibility Principle, SRP) 是软件设计中的一种原则,它强调每个类应该只有一个职责,即一个类只负责一项功能或一类功能的逻辑。这个原则是 SOLID 原则中的第一个,它有助于提高代码的可维护性、可读性和可扩展性。

就一个类而言,应该仅有一个引起它变化的原因。应该只有一个职责。如果一个类有一个以上的职责,这些职责就耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。想要避免这种现象的发生,就要尽可能的遵守单一职责原则。

单一职责原则的核心就是解耦和增强内聚性。

不遵循单一职责原则的设计

public class ReportManager {
    public String generateReport() {
        // 生成报告的逻辑
        return "Report Content";
    }

    public void printReport(String report) {
        // 打印报告的逻辑
        System.out.println(report);
    }
}

在上面的代码示例中,ReportManager 类同时负责生成报告和打印报告。这两个职责耦合在一起,如果将来需要修改打印报告的方式,我们需要修改 ReportManager 类,这违反了单一职责原则。

遵循单一职责原则的设计

我们可以将生成报告和打印报告的职责分离到不同的类中:

// 生成报告的类
public class ReportGenerator {
    public String generateReport() {
        // 生成报告的逻辑
        return "Report Content";
    }
}

// 打印报告的类
public class ReportPrinter {
    public void printReport(String report) {
        // 打印报告的逻辑
        System.out.println(report);
    }
}

现在,ReportGenerator 类只负责生成报告,ReportPrinter 类只负责打印报告。这种设计使得每个类的职责单一,如果将来需要修改打印报告的方式,只需要修改 ReportPrinter 类,不会影响到 ReportGenerator 类。

单一职责的优点

  1. 提高可维护性:职责单一的类更容易理解和维护。每个类的代码量减少,逻辑更加清晰。
  2. 提高可复用性:职责单一的类可以更容易地在不同的上下文中复用,而无需担心未使用的功能带来的负担。
  3. 增强测试性:职责单一的类通常具有较少的依赖,单元测试更容易编写和执行。
  4. 降低耦合度:将不同的职责分离到不同的类中,减少了类之间的耦合,增强了系统的灵活性和可扩展性。

示例代码:

下面我们来写一个一个更完整的代码示例,展示了如何使用单一职责原则设计一个简单的学生管理系统:

// 学生类,负责学生信息
public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 学生数据库操作类,负责与数据库的交互
public class StudentRepository {
    public void save(Student student) {
        // 将学生信息保存到数据库的逻辑
        System.out.println("Saving student: " + student.getName());
    }

    public Student findByName(String name) {
        // 从数据库中查找学生信息的逻辑
        return new Student(name, 20); // 模拟返回一个学生对象
    }
}

// 学生信息展示类,负责学生信息的展示
public class StudentView {
    public void displayStudentInfo(Student student) {
        System.out.println("Student Name: " + student.getName());
        System.out.println("Student Age: " + student.getAge());
    }
}

// 主类,负责调用其他类完成具体功能
public class Main {
    public static void main(String[] args) {
        StudentRepository studentRepository = new StudentRepository();
        StudentView studentView = new StudentView();

        Student student = new Student("John Doe", 20);
        studentRepository.save(student);

        Student retrievedStudent = studentRepository.findByName("John Doe");
        studentView.displayStudentInfo(retrievedStudent);
    }
}
  • Student 类只负责保存学生的基本信息。
  • StudentRepository 类负责与数据库的交互,处理学生信息的保存和查询。
  • StudentView 类负责展示学生信息。

里氏替换原则

什么是里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)是由计算机科学家 Barbara Liskov 在 1987 年提出的,是面向对象设计的五大基本原则之一(SOLID 原则中的 L)。里氏替换原则的核心思想是:在一个程序中,如果基类可以被子类替换,而不影响程序的正确性,那么这个子类是正确的。换句话说,子类对象应该能够替换基类对象而不改变程序的行为。

里式替换原则是用来帮助我们在继承关系中进行父子类的设计。

里氏替换原则(Liskov Substitution principle)是对子类型的特别定义的. 为什么叫里式替换原则呢?因为这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。

里式替换原则有两层定义:

定义1

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

如果S是T的子类,则T的对象可以替换为S的对象,而不会破坏程序。

定义2:

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。

所有引用其父类对象方法的地方,都可以透明的替换为其子类对象

示例代码:

下面是一个违反里氏替换原则的代码示例。

违反里氏替换原则的代码

假设我们有一个基类 Bird 和一个子类 Penguin

class Bird {
    public void fly() {
        System.out.println("I can fly");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}

这个代码示例中,Penguin 类重写了 fly 方法并抛出异常,这违反了里氏替换原则,因为如果我们使用 Penguin 对象替换 Bird 对象,程序将会抛出异常,导致行为不一致。

遵循里氏替换原则的代码

为了遵循里氏替换原则,我们可以引入一个接口 Flyable,并让 Bird 和其他可以飞的鸟类实现这个接口,而 Penguin 则不实现这个接口:

interface Flyable {
    void fly();
}

class Bird {
    public void eat() {
        System.out.println("I can eat");
    }
}

class Sparrow extends Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("I can fly");
    }
}

class Penguin extends Bird {
    // 企鹅没有实现Flyable这个接口
}

在这个重构后的设计中,Penguin 类不再需要实现 fly 方法,从而避免了违反里氏替换原则。现在,如果我们有一个方法需要处理所有可以飞的鸟,我们可以使用 Flyable 接口:

public void letBirdFly(Flyable bird) {
    bird.fly();
}

public static void main(String[] args) {
    Sparrow sparrow = new Sparrow();
    Penguin penguin = new Penguin();
    
    letBirdFly(sparrow); // This works
    // letBirdFly(penguin); // 这将导致编译时错误
}

通过这种方式,我们确保了替换基类对象不会影响程序的行为,从而遵循了里氏替换原则。

里氏替换原则的优点

  1. 提高代码的可维护性:遵循里氏替换原则,可以确保子类和基类的行为一致,减少代码中的错误,提升代码的可维护性。
  2. 增强代码的可扩展性:通过接口和抽象类的使用,可以更容易地扩展系统,添加新的子类而不影响现有代码。
  3. 增强代码的可重用性:遵循里氏替换原则,可以提高代码的重用性,使得基类和子类之间的关系更加明确和稳固。

依赖倒置原则

什么是依赖倒置原则

**依赖倒置原则(Dependency Inversion Principle,DIP)**是面向对象设计的五个SOLID原则之一。该原则强调:

  1. 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
  2. 抽象不应该依赖于具体实现。具体实现应该依赖于抽象。

简单来说,依赖倒置原则提倡面向接口编程,而不是面向实现编程。这可以减少高层模块与低层模块之间的耦合,使系统更具灵活性和可扩展性。

依赖倒置原则的核心思想

  1. 依赖于抽象(接口或抽象类),而不是具体类:通过依赖于抽象,可以在不修改高层模块的情况下更换低层模块的实现。
  2. 通过依赖注入来实现依赖倒置:使用构造器注入、方法注入或属性注入的方式,将具体实现传递给高层模块。

依赖倒置原则的优点

  • 降低耦合:高层模块和低层模块之间通过接口或抽象类解耦。
  • 增强可维护性:修改低层模块的实现不会影响高层模块。
  • 提高可扩展性:可以方便地替换或新增实现而不改变现有代码。

示例代码:

不遵循依赖倒置原则的设计

在这个例子中,Light 类和 Switch 类之间有直接的依赖关系:

// 灯类
class Light {
    public void turnOn() {
        System.out.println("Light is turned on.");
    }

    public void turnOff() {
        System.out.println("Light is turned off.");
    }
}

// 开关类
class Switch {
    private Light light;

    public Switch() {
        this.light = new Light();
    }

    public void operate(String command) {
        if (command.equalsIgnoreCase("ON")) {
            light.turnOn();
        } else if (command.equalsIgnoreCase("OFF")) {
            light.turnOff();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Switch lightSwitch = new Switch();
        lightSwitch.operate("ON");
        lightSwitch.operate("OFF");
    }
}

在这个设计中,Switch 类直接依赖于 Light 类,如果需要更换 Light 的实现,需要修改 Switch 类的代码。

遵循依赖倒置原则的设计

在这个例子中,通过引入接口 Switchable,实现依赖倒置原则:

// 开关接口
interface Switchable {
    void turnOn();
    void turnOff();
}

// 灯类实现开关接口
class Light implements Switchable {
    public void turnOn() {
        System.out.println("Light is turned on.");
    }

    public void turnOff() {
        System.out.println("Light is turned off.");
    }
}

// 开关类依赖于开关接口,而不是具体的实现
class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate(String command) {
        if (command.equalsIgnoreCase("ON")) {
            device.turnOn();
        } else if (command.equalsIgnoreCase("OFF")) {
            device.turnOff();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Switchable light = new Light();
        Switch lightSwitch = new Switch(light);
        lightSwitch.operate("ON");
        lightSwitch.operate("OFF");
    }
}

在上面的设计中,Switch 类依赖于 Switchable 接口,而不是具体的 Light 类。如果将来需要更换实现,只需实现 Switchable 接口并传递新的实现给 Switch 类。

实际应用中的优点

  1. 增强代码的可测试性:通过依赖注入,可以轻松地将实际实现替换为模拟对象,从而进行单元测试。
  2. 增加代码的灵活性和可扩展性:通过依赖抽象,代码可以适应不同的实现,而不需要修改高层模块。
  3. 提高代码的可维护性:代码的变更只会影响具体实现,不会波及依赖于抽象的高层模块。

接口隔离原则

什么是接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP) 是面向对象设计的五个SOLID原则之一。该原则强调:

  1. 客户不应该被迫依赖他们不使用的方法。
  2. 多个特定客户端接口要好于一个宽泛用途的接口。

Clients should not be forced to depend upon interfaces that they don’t use. 客户端只依赖于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。

The dependency of one class to another one should depend on the smallest possible interface. 类间的依赖关系应建立在最小的接口上。

换句话说,接口隔离原则提倡将大接口拆分为多个小接口,使得接口更具针对性和灵活性。这样,客户端只需依赖它们真正需要的接口,避免了冗余和不必要的依赖。

也就是说: 接口尽量细化,接口中的方法尽量少

示例代码:

不遵循接口隔离原则的设计

在这个例子中,Worker 接口包含了所有工作者可能需要的方法,但具体的工作者类可能只需要其中的一部分:

// 工作者接口
public interface Worker {
    void work();
    void eat();
}

// 开发者类
public class Developer implements Worker {
    @Override
    public void work() {
        System.out.println("Developer is working.");
    }

    @Override
    public void eat() {
        System.out.println("Developer is eating.");
    }
}

// 机器人类
public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Robot is working.");
    }

    @Override
    public void eat() {
        // 机器人不需要吃饭,但必须实现这个方法
    }
}

在这个设计中,Robot 类被迫实现了 eat 方法,这违反了接口隔离原则。

遵循接口隔离原则的设计

通过将 Worker 接口拆分为更细化的接口,可以避免上述问题:

// 工作接口
public interface Workable {
    void work();
}

// 吃饭接口
public interface Eatable {
    void eat();
}

// 开发者类实现了工作和吃饭接口
public class Developer implements Workable, Eatable {
    @Override
    public void work() {
        System.out.println("Developer is working.");
    }

    @Override
    public void eat() {
        System.out.println("Developer is eating.");
    }
}

// 机器人类只实现了工作接口
public class Robot implements Workable {
    @Override
    public void work() {
        System.out.println("Robot is working.");
    }
}

在这个设计中,Developer 类实现了 WorkableEatable 接口,而 Robot 类只实现了 Workable 接口,遵循了接口隔离原则。

实际应用中的好处

  1. 提高灵活性:将大接口拆分为多个小接口,使得类可以选择实现自己需要的接口,增加了系统的灵活性。
  2. 减少冗余:客户端只依赖它们实际需要的接口,减少了不必要的方法实现。
  3. 增强可维护性:接口的细化使得系统更易于理解和维护,修改和扩展时影响范围更小。
  4. 提高可测试性:小接口更容易进行单元测试,因为每个接口只包含了特定的功能方法。

示例代码:

// 工具使用接口
public interface ToolUsable {
    void useTool();
}

// 吃饭接口
public interface Eatable {
    void eat();
}

// 工人类实现了工具使用和吃饭接口
public class Worker implements ToolUsable, Eatable {
    @Override
    public void useTool() {
        System.out.println("Worker is using a tool.");
    }

    @Override
    public void eat() {
        System.out.println("Worker is eating.");
    }
}

// 机器人类只实现了工具使用接口
public class Robot implements ToolUsable {
    @Override
    public void useTool() {
        System.out.println("Robot is using a tool.");
    }
}

public class Main {
    public static void main(String[] args) {
        ToolUsable workerToolUser = new Worker();
        Eatable workerEater = new Worker();
        ToolUsable robotToolUser = new Robot();

        workerToolUser.useTool();
        workerEater.eat();
        robotToolUser.useTool();
    }
}

在这个示例中,我们将 Worker 类和 Robot 类的接口细化,使得它们只实现自己需要的接口,遵循了接口隔离原则。

迪米特原则

什么是迪米特法则

迪米特法则(Law of Demeter, LoD),又称为最少知识原则(Principle of Least Knowledge),是一种软件设计原则,其主要思想是:一个对象应该对其他对象有尽可能少的了解,即一个对象不应该知道太多不属于它直接责任的对象细节。

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

朋友圈的确定“朋友”条件:

  1. 当前对象本身(this)
  2. 以参数形式传入到当前对象方法中的对象. 方法入参是一个对象, 这是这个对象和当前类是朋友
  3. 当前对象的实例变量直接引用的对象 定一个一个类, 里面的属性引用了其他对象, 那么这个对象的实例和当前实例是朋友
  4. 当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友 如果属性是一个对象, 那么属性和对象里的元素都是朋友
  5. 当前对象所创建的对象

任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”;否则就是“陌生人”。

狭义的迪米特法则的缺点:

在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关。 遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

迪米特法则的规则

  1. 只调用自己的方法:对象只能调用自己方法,或者调用由自身创建的对象的方法。
  2. 只调用直接朋友的方法:对象只能调用作为参数传递给它的对象的方法,或是它的成员变量、全局变量的方法。

示例代码:

违反迪米特法则的代码

假设我们有一个 Car 类,它包含一个 Engine 对象,而 Engine 对象包含一个 Oil 对象:

class Oil {
    public void checkOilLevel() {
        System.out.println("Checking oil level");
    }
}

class Engine {
    private Oil oil;

    public Engine() {
        this.oil = new Oil();
    }

    public Oil getOil() {
        return oil;
    }
}

class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine();
    }

    public Engine getEngine() {
        return engine;
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        // 违反迪米特法则的代码:直接访问内部对象的内部对象的方法
        car.getEngine().getOil().checkOilLevel();
    }
}

在上面的例子中,Main 类通过 Car 对象访问 Engine 对象,再通过 Engine 对象访问 Oil 对象,最终调用 checkOilLevel 方法。这违反了迪米特法则,因为 Main 类知道了太多关于 EngineOil 的细节。

遵循迪米特法则的代码

我们可以通过在 Car 类中添加一个方法,来避免直接访问内部对象的内部对象:

class Oil {
    public void checkOilLevel() {
        System.out.println("Checking oil level");
    }
}

class Engine {
    private Oil oil;

    public Engine() {
        this.oil = new Oil();
    }

    public void checkOilLevel() {
        oil.checkOilLevel();
    }
}

class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine();
    }

    public void checkOilLevel() {
        engine.checkOilLevel();
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        // 遵循迪米特法则的代码:只调用直接对象的方法
        car.checkOilLevel();
    }
}

在这个重构后的例子中,Main 类只调用了 Car 对象的方法 checkOilLevelCar 对象内部处理了所有与 EngineOil 对象的交互。这遵循了迪米特法则,降低了对象之间的耦合度。

迪米特法则的优点

  1. 降低耦合度:减少对象之间的依赖关系,使得系统更容易维护和扩展。
  2. 提高内聚性:每个对象只关注自身的职责,增强了代码的内聚性。
  3. 增强可读性:减少了代码的复杂性,使代码更容易理解和阅读。

开闭原则

什么是开闭原则

开闭原则(Open-Closed Principle, OCP)是面向对象设计中的重要原则之一,由 Bertrand Meyer 于 1988 年提出。它是 SOLID 原则中的第二个,指的是软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着一个系统在需要改变的时候,应该通过扩展已有代码的行为来实现,而不是修改已有的代码。

如何实现开闭原则

“需求总是变化”、“世界上没有一个软件是不变的”。这里投射出的意思是:需求总是变化的, 可是对于软件设计者来说, 如何才能做到不对原有系统修改的前提下, 实现灵活的扩展. 这就是开闭原则要实现的。

我们在设计系统的时候, 不可能设想一次性把需求确定后, 后面就不改变了.这不科学也不现实的. 既然需求是一定会变化的, 那么我们要如何优雅的面对这种变化呢? 如何设计可以使软件相对容易修改, 不至于需求一变, 就要把整个程序推到重来?

开封-封闭原则. 设计软件要容易维护且不容易出问题的最好办法, 就是多扩展, 少修改。

开闭原则通常通过使用抽象和多态性来实现。具体来说,可以通过以下几种方法:

  1. 使用接口或抽象类:定义一个接口或抽象类,并通过不同的实现类来扩展功能。
  2. 使用设计模式:策略模式、装饰器模式、工厂模式等设计模式都可以帮助实现开闭原则。

示例代码:

场景描述

假设我们要开发一个简单的绘图应用程序,该程序可以绘制不同的形状(如圆形、矩形)。我们希望能够在不修改现有代码的情况下,轻松添加新的形状。

违反开闭原则的设计

以下代码在添加新形状时需要修改 ShapeDrawer 类,违反了开闭原则:

class ShapeDrawer {
    public void draw(String shapeType) {
        if (shapeType.equals("circle")) {
            System.out.println("Drawing a circle");
        } else if (shapeType.equals("rectangle")) {
            System.out.println("Drawing a rectangle");
        }
        // 添加新形状时需要修改此处代码
    }
}

public class Main {
    public static void main(String[] args) {
        ShapeDrawer drawer = new ShapeDrawer();
        drawer.draw("circle");
        drawer.draw("rectangle");
    }
}
遵循开闭原则的设计

我们可以使用策略模式,通过抽象类或接口来扩展新形状,而不需要修改现有的代码:

// 定义抽象类 Shape
abstract class Shape {
    public abstract void draw();
}

// 圆形类
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

// 矩形类
class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

// 使用策略模式的绘图类
class ShapeDrawer {
    private Shape shape;

    public void setShape(Shape shape) {
        this.shape = shape;
    }

    public void drawShape() {
        shape.draw();
    }
}

public class Main {
    public static void main(String[] args) {
        ShapeDrawer drawer = new ShapeDrawer();
        
        drawer.setShape(new Circle());
        drawer.drawShape();
        
        drawer.setShape(new Rectangle());
        drawer.drawShape();
        
        // 可以通过添加新类来扩展新形状,而无需修改 ShapeDrawer 类
        class Triangle extends Shape {
            @Override
            public void draw() {
                System.out.println("Drawing a triangle");
            }
        }
        
        drawer.setShape(new Triangle());
        drawer.drawShape();
    }
}

在这个示例代码中,我们定义了一个抽象类 Shape,并为每种形状创建一个具体实现类。ShapeDrawer 类通过组合一个 Shape 对象来绘制形状。在需要添加新形状时,只需创建一个新的形状类并实现 Shape 抽象类,而无需修改 ShapeDrawer 类的代码。这就实现了开闭原则。

开闭原则的优点

  1. 提高可维护性:减少了对已有代码的修改,降低了引入新错误的风险。
  2. 提高可扩展性:通过扩展现有代码来实现新功能,而不是修改现有代码,增加了系统的灵活性。
  3. 提高复用性:抽象和实现分离,使得代码更易于复用。

总结🍭

在软件设计中,遵循设计原则有助于提高代码的可维护性、可扩展性和复用性。

  1. 单一职责原则(SRP):
    1. 核心思想:每个类应当只有一个引起其变化的原因,即一个类只负责一项职责。
    2. 优势:提高代码的可读性和可维护性,降低类之间的耦合度,增强系统的灵活性。
    3. 示例:将生成报告和打印报告的功能分离到不同的类中。
  2. 里氏替换原则(LSP):
    1. 核心思想:子类必须能够替换基类而不影响程序的正确性。
    2. 优势:确保子类能够正确扩展基类功能,提高代码的稳定性和可扩展性。
    3. 示例:通过接口和抽象类实现多态性,避免子类破坏基类的行为。
  3. 迪米特法则(LoD):
    1. 核心思想:一个对象应当尽可能少地了解其他对象,即只与直接的朋友通信。
    2. 优势:降低对象之间的耦合度,提高系统的内聚性和可维护性。
    3. 示例:通过在 Car 类中添加方法来避免直接访问内部对象的内部对象。
  4. 开闭原则(OCP):
    1. 核心思想:软件实体应当对扩展开放,对修改关闭。
    2. 优势:通过扩展现有代码来实现新功能,而不是修改已有代码,减少引入新错误的风险,提高系统的灵活性和可扩展性。
    3. 示例:使用策略模式,通过抽象类或接口来扩展新形状,而不修改现有代码。

在实际开发中,这些设计原则通常是相辅相成的。 例如,通过遵循单一职责原则,可以提高类的内聚性和可维护性,而结合里氏替换原则和开闭原则,可以设计出更灵活和可扩展的系统。此外,迪米特法则可以进一步降低类之间的耦合度,提高系统的健壮性。

通过理解和应用这些设计原则,可以构建出更高质量的软件系统,减少后期维护的复杂性,并提高开发效率和代码复用性。这些原则不仅适用于面向对象编程,也同样适用于其他编程范式,是软件开发过程中不可或缺的指导思想。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1796235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day30 登录界面设计

​ 本章节,实现了登录界面窗口设计 一.准备登录界面图片素材(透明背景图片) 把准备好的图片放在 Images 文件夹下面,格式分别是 .png和 .icoico 图片,右键属性,生成操作选 内容 png 图片,右键属性,生成操作选 资源 选中 login.png图片鼠标右键,选择属性。生成的操作选…

(学习笔记)数据基建-数据安全

(学习笔记)数据基建-数据安全 数据安全数据安全实施难点数据安全保障流程数据安全措施实施阶段数据安全如何量化产出数据安全思考 数据安全 数据安全问题是最近比较热的话题,数据泄漏引发的用户信任危机事件也比比皆是,以及跨部门…

windows架设NTP时间服务器进行时间同步

一、windows架设NTP时间服务器 1.win11更改注册表 winR输入regedit 2.HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config,找到Config目录,双击Config目录下的AnnounceFlags,设为5。 3.HKEY_LOCAL_MACHINE\SYSTEM\Current…

Unity + 雷达 粒子互动(待更新)

效果预览: 花海(带移动方向) VFX 实例 脚本示例 使用TouchScript,计算玩家是否移动,且计算移动方向 using System.Collections; using System.Collections.Generic; using TouchScript; using TouchScript.Pointers; using UnityEngine; using UnityEngine.VFX;public …

Java概述 , Java环境安装 , 第一个Hello World

环境变量,HelloWorld 1.会常用的dos命令 2.会安装java所需要的环境(jdk) 3.会配置java的环境变量 4.知道java开发三步骤 5.会java的入门程序(HelloWorld) 6.会三种注释方式 7.知道Java入门程序所需要注意的地方 8.知道println和print的区别第一章 Java概述 1.1 JavaSE体系介绍…

【干货】视频文件抽帧(opencv和ffmpeg方式对比)

1 废话不多说,直接上代码 opencv方式 import time import subprocess import cv2, os from math import ceildef extract_frames_opencv(video_path, output_folder, frame_rate1):"""使用 OpenCV 从视频中抽取每秒指定帧数的帧,并保存到指定文件夹…

关于队列的知识点以及例题讲解

本篇文章将带大家学习队列的相关知识点,并且讲解几道例题,请各位小伙伴耐心观看 队列的概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表 遵循原则 先进先出 队列的实现 图解: 代码实…

性能监控工具

性能是任何一款软件都需要关注的重要指标。除了软件的基本功 能,性能可以说是评价软件优劣的最重要的指标之一。我们该如何有 效地监控和诊断性能问题呢?本章基于实践,着重介绍一些针对系统 和Java虚拟机的监控和诊断工具,以帮助读者在实际开…

软件测试——蓝桥杯笔记(自用)

Before和BeforeClass,在测试前,初始化Driver,BeforeClass适用于静态方法 After和AfterClass,在测试后,关闭Driver,AfterClass适用于静态方法 自动化测试记得使用BeforeClass,AfterClass 单元…

Linux应用 sqlite3编程

1、概念 SQLite3是一个轻量级的、自包含的、基于文件的数据库管理系统,常用于移动设备、嵌入式设备和小型应用程序中,应用场景如下: 移动应用程序:由于SQLite3是零配置、无服务器的数据库引擎,非常适合用于移动应用程…

【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)

导航: 本文一些内容需要聚簇索引、非聚簇索引、B树、覆盖索引、索引下推等前置概念,虽然本文有简单回顾,但详细可以参考下文的【MySQL高级篇】 【Java笔记踩坑汇总】Java基础进阶JavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成…

[Cesium]加载GeoJSON并自定义设置符号(以点要素为例)

数据准备: Geoserver发布WFS(Web Feature Service)服务 [GeoServer系列]Shapefile数据发布-CSDN博客 数据加载: 数据准备第二种方式加载数据,利用for循环加载多个图层。首先将获取数据(每获取一次获得pomise,将其加入…

SQL优化系列-快速学会分析SQL执行效率(下)

1 show profile 分析慢查询 有时需要确定 SQL 到底慢在哪个环节,此时 explain 可能不好确定。在 MySQL 数据库中,通过 profile,能够更清楚地了解 SQL 执行过程的资源使用情况,能让我们知道到底慢在哪个环节。 知识扩展&#xff1…

Wireshark 如何查找包含特定数据的数据帧

1、查找包含特定 string 的数据帧 使用如下指令: 双引号中所要查找的字符串 frame contains "xxx" 查找字符串 “heartbeat” 示例: 2、查找包含特定16进制的数据帧 使用如下指令: TCP:在TCP流中查找 tcp contai…

汽车分销商文件流转优化:实现稳定高效的文件分发处理

在汽车圈里,分销商可是个不可或缺的角色。他们既要跟汽车厂家紧紧绑在一起,还得跟下游的销售渠道或者直接跟消费者打成一片,文件来回传递那是家常便饭。 这文件发放的速度快不快,安不安全,直接影响到分销商做事的效率…

容器(Docker)安装

centos安装Docker sudo yum remove docker* sudo yum install -y yum-utils#配置docker的yum地址 sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo#安装指定版本 - 可以根据实际安装版本 sudo yum install -y docke…

Centos 报错 One of the configured repositories failed

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 使用yum update更新命令就出现下面问题,系统是刚安装的,然后修改了一下IP变成手动。(排查问题前,先回顾自己做了哪些操作,方便进一步排错&a…

策略模式的理解和运用

在之前的小游戏项目中,处理websocket长连接请求的时候,需要根据传递数据包的不同类型,进行不同的处理。为了实现这个场景,比较简单的方法就是使用if-else或者switch-case语句,根据条件进行判断。但是这导致了项目代码复…

梦幻西游外网架设教程-端游篇

《梦幻西游》是一款由中国网易公司自行开发并营运的网络国产游戏。游戏以著名的章回小说《西游记》故事为背景,透过Q版的人物,试图营造出浪漫的网络游戏风格。 《梦幻西游》拥有注册用户超过3.1亿,一共开设收费服务器达472组,最高…

Sigmoid图像

import matplotlib.pyplot as plt import numpy as npdef sigmoid(x):# 直接返回sigmoid函数return 1. / (1. np.exp((0.7 -(x))/0.075))def plot_sigmoid():# param:起点,终点,间距x np.arange(0, 1, 1 / 16000)y sigmoid(x)plt.plot(x, y)plt.show…