设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列

2023-12-22 12:47:06


前言

当谈论面向对象设计原则时,常常提到 SOLID 原则,这是一组面向对象设计的基本原则,旨在帮助开发人员设计出灵活、可维护和可扩展的软件系统。下面我将详细解释 SOLID 原则的原理,使用场景,以及代码示例。

一、什么是6大设计原则?以及它们的使用场景?

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

单一职责原则指的是一个类或模块应该有且只有一个职责。换句话说,一个类应该只负责一个逻辑单元或功能。这样做有助于提高代码的可读性、可维护性和可测试性,并降低类之间的耦合度。

##2. 里氏替换原则 (Liskov Substitution Principle, LSP):

里氏替换原则要求子类能够替换掉父类,并且在不影响程序正确性的前提下,扩展或修改父类的行为。简而言之,子类应该能够以父类的身份无缝地替代使用,这样可以确保代码的稳定性和可靠性。

3.依赖倒置原则 (Dependency Inversion Principle, DIP):

依赖倒置原则强调高层模块不应该依赖于低层模块的具体实现,而是应该依赖于抽象接口。抽象接口应该定义高层模块所需的功能,而具体实现则由低层模块来提供。这样可以减少模块之间的直接依赖,提高代码的灵活性和可扩展性。

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

接口隔离原则建议将大型接口拆分为更小、更具体的接口,以便客户端只依赖于它们所需的接口。这样可以避免客户端依赖于不需要的接口或方法,从而减少对不相关功能的依赖,提高代码的内聚性和可理解性。

##5. 迪米特法则 (Law of Demeter, LoD):

迪米特法则也被称为最少知识原则,它强调一个对象应该尽量减少与其他对象之间的直接交互,只与它们的直接朋友进行通信。直接朋友包括该对象本身、被当作参数传递到方法中的对象、该对象的成员变量、方法内部创建的对象等。这样可以减少对象之间的耦合度,提高代码的可维护性和复用性。

6.开闭原则 (Open-Closed Principle, OCP):

开闭原则指的是软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,当需要添加新的功能时,应该通过扩展现有的代码来实现,而不是直接修改已有的代码。通过使用抽象化和多态等技术,可以实现代码的可扩展性和可维护性。

这些原则共同助力于构建可维护、灵活和可扩展的软件系统,它们在面向对象设计中具有重要意义,并且相互关联。遵循这些原则可以提高代码的质量和可读性,减少代码的耦合性,并为系统的演化和变化提供更好的支持。

二、代码示列

下面提供一些示例代码,以便更好地理解每个原则的实际应用。

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

class Customer {
    private String name;
    private String email;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public void saveToDatabase() {
        // 保存客户数据到数据库
    }
    
    public void sendEmail() {
        // 发送电子邮件给客户
    }
}

在上述示例中,Customer类负责客户信息的管理和持久化到数据库,同时也负责发送电子邮件给客户。这违反了单一职责原则。更好的设计是将保存数据库和发送邮件的功能拆分到不同的类中。

2.里氏替换原则 (Liskov Substitution Principle, 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 getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
    
    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

在上述示例中,Square 类继承自 Rectangle 类,并重写了父类的设置宽度和高度的方法。然而,通过将一个 Square 对象赋值给 Rectangle 引用,可能会导致不符合预期的行为。这违反了里氏替换原则。更好的设计是将 Rectangle 和 Square 的关系改为使用组合而不是继承。

3.依赖倒置原则 (Dependency Inversion Principle, DIP):

interface MessageSender {
    void sendMessage(String message);
}

class EmailSender implements MessageSender {
    public void sendMessage(String message) {
        // 发送电子邮件
    }
}

class SMSender implements MessageSender {
    public void sendMessage(String message) {
        // 发送短信
    }
}

class NotificationService {
    private MessageSender sender;
    
    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }
    
    public void sendNotification(String message) {
        sender.sendMessage(message);
    }
}

在上述示例中,NotificationService 类依赖于抽象的 MessageSender 接口,而不是具体的实现类。这遵循了依赖倒置原则,高层模块(NotificationService)通过依赖于抽象接口(MessageSender)来与低层模块(EmailSender、SMSender)进行通信。

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

interface Printer {
    void print();
    void scan();
    void fax();
}

class SimplePrinter implements Printer {
    public void print() {
        // 打印
    }
    
    public void scan() {
        // 扫描
    }
    
    public void fax() {
        // 传真
    }
}

class AdvancedPrinter implements Printer {
    public void print() {
        // 打印
    }
    
    public void scan() {
        // 扫描
    }
    
    public void fax() {
        // 传真
    }
    
    public void photocopy() {
        // 复印
    }
}

