一文读懂!软件设计模式的原理与应用

一文读懂!软件设计模式的原理与应用

  • 一、软件设计模式的原理
    • 1.单一责任原则(SRP)
    • 2.开闭原则(OCP)
    • 3.里氏替换原则(LSP)
    • 4.依赖倒置原则(DIP)
    • 5.接口隔离原则(ISP)
    • 6.迪米特法则(LoD)
  • 二、软件设计模式的应用
    • 1.创建型模式
    • 2.结构型模式
    • 3.行为型模式

一、软件设计模式的原理

1.单一责任原则(SRP)

  • 含义:一个类应该仅有一个引起它变化的原因,即一个类只负责一项职责。
  • 作用:将不同的职责分离到不同的类中,避免一个类承担过多的功能,使得类的功能明确、简洁,易于理解和维护。当某个职责发生变化时,只需要在对应的类中进行修改,不会影响到其他职责和类。
  • 示例:在一个电商系统中,用户管理类只负责处理用户的注册、登录、信息修改等与用户相关的操作,而订单处理、商品管理等功能则由其他专门的类来负责。这样,当用户管理功能需要扩展或修改时,不会对订单处理和商品管理等功能产生影响。

2.开闭原则(OCP)

  • 含义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即在不修改现有代码的前提下,通过扩展来增加新的功能。
  • 作用:提高软件的可维护性和可扩展性。当软件系统需要添加新功能时,能够在不破坏原有系统结构和功能的基础上进行扩展,避免了因修改现有代码而可能引入的错误,同时也使得软件系统更具灵活性和适应性。
  • 示例:在一个图形绘制系统中,已经实现了绘制圆形、矩形等基本图形的功能。当需要增加绘制三角形的功能时,按照开闭原则,不应该直接修改原有的图形绘制代码,而是通过增加一个新的三角形类,实现与其他图形类相同的绘制接口,来完成新功能的扩展。

3.里氏替换原则(LSP)

  • 含义:所有派生类(子类)对象都应该能够替换其基类(父类)对象,而不会影响程序的正确性。
  • 作用:确保了继承关系的合理性和稳定性,使得在使用继承时,子类能够安全地替代父类,保证了基于父类编写的代码在使用子类对象时也能正确运行,维护了程序的可靠性和可扩展性。
  • 示例:在一个几何图形系统中,长方形是四边形的一种特殊情况,遵循里氏替换原则,在程序中凡是使用四边形的地方,都应该可以用长方形来替换,而不会出现错误。例如,计算图形面积的函数,接受一个四边形对象作为参数,那么也应该能够接受长方形对象作为参数,并正确计算出长方形的面积。

4.依赖倒置原则(DIP)

  • 含义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。即通过抽象类或接口来定义依赖关系,而不是直接依赖于具体的实现类。
  • 作用:降低模块之间的耦合度,提高系统的可维护性和可扩展性。使得高层模块和低层模块之间的依赖关系更加灵活,当低层模块的实现发生变化时,只要其遵循相同的抽象接口,高层模块就不需要进行修改。
  • 示例:在一个电商系统中,业务逻辑层(高层模块)不应该直接依赖于数据库访问层(低层模块)的具体实现,而是通过定义一个抽象的数据库访问接口,业务逻辑层依赖于这个抽象接口来进行数据的读取和存储操作。这样,当数据库的类型或存储方式发生变化时,只需要在数据库访问层实现新的具体类,并实现相同的抽象接口,业务逻辑层无需修改代码即可适应变化。

5.接口隔离原则(ISP)

  • 含义:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
  • 作用:避免接口的臃肿和不必要的依赖,使得类之间的依赖关系更加清晰和简洁。每个类只需要实现它所需要的接口方法,减少了类之间的耦合度,提高了软件的可维护性和可扩展性。
  • 示例:在一个音乐播放系统中,有播放音乐、暂停音乐、快进音乐等功能。对于一个只需要实现播放和暂停功能的简单音乐播放器类,不应该强迫它依赖于包含所有播放、暂停、快进等功能的完整接口,而是应该为其提供一个只包含播放和暂停方法的精简接口,这样可以使该类更加专注于自身的功能,并且与其他功能的耦合度更低。

6.迪米特法则(LoD)

  • 含义:一个对象应该对其他对象有最少的了解,只与直接的朋友通信,而避免和陌生人通信。这里的 “朋友” 指的是对象的成员变量、方法的参数和返回值中的对象,以及方法内部创建的对象等;“陌生人” 则是指在对象的方法中出现,但不符合上述 “朋友” 定义的对象。该法则强调了对象之间的松耦合关系,以降低软件系统中各个模块之间的相互依赖程度。
  • 作用
    • 降低耦合度:使软件系统中的各个模块相对独立,当某个模块发生变化时,对其他模块的影响被控制在较小范围内,提高了软件的可维护性和可扩展性。例如,在一个复杂的企业级应用中,如果不同模块之间严格遵循迪米特法则,那么当一个业务模块的功能需要调整时,不会因为与其他模块的紧密耦合而导致大面积的代码修改。
    • 提高可理解性:每个对象的职责和与其他对象的交互关系更加明确,开发人员能够更清晰地理解代码的逻辑结构和功能,有助于提高软件开发和维护的效率。当阅读某个类的代码时,只需关注其与直接朋友的交互,而无需了解过多无关对象的细节,使代码的可读性更强。
  • 示例
    假设你正在开发一个学校管理系统,其中有学生(Student)、班级(Class)、学校(School)三个类。学生属于某个班级,班级又属于某个学校。
class School {
    private String schoolName;

    public School(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getSchoolName() {
        return schoolName;
    }
}

class Class {
    private String className;
    private School school;

    public Class(String className, School school) {
        this.className = className;
        this.school = school;
    }

    public String getClassName() {
        return className;
    }

    public School getSchool() {
        return school;
    }
}

class Student {
    private String studentName;
    private Class clazz;

    public Student(String studentName, Class clazz) {
        this.studentName = studentName;
        this.clazz = clazz;
    }

    public String getStudentName() {
        return studentName;
    }

    // 学生获取所在学校名称的方法,遵循迪米特法则
    public String getSchoolName() {
        return clazz.getSchool().getSchoolName();
    }

    // 违反迪米特法则的示例,如果学生类中直接有以下方法
    // public String getSchoolNameDirectly(School school) {
    //     return school.getSchoolName();
    // }
    // 那么学生类就与学校类产生了直接的依赖关系,不符合迪米特法则,
    // 因为学生类不应该直接知道学校类的信息,而应该通过它的直接朋友班级类来获取相关信息。
}

在上述代码中,Student 类通过它的直接朋友 Class 类来获取学校名称,而不是直接与 School 类进行交互,这遵循了迪米特法则。如果 Student 类中有直接与 School 类交互的方法,就会增加 Student 类与 School 类之间的耦合度,违反迪米特法则。

二、软件设计模式的应用

1.创建型模式

  • 单例模式
    • 定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
    • 应用场景:适用于需要全局唯一访问点或共享资源的情况。例如,在一个系统中,日志记录器需要在多个地方进行日志记录,使用单例模式可以保证整个系统中只有一个日志记录器实例,所有的日志都记录到同一个地方,方便管理和维护。另外,数据库连接池、系统配置信息等也常采用单例模式来实现,确保在整个系统中能够统一访问和管理这些资源。
  • 工厂模式
    • 定义:提供一个创建对象的工厂类,将对象的创建和使用分离,通过工厂类来根据不同的条件创建不同类型的对象。
    • 应用场景:当创建对象的过程比较复杂,或者需要根据不同的条件创建不同类型的对象时,工厂模式非常有用。例如,在一个游戏开发中,根据玩家选择的不同角色类型,使用工厂模式来创建相应的角色对象,每个角色可能有不同的属性、技能和行为,工厂类负责根据玩家的选择创建出正确的角色对象,并进行初始化设置。

2.结构型模式

  • 适配器模式
    • 定义:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。
    • 应用场景:在软件系统中,经常会遇到需要使用一些现有类,但这些类的接口与我们的需求不匹配的情况。例如,有一个旧的文件读取类,它的接口是按照特定的文件格式进行读取的,而现在我们需要读取一种新的文件格式,但是又不想修改旧的文件读取类的代码。这时可以使用适配器模式,创建一个适配器类,将新文件格式的读取接口适配到旧的文件读取类的接口上,使得旧的文件读取类能够用于读取新的文件格式。
  • 代理模式
    • 定义:为其他对象提供一个代理对象,代理对象控制对原对象的访问,在访问原对象时可以进行一些额外的处理,如权限验证、缓存、日志记录等。
    • 应用场景:当需要控制对某个对象的访问,或者在访问对象前后需要进行一些额外的操作时,代理模式是一个很好的选择。例如,在网络访问中,当访问一些国外网站时,可能会通过代理服务器来进行访问,代理服务器就相当于代理对象,它可以对请求进行过滤、缓存等操作,然后再将请求转发到目标网站,同时也可以隐藏客户端的真实 IP 地址,提高安全性。

3.行为型模式

  • 观察者模式
    • 定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
    • 应用场景:在许多软件系统中,经常会出现对象之间的状态依赖关系。例如,在社交媒体平台上,当一个用户发布了一条新动态,关注他的其他用户会收到通知,这些关注者就是观察者,发布动态的用户就是被观察的对象。当被观察的对象(用户发布动态)状态发生变化时,所有的观察者(关注者)都会得到通知并进行相应的操作(如更新动态列表、显示通知等)。此外,在图形界面应用程序中,当一个组件的状态发生变化时,其他相关组件需要自动更新,也可以使用观察者模式来实现。
  • 策略模式
    • 定义:定义一系列的算法,将它们一个个封装起来,并且使它们可以相互替换,使得算法的变化独立于使用它的客户。
    • 应用场景:在软件系统中,当需要根据不同的情况选择不同的算法或行为时,策略模式非常适用。例如,在一个电商系统中,根据不同的促销活动,需要选择不同的折扣计算策略,如满减策略、打折策略、赠品策略等。通过策略模式,可以将这些不同的折扣计算策略封装成不同的策略类,在需要计算折扣时,根据当前的促销活动选择相应的策略类来进行计算,而不需要在大量的条件判断语句中编写不同的折扣计算逻辑,使得代码更加清晰、易于维护和扩展。

你可能感兴趣的:(设计模式)