当前位置: 首页 > news >正文

(每日一问)设计模式:设计模式的原则与分类——如何提升代码质量?

(每日一问)设计模式:设计模式的原则与分类——如何提升代码质量?

设计模式不仅是解决常见软件开发问题的良方,还依赖于一些关键原则来指导其应用。这些原则帮助开发者创建出更灵活、更易维护的代码结构。与此同时,设计模式本身也分为三大类:创建型模式、结构型模式和行为型模式,每一类设计模式都有其独特的用途和应用场景。本文将探讨设计模式的核心原则与分类,并通过具体的代码示例说明如何在实践中应用这些原则,以提升代码的质量和可扩展性。


文章目录

  • (每日一问)设计模式:设计模式的原则与分类——如何提升代码质量?
    • 一、什么是设计模式?
      • 1.1 设计模式的起源
      • 1.2 设计模式的定义
      • 1.3 设计模式的作用
    • 二、设计模式的原则
      • 2.1设计模式原则
        • 2.1.1 单一职责原则(Single Responsibility Principle,SRP)
        • 2.1.2 开闭原则(Open/Closed Principle,OCP)
        • 2.1.3 里氏替换原则(Liskov Substitution Principle,LSP)
        • 2.1.4 接口隔离原则(Interface Segregation Principle,ISP)
        • 2.1.5 依赖倒置原则(Dependency Inversion Principle,DIP)
      • 2.2如何在设计模式中应用这些原则?
        • 2.2.1 识别职责并分离
        • 2.2.2 使用接口和抽象类进行扩展
        • 2.2.3 正确使用继承
        • 2.2.4 拆分接口
        • 2.2.5 依赖于抽象而非具体实现
    • 三、设计模式的分类
      • 3.1 创建型模式
      • 3.2结构型模式
      • 3.3 行为型模式
    • 四、总结

一、什么是设计模式?

设计模式(Design Patterns)是经验的总结,它们提供了一些可重用的解决方案,用来解决在软件设计中反复出现的问题。设计模式并不是代码,而是为某个特定问题提出的一种思路或者模板,开发者可以根据需求将其应用到具体的代码中。

1.1 设计模式的起源

设计模式的概念最早来源于建筑领域,由建筑学家克里斯托弗·亚历山大提出。他认为建筑设计中有一些通用的解决方案可以重复使用。这个思想后来被引入到软件开发中,由此诞生了软件设计模式。

1.2 设计模式的定义

简而言之,设计模式是一套针对常见软件设计问题的最佳实践,旨在帮助开发者以一种更灵活、更高效的方式进行编程。每种设计模式都有一个特定的名字、描述和用法,并且能够在不同的场景下重复使用。

1.3 设计模式的作用

设计模式在软件开发中扮演了至关重要的角色。它们不仅提供了解决问题的标准化方法,还帮助开发者以更少的代码实现更复杂的功能。以下是设计模式的几个主要作用:

  • 提高代码复用性:设计模式提供了一种通用的解决方案,使开发者能够在不同的项目中重复使用这些解决方案。
  • 增强代码可读性:设计模式有助于创建结构清晰、易于理解的代码,这对于团队协作尤为重要。
  • 简化维护:通过应用设计模式,代码结构更加清晰,模块之间的耦合度降低,从而使后期的维护更加简单。
  • 提高扩展性:设计模式为软件的扩展提供了良好的基础,使得新功能的添加不会破坏原有的系统。

二、设计模式的原则

设计模式的原则是指导我们如何编写优秀软件的基本准则。这些原则不仅适用于设计模式的应用,也适用于软件设计的各个方面。通过遵循这些原则,开发者可以创建出更为灵活、可维护的代码结构。

2.1设计模式原则

2.1.1 单一职责原则(Single Responsibility Principle,SRP)

定义:一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。

解释:单一职责原则强调一个类应仅负责一个功能或模块。将多个职责混合在一个类中,会导致类变得臃肿和复杂,使得维护和修改变得困难。

代码示例

// 图书信息类,负责存储和管理图书的基本信息
public class BookInfo {private String title;private String author;// 省略详细实现
}// 借阅管理类,负责管理图书的借阅逻辑
public class BorrowingManager {public void borrowBook(BookInfo book) {// 处理借阅逻辑}// 省略详细实现
}// 库存管理类,负责管理图书的库存
public class InventoryManager {public void updateInventory(BookInfo book, int change) {// 更新库存逻辑}// 省略详细实现
}

解释:通过将图书信息、借阅管理和库存管理分离到不同的类中,每个类只负责一个职责,这样可以使得代码更清晰、更易于维护。

2.1.2 开闭原则(Open/Closed Principle,OCP)

定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

解释:开闭原则提倡在不修改现有代码的前提下,通过扩展功能来增加新特性。这样可以减少引入新错误的风险,同时提高代码的可扩展性。

代码示例

// 支付接口,定义支付操作
public interface PaymentMethod {// 定义一个通用的支付方法,所有具体的支付方式都要实现这个方法void pay(double amount);
}// 实现具体的支付方式:信用卡支付
public class CreditCardPayment implements PaymentMethod {@Overridepublic void pay(double amount) {// 处理信用卡支付的具体逻辑System.out.println("Paid " + amount + " using Credit Card.");}
}// 实现具体的支付方式:PayPal支付
public class PayPalPayment implements PaymentMethod {@Overridepublic void pay(double amount) {// 处理PayPal支付的具体逻辑System.out.println("Paid " + amount + " using PayPal.");}
}// 支付系统类,依赖于支付接口
public class PaymentProcessor {private PaymentMethod paymentMethod;// 构造函数接受一个支付方式的实现,通过依赖注入方式注入具体的支付方式public PaymentProcessor(PaymentMethod paymentMethod) {this.paymentMethod = paymentMethod;}// 处理支付请求,调用具体支付方式的pay方法public void processPayment(double amount) {paymentMethod.pay(amount); // 调用具体的支付逻辑}
}

如果需要添加一个新的支付方式,例如“Google Pay”,只需创建一个实现PaymentMethod接口的新类:

// 实现具体的支付方式:Google Pay支付
public class GooglePayPayment implements PaymentMethod {@Overridepublic void pay(double amount) {// 处理Google Pay支付的具体逻辑System.out.println("Paid " + amount + " using Google Pay.");}
}

然后在使用PaymentProcessor时,通过构造函数传入这个新类的实例:

PaymentMethod googlePay = new GooglePayPayment();
PaymentProcessor processor = new PaymentProcessor(googlePay);
processor.processPayment(100.0); // 输出: Paid 100.0 using Google Pay.

解释:通过定义支付接口并实现不同的支付方式,我们可以在不修改PaymentProcessor类的情况下添加新的支付方式,这符合开闭原则。

2.1.3 里氏替换原则(Liskov Substitution Principle,LSP)

定义:子类对象必须能够替换父类对象,并且程序的行为不会改变。

解释:里氏替换原则强调子类必须完全实现父类的功能,并且不能改变父类的预期行为。这确保了继承关系的正确性,并防止多态行为中的意外错误。

代码示例

// 鸟类基类,具有飞行能力
public class Bird {public void fly() {// 实现飞行功能}
}// 鹰类,继承自鸟类,可以飞行
public class Eagle extends Bird {// 继承fly方法
}// 企鹅类,不会飞行,继承自不能飞的鸟类
public class Penguin extends NonFlyingBird {// 企鹅没有fly方法
}// 不飞的鸟类基类
public class NonFlyingBird extends Bird {@Overridepublic void fly() {//当NonFlyingBird类继承自Bird类时,它重写了fly()方法。Java允许子类重写父类的方法,这样子类可以提供与父类不同的实现。throw new UnsupportedOperationException("不飞的鸟不会飞");}
}

解释:通过创建一个不飞的鸟类基类,企鹅继承自这个基类,从而避免了子类不完全符合父类行为的问题,符合里氏替换原则。

2.1.4 接口隔离原则(Interface Segregation Principle,ISP)

定义:客户端不应该被强迫依赖于它不需要的接口。

解释:接口隔离原则提倡将庞大的接口拆分为更小、更专一的接口,以便于实现类只需依赖其需要的接口。这减少了不必要的依赖,也提高了代码的灵活性。

代码示例

// 用户添加接口
public interface UserAdd {void addUser();
}// 用户删除接口
public interface UserDelete {void deleteUser();
}// 用户更新接口
public interface UserUpdate {void updateUser();
}// 实现只需要添加用户功能的类
public class UserManager implements UserAdd {public void addUser() {// 添加用户的实现}
}

解释:通过将用户操作接口拆分为更小的接口,实现类只需实现自己所需的接口,避免了不必要的依赖,符合接口隔离原则。

2.1.5 依赖倒置原则(Dependency Inversion Principle,DIP)

定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

解释:依赖倒置原则强调模块间的依赖关系应该基于抽象(接口或抽象类),而不是具体实现。这有助于减少模块间的耦合,增强系统的可维护性和灵活性。

代码示例

// 电池接口,定义了一个供电方法
public interface Battery {void supplyPower(); // 定义供电的方法
}// AA电池实现类,实现了Battery接口
public class AABattery implements Battery {@Overridepublic void supplyPower() {System.out.println("Providing power using AA Battery");}
}// 锂电池实现类,实现了Battery接口
public class LithiumBattery implements Battery {@Overridepublic void supplyPower() {System.out.println("Providing power using Lithium Battery");}
}// 电源模块类,表示一个依赖于电池供电的模块
public class PowerModule {// Battery是一个接口类型,battery是这个接口的引用(字段)private Battery battery;// 构造函数,接受一个Battery类型的参数public PowerModule(Battery battery) {// this.battery 指的是当前对象的battery字段// battery(参数)是通过构造函数传入的具体Battery实现类的实例// 将传入的具体电池实现类保存到PowerModule对象的battery字段this.battery = battery;}// 提供电源的方法,调用Battery接口的supplyPower方法public void providePower() {battery.supplyPower(); // 使用接口提供电源,不关心具体的电池实现}
}

在实际使用中,你可以根据需要选择不同的电池实现类传递给PowerModule,然后调用providePower()方法来为电源模块供电。

public class Main {public static void main(String[] args) {// 使用AA电池为电源模块供电Battery aaBattery = new AABattery(); // 创建一个AA电池的实例PowerModule powerModule = new PowerModule(aaBattery); // 创建一个依赖于AA电池的电源模块powerModule.providePower(); // 输出: Providing power using AA Battery// 使用锂电池为电源模块供电Battery lithiumBattery = new LithiumBattery(); // 创建一个锂电池的实例PowerModule powerModule2 = new PowerModule(lithiumBattery); // 创建一个依赖于锂电池的电源模块powerModule2.providePower(); // 输出: Providing power using Lithium Battery}
}

解释:电源模块依赖于抽象的电池接口,而不是具体的电池类型,这样可以轻松替换不同类型的电池,而不需要修改电源模块的代码,符合依赖倒置原则。

2.2如何在设计模式中应用这些原则?

2.2.1 识别职责并分离

在应用设计模式时,始终考虑单一职责原则。例如,在设计工厂模式时,确保每个工厂类只负责创建一种类型的对象,避免让工厂类承担过多职责。

2.2.2 使用接口和抽象类进行扩展

在设计系统时,通过定义接口或抽象类来确保代码对扩展开放、对修改关闭。例如,在策略模式中,将可变的算法部分封装在独立的策略类中,通过接口扩展新的算法,而不修改现有代码。

2.2.3 正确使用继承

继承是设计模式中的一个重要机制,但使用时应遵循里氏替换原则。确保子类可以完全替代父类,而不会引起程序的异常行为。例如,在装饰器模式中,装饰类应能够替换被装饰的对象,而不影响其行为。

2.2.4 拆分接口

在设计接口时,避免创建过于庞大的接口,应用接口隔离原则。例如,在适配器模式中,通过将接口细化,可以避免实现类承担不必要的职责。

2.2.5 依赖于抽象而非具体实现

在设计系统的模块间依赖时,尽量使用依赖倒置原则。例如,在观察者模式中,让观察者依赖于抽象的通知接口,而不是具体的通知实现。

三、设计模式的分类

常见的设计模式可以分为三大类:创建型模式结构型模式行为型模式。下面是每类设计模式的详细列表及其简要说明:

3.1 创建型模式

设计模式说明
单例模式确保一个类只有一个实例,并提供一个全局访问点。
工厂方法模式定义一个用于创建对象的接口,但由子类决定实例化哪个类。
抽象工厂模式提供一个创建一系列相关或依赖对象的接口,而无需指定它们具体的类。
生成器模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
原型模式通过复制现有对象来创建新的对象,而不是直接实例化类。

3.2结构型模式

设计模式说明
适配器模式将一个类的接口转换成客户希望的另一个接口,使原本不兼容的类可以一起工作。
桥接模式将抽象部分与实现部分分离,使它们可以独立变化。
装饰器模式动态地给对象添加一些额外的职责,就增加功能而言,装饰器模式比生成子类更灵活。
外观模式为子系统中的一组接口提供一个一致的界面,定义一个高层接口使子系统更易使用。
享元模式通过共享已经存在的对象来减少创建对象的数量,以节省内存。
代理模式为其他对象提供一种代理以控制对这个对象的访问。

3.3 行为型模式

设计模式说明
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者与接收者耦合在一起。
命令模式将请求封装成对象,使得可以用不同的请求对客户进行参数化。
解释器模式给定一种语言,定义它的文法表示,并定义一个解释器来解释语言中的句子。
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
中介者模式用一个中介对象来封装一系列对象的交互。中介者使各对象不需要显式地相互引用。
备忘录模式在不破坏封装性的前提下,捕获并保存一个对象的内部状态,以便以后恢复它。
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知并自动更新。
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
策略模式定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。
模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
访问者模式在不改变数据结构的前提下,增加作用于对象结构上的新操作。

四、总结

设计模式的核心原则为我们提供了编写高质量代码的指南,而设计模式的分类则帮助我们更好地理解这些模式的应用场景。通过遵循单一职责、开闭原则等关键原则,开发者可以设计出更加灵活和可扩展的软件系统。同时,理解并应用创建型、结构型和行为型模式,可以使我们在面对不同类型的问题时,选择最合适的解决方案。在实际开发中,掌握这些原则和模式,能够显著提升代码质量,使软件开发更加高效和可靠。

✨ 我是专业牛,一个渴望成为大牛🏆的985硕士🎓,热衷于分享知识📚,帮助他人解决问题💡,为大家提供科研、竞赛等方面的建议和指导🎯。无论是科研项目🛠️、竞赛🏅,还是图像🖼️、通信📡、计算机💻领域的论文辅导📑,我都以诚信为本🛡️,质量为先!🤝 如果你觉得这篇文章对你有所帮助,别忘了点赞👍、收藏📌和关注🔔哦!你的支持是我继续分享知识的动力🚀!✨ 如果你有任何问题或需要帮助,随时留言📬或私信📲,我都会乐意解答!😊

相关文章:

  • leetcode 80 删除有序数组中的重复项 II
  • 24.8.26学习心得
  • Python将Word文档转为PDF
  • go国内源设置
  • 四,接口类型和网络协议
  • 数据结构-全部由1组成的子矩形数量
  • Springboot统一给redis缓存的Key加前缀
  • 又一个坑爹:未启用约束一行或多行中包含违反非空、唯一或外键约束的值。
  • 并行 parallel DOP 受 Resource Manager 限制
  • spring框架简介
  • 网络工程师学习笔记——广域网通信
  • redis的aof日志配置项详解
  • AI大模型编写多线程并发框架(六十一):从零开始搭建框架
  • 【书生大模型实战营第三期 | 进阶岛第5关-茴香豆:企业级知识库问答工具】
  • 【深度学习】嘿马深度学习笔记第5篇:神经网络与tf.keras,学习目标【附代码文档】
  • css选择器
  • Django 博客开发教程 16 - 统计文章阅读量
  • HashMap ConcurrentHashMap
  • HTTP那些事
  • IP路由与转发
  • JavaScript中的对象个人分享
  • Java到底能干嘛?
  • java第三方包学习之lombok
  • Java知识点总结(JavaIO-打印流)
  • LintCode 31. partitionArray 数组划分
  • React的组件模式
  • React-生命周期杂记
  • SOFAMosn配置模型
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • 闭包--闭包作用之保存(一)
  • 反思总结然后整装待发
  • 和 || 运算
  • 计算机常识 - 收藏集 - 掘金
  • 聊聊sentinel的DegradeSlot
  • 前端之Sass/Scss实战笔记
  • 深入浅出webpack学习(1)--核心概念
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • # centos7下FFmpeg环境部署记录
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • #QT(串口助手-界面)
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (5)STL算法之复制
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (day6) 319. 灯泡开关
  • (二)c52学习之旅-简单了解单片机
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (十三)Maven插件解析运行机制
  • (顺序)容器的好伴侣 --- 容器适配器
  • (文章复现)基于主从博弈的售电商多元零售套餐设计与多级市场购电策略
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转载)hibernate缓存