在上述示例中,Printer 接口定义了打印、扫描和传真的方法。然而,AdvancedPrinter 类实现了额外的 photocopy() 方法。这可能导致那些只需要基本打印功能的客户端依赖于不需要的方法,违反了接口隔离原则。更好的设计是将接口拆分为更小的接口,以便客户端只依赖于它们所需的接口。

5.迪米特法则 (Law of Demeter, LoD):

class Teacher {
    private String name;
    
    public Teacher(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

class Course {
    private String name;
    private Teacher teacher;
    
    public Course(String name, Teacher teacher) {
        this.name = name;
        this.teacher = teacher;
    }
    
    public String getCourseInfo() {
        String teacherName = teacher.getName();
        return "Course: " + name + ", Teacher: " + teacherName;
    }
}

class Student {
    private String name;
    
    public Student(String name) {
        this.name = name;
    }
    
    public void enrollCourse(Course course) {
        // 学生选课
        String courseInfo = course.getCourseInfo();
        // ...
    }
}

在上述示例中,Student 类通过调用 Course 的 getCourseInfo() 方法获取课程信息,而不是直接访问 Course 和 Teacher 的内部状态。这遵循了迪米特法则,减少了对象之间的耦合。

6.开闭原则 (Open-Closed Principle, OCP):

interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    private double width;
    private double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    public double getWidth() {
        return width;
    }
    
    public double getHeight() {
        return height;
    }
    
    public double calculateArea() {
        return width * height;
    }
}

class Circle implements Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    public double getRadius() {
        return radius;
    }
    
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class AreaCalculator {
    public double calculateTotalArea(Shape[] shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.calculateArea();
        }
        return totalArea;
    }
}

在上述示例中,Shape 接口定义了 calculateArea() 方法,Rectangle 和 Circle 类实现了该接口并提供了各自的计算面积的方法。AreaCalculator 类可以通过接收一个 Shape 数组来计算所有形状的总面积,而无需修改现有的代码。这遵循了开闭原则,通过扩展 Shape 接口的实现类来添加新的形状,而无需修改已有的代码。

这些示例代码帮助说明了每个 SOLID 原则在具体的 Java 代码中的应用。请注意,这些原则并不是孤立的,它们相互关联,共同助力于构建可维护、灵活和可扩展的软件系统。

使用场景

1.单一职责原则 (SRP):

当一个类有多个责任时,应该将这些责任分离为不同的类。这样做可以提高类的内聚性,并使其更易于理解、维护和扩展。
使用场景:当一个类负责处理多个不同的功能或操作时,考虑将这些功能或操作分离到独立的类中,以确保每个类只负责一个单一的职责。

2.里氏替换原则 (LSP):

子类应该能够替代其父类,并且不会破坏程序的正确性。子类应该遵循父类的契约,并且可以在不引起意外行为的情况下进行替换。
使用场景:在继承关系中,确保子类可以无缝地替换父类,而不会导致不符合预期的行为。遵循 LSP 可以提高代码的可扩展性和灵活性。

3.依赖倒置原则 (DIP):

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现细节,而具体实现应该依赖于抽象。
使用场景:通过使用接口或抽象类来定义模块之间的依赖关系,而不是依赖于具体的实现类。这样可以降低模块之间的耦合度,使系统更加灵活和可扩展。

4.接口隔离原则 (ISP):

客户端不应该依赖于它们不需要的接口。接口应该小而专注,应该根据客户端的需要进行划分。
使用场景:当一个接口变得庞大臃肿,或者一个类需要实现大量不相关的接口方法时,考虑将接口拆分为更小的、更具体的接口,以便客户端只依赖于它们所需的接口。

5.迪米特法则 (LoD):

一个对象应该尽可能少地了解其他对象,减少对象之间的直接依赖关系。模块之间的通信应该通过最少的接口进行。
使用场景:在类之间建立松散的耦合关系,避免对其他对象的直接依赖。通过只与必要的对象进行通信,可以降低代码的复杂性,并提高代码的可维护性。

6.开闭原则 (OCP):

软件实体应该对扩展开放,对修改关闭。即,应该通过扩展现有的代码来实现新的功能,而不是修改已有的代码。
使用场景:当需要添加新的功能或修改现有功能时,通过扩展而不是修改现有的代码来实现变化。这可以减少代码的脆弱性,并增加系统的可扩展性。

这些原则在软件设计和开发中广泛应用,它们有助于构建可维护、灵活和可扩展的代码。根据具体的场景和需求,可以根据需要选择适合的原则来指导设计和开发过程。

文章来源:https://blog.csdn.net/qq_42262444/article/details/135147837
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。