悉数六大设计原则

作者 : admin 本文共14091个字,预计阅读时间需要36分钟 发布时间: 2024-06-10 共2人阅读

悉数六大设计原则

目录

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

前言☕

大家好,我是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. 示例:使用策略模式,通过抽象类或接口来扩展新形状,而不修改现有代码。

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

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

本站无任何商业行为
个人在线分享 » 悉数六大设计原则
E-->