大话设计模式:七大设计原则
目录
一、单一职责原则(Single Responsibility Principle, SRP)
二、开放封闭原则(Open-Closed Principle, OCP)
三、依赖倒置原则(Dependency Inversion Principle, DIP)
四、里氏替换原则(Liskov Substitution Principle, LSP)
五、接口隔离原则(Interface Segregation Principle, ISP)
六、合成复用原则(Composite Reuse Principle, CRP)
七、迪米特法则(Demeter Principle, DP)
设计模式的七大原则是软件设计和开发中的重要指导原则,它们帮助开发者创建可扩展、可维护和灵活的软件系统。这些原则包括:
- 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起变化的原因。这意味着每个类应该有一个明确的职责,并且仅负责完成一项功能,这样可以使类更加模块化和可维护。
- 开放封闭原则(Open-Closed Principle, OCP):软件实体(如类、模块或函数)应该对扩展开放,对修改关闭。这意味着当软件需要适应新的环境或需求时,应该通过添加新的代码来扩展系统的行为,而不是修改现有的代码。
- 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这意味着代码应该依赖于接口或抽象类,而不是具体的实现类。
- 里氏替换原则(Liskov Substitution Principle, LSP):子类型必须能够替换其基类型而不会产生任何问题。这确保了继承关系的正确使用,子类应该能够保持与父类相同的接口和行为,从而保持系统的稳定性和可维护性。
- 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该依赖于它不需要的接口。这意味着接口应该被细分为更小的、更具体的接口,这样客户端只需要知道和使用它感兴趣的方法。
- 合成复用原则(Composite Reuse Principle, CRP):优先使用对象组合而不是继承来达到复用的目的。这意味着在面向对象设计中,应该优先考虑通过组合或聚合关系来复用已有的设计和实现,而不是通过继承。
- 迪米特法则(Demeter Principle, DP):一个对象应当仅与它的朋友(friendly objects)说话。这有助于减少对象之间的耦合,提高软件的可维护性和可读性。
遵循这些原则可以帮助开发人员创建更加灵活、可维护和可扩展的软件系统。
一、单一职责原则(Single Responsibility Principle, SRP)
定义:一个类应该只有一个引起它变化的原因。
一个类 / 接口 / 方法只负责一项职责
优点:降低类的负责度、提高类的可读性,提高系统的可维护性、降低变更的风险。
public interface UserService {void updateUser(User user,int opt);
}
传入修改类型,用户名,去修改用户信息。但这里却违背了(单一职责原则)
改造方法:
public interface UserService {void changeName(String name);void changePwd(String pwd);
}
这种更符合我们单一职责原则。
但是我们开发的时候可能会有些迷茫,什么时候去划分,什么时候放到一起,那这个时候,就应该去重新审视代码,哪些是需要合并到一起,哪些是要应用。
这里的原则,既是最简单的原则,也是最难的原则,难的时候,可能我们不是特别好区分。
二、开放封闭原则(Open-Closed Principle, OCP)
定义:一个软件实体,例如类、模块、函数,应该对扩展是开放的,对修改是关闭的。
实现:用抽象构建框架,用实现扩展细节。
优点:提高软件系统的可复用性及可维护性。
用例:
书籍实体:
public interface IBook {/*** 编号* @return*/Integer getId();/*** 名称* @return*/String getName();/*** 价格* @return*/Double getPrice();
}
书籍接口:
public class BookImpl implements IBook {private Integer id;private String name;private Double price;public BookImpl(Integer id, String name, Double price) {this.id = id;this.name = name;this.price = price;}@Overridepublic Integer getId() {return id;}@Overridepublic String getName() {return name;}@Overridepublic Double getPrice() {return price;}}
测试用例:
public class Test {public static void main(String[] args) {DiscountBookImpl book = new DiscountBookImpl(1,"java", 100.0);System.out.println("Book Id: " + book.getId() + ", Title: " + book.getTitle() + ", Price: " + book.getPrice());}
}
假设我们来了业务需求,书籍打折,我们在原来的书籍接口上这样修改:
public class BookImpl implements IBook {private Integer id;private String name;private Double price;public BookImpl(Integer id, String name, Double price) {this.id = id;this.name = name;this.price = price;}@Overridepublic Integer getId() {return id;}@Overridepublic String getName() {return name;}@Overridepublic Double getPrice() {return this.price * 0.8;}}
这样本身就违背了我们这条原则,正确的做法应该是:
新建一个打折书籍接口:
public class DiscountBookImpl extends BookImpl{public DiscountBookImpl(Integer id, String name, Double price, Double discount) {super(id, name, price);this.discount = discount;}@Overridepublic Double getPrice() {return super.getPrice() * 0.8;}public Double getOriginalPrice() {return super.getPrice();}
}
三、依赖倒置原则(Dependency Inversion Principle, DIP)
定义:程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程。
面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而大大提高了开发的成本。
面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。
优点:可以减少类间的耦合性,提高系统的稳定性,提高代码可读性和维护性,可降低修改程序所造成的风险。
用例:
学生实体:
public class Student {public void studyJavaCourse() {System.out.println("学习Java课程");}public void studyPythonCourse() {System.out.println("学习Python课程");}
}
学生学习课程:
public class Test {public static void main(String[] args) {Student student = new Student();student.studyJavaCourse();student.studyPythonCourse();}
}
降低耦合改造:
新建课程接口:
public interface ICourse {void study();
}
新建Java课程类:
public class JavaCourse implements ICourse {@Overridepublic void study(){System.out.println("JavaCourse study");}
}
新建Python类:
public class PythonCourse extends ICourse {@Overridepublic void study(){System.out.println("PythonCourse study");}
}
改造学生实现类:
public class Student {public void study(ICourse course){course.study();}
}
改造测试类:
public class Test {public static void main(String[] args) {JavaCourse javaCourse = new JavaCourse();Student student = new Student();Student.study(javaCourse);}
}
改造之后,依赖于上层、下层通过接口访问。
改造2【利用Spring构造器依赖注入原理】
Student改造:
public class Student {ICourse course;public Student(ICourse course){this.course = course;}public void study(){course.study();}
}
测试类改造:
public class Test {public static void main(String[] args) {JavaCourse javaCourse = new JavaCourse();Student student = new Student(javaCourse);Student.study();}
}
改造3【利用Spring Set注入】
Student改造:
public class Student {ICourse course;public void setCourse(ICourse course){this.course = course;}public void study(){course.study();}
}
测试类改造:
public class Test {public static void main(String[] args) {JavaCourse javaCourse = new JavaCourse();Student student = new Student();student.setCourse(javaCourse);Student.study();}
}
备注:执行的过程是一样的
四、里氏替换原则(Liskov Substitution Principle, LSP)
定义:派生类(子类)对象可以在程式中替代其基类(超类)对象。
因为继承带来的侵入性,增加了耦合性,也降低了代码灵活性,父类修改代码,子类也会受到影响,此时就需要里氏替换原则。
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。【在java里面可以重写,但是不建议】
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
用例:
父类:
public class Parent {public void method(int i){System.out.println("Parent method");}
}
子类:
public class Child extends Parent {@Overridepublic void method(int i){System.out.println("Child method");}
}
单元测试:
public class Test {public static void main(String[] args) {Parent p = new Parent();p.method(10);}
}
执行结果:
当改成子类时:
public class Test {public static void main(String[] args) {Parent p = new Child();p.method(10);}
}
用里氏替换原则进行调整。
父类:
public abstract class Parent {public List method(ArrayList i){return null;}public abstract ArrayList<String> method(List i);public abstract void abstractMethod(int i);
}
子类:
public class Child extends Parent {@Overridepublic ArrayList<String> method(List i){System.out.println("Child method");return null;}@Overridepublic void abstractMethod(int i) {}public void method2(int i){System.out.println("Child method2");}
}
优点:
- 提高了代码的重用性,子类拥有父类的方法和属性;
- 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;
缺点:侵入性、不够灵活、高耦合
- 继承是侵入的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
- 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成非常糟糕的结果,要重构大量的代码。
五、接口隔离原则(Interface Segregation Principle, ISP)
定义:用多个专门的接口,不使用单一的总接口,客户端不应该依赖他不需要的接口
- 一个类对应一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
优点:符合高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性
通俗点讲,把几个大的接口分成小接口,把接口细化。
动物接口:
public interface IAnimal {void pref();void fly();void swim();
}
狗实体:
public class Dog implements Animal {@Overridepublic void prey(){}@Overridepublic void fly(){}@Overridepublic void swim(){}
}
但是狗不会飞
鸟实体:
public class Bird implements Animal{@Overridepublic void prey(){}@Overridepublic void fly(){}@Overridepublic void swim(){}
}
鸟也不会游泳
这个时候,我们就应该把接口隔离,把它分开。
public class Bird implements IPreyable, IFlyable{@Overridepublic void prey(){}@Overridepublic void fly(){}
}
设计接口的时候呢,过大过小都不好,只有不断的思考,才能去实现
六、合成复用原则(Composite Reuse Principle, CRP)
定义:软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
问题由来:通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。
- 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
- 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
- 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
解决方案:合成复用原则,是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用的效果。
继承关系:
引擎类:
public class Engine {//发动机功率String power;//发动机排量String displacement;
}
汽车类:
public class Car extends Engine {//汽车型号String type;//汽车重量String weight;
}
使用合成类进行改造:
public class Car{//汽车型号String type;//汽车重量String weight;Engine engine;
}
使用之前的类作为现在的一个属性
合成是拥有关系,聚合是整体与部分的关系。
聚合:班级,学生,班级是一个整体,学生是一个部分,一个班级里有很多班级,这种关系叫聚合。而汽车,汽车有很多构建,他们有很多东西组成。聚合是实心的对象与实心的组合,而合成是空心与实心的实现。两者非常像。要根据具体业务进行区分。
七、迪米特法则(Demeter Principle, DP)
定义:一个对象对其他对象保持最少的了解。又叫最少知道原则
通俗点讲:小明、小张都想知道彼此新入职公司对方的工资,但是他们作为职业打工人心里都默念打工人基本素养(社会上的事情少打听,以免破坏员工和谐)
- 强调只和朋友交流
- 朋友:出现在成员变量、方法的输入、输出参数中的类成为成员朋友类,而出现在方法体内部的类不属于朋友类
优点:降低类之间的耦合
1987年美国在一个项目组提出的概念。
用例:
学生类:
public class Student {}
班长类:
public class Monitor {public void count(List<Student> students){System.out.println("学生数:"+students.size());}
}
老师类:
public class Teacher {public void commond(Monitor monitor){List<Student> students = new ArrayList<>();for(int i=0;i<10;i++){students.add(new Student());}monitor.count(students);}
}
测试类:
public class Test {public static void main(String[] args) {Teacher teacher = new Teacher();teacher.commond(new Monitor());}
}
改造,老师只跟班长进行交流、不跟学生进行交流:
班长类:
public class Monitor {public void count(){List<Student> students = new ArrayList<>();for(int i=0;i<10;i++){students.add(new Student());}System.out.println("学生数:"+students.size());}
}
老师类:
public class Teacher {public void commond(Monitor monitor){monitor.count();}
}
单元测试类:
public class Test {public static void main(String[] args) {Teacher teacher = new Teacher();teacher.commond(new Monitor());}
}
明显职责变得更清晰了
相关文章:

大话设计模式:七大设计原则
目录 一、单一职责原则(Single Responsibility Principle, SRP) 二、开放封闭原则(Open-Closed Principle, OCP) 三、依赖倒置原则(Dependency Inversion Principle, DIP) 四、里氏替换原则&am…...

利用多商家AI智能名片小程序提升消费者参与度与个性化体验:重塑零售行业的忠诚策略
摘要:在数字化浪潮席卷全球的今天,零售行业正经历着前所未有的变革。消费者对于购物体验的需求日益多样化、个性化,而零售商则面临着如何将一次性购物者转化为品牌忠诚者的巨大挑战。多商家AI智能名片小程序作为一种新兴的数字营销工具&#…...
Scala 闭包
Scala 闭包 Scala 闭包是一个非常重要的概念,它允许我们创建可以在稍后某个时间点执行的功能片段。闭包是一个函数,它捕获了封闭范围内的变量,即使在函数外部,这些变量也可以在函数内部使用。这使得闭包成为处理异步操作、回调和…...
前端JS总结(中)
目录 前言 正文 对象: 分类: 自定义对象: 内置对象: 重点: 常用内置对象: 字符串对象:String 获取字符串长度: 大小写转换: 获取某个字符: 截取字…...

elasticsearch的match_phrase匹配及其可能导致的查询问题
目录 1.match_phrase使用介绍 2.规避可能产生的查询问题 解决方式 一.查询和索引分词器一致,即都使用max_word或者都使用smart 二.使用slop增加匹配的容忍度 3.参考文档 1.match_phrase使用介绍 elasticsearch的match_phrase查询是全文查询,主要用…...

C++快速理解之继承
一、继承和派生 1.是什么? C 中的继承是类与类之间的关系,与现实世界中的继承类似 例如:儿子继承父亲的财产 继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程 例如: 类B继承…...

Node.JS - 基础(Express)
目录 A. 简介 B. 下载,安装 C. 启动服务,查看文件结构 A. 简介 Express 是一个基于 Node.js 平台的极简、灵活的 Web 应用开发框架,它提供了一系列强大的功能来构建 Web 应用程序和 API。 一、Express 的基本特点 简洁的路由系统: Express 的路由系…...

I/O复用
I/O复用使得程序能够同时监听多个文件描述符,这对提高程序的性能至关重要。 举个例子: 就好比你天天玩手机,你妈为了监控你,在你房间安装了一个监控,这个监控可以实时监控你的一举一动,并上传到你妈手机上…...

【验证可用】解决安装SQL Server数据库时,报错“启用 windows 功能 NetFx3 时出错,错误代码:-2146498298......“的问题
目录 背景一. 报错信息1.1 报错的图片信息1.2 报错的文字信息 二. 解决报错2.1 下载 NetFx3.cab 文件2.2 执行命令 三. SQL Server 修复安装 背景 一次在阿里云服务器安装 SQL Server 2012时,系统报错了,导致安装进行不下去…通过在网上查找了多种解决方…...

STM32的SDIO接口详解
目录 1. 定义与兼容性 2. SDIO时钟 3. SDIO命令与响应 4. SDIO块数据传输 5. SDIO控制器的硬件结构 6.代码实现 1.SD初始化 2.测试SD卡的读取 3.测试SD卡的写入 STM32的SDIO(Secure Digital Input/Output,安全数字输入输出)接口是一…...

docker容器常用指令,dockerfile
docker:容器,主要是解决环境迁移的问题,将环境放入docker中,打包成镜像。 docker的基本组成:镜像(image),容器(container),仓库(repository)。镜像相当于类,容器相当于类的实例对象…...

C语言学习笔记 Day11(指针--下)
Day11 内容梳理: 目录 Chapter 7 指针 7.6 指针 & 函数 (1)形参改变实参的值 (2)字符数组作为函数参数 1)合并字符串 2)删掉字符串中空格 (3)指针作为函数返…...

(24)(24.2) Minim OSD快速安装指南(二)
文章目录 前言 6 MinimOSD-extra NG 7 替代硬件 前言 本文简要介绍了如何连接电路板。有关更多详细说明,请参阅 MinimOSD 项目维基(MinimOSD Project wiki)。 6 MinimOSD-extra NG 该项目位于此处(here);文档位于此处(here);支撑线位于此…...

GD32 MCU碰到IIC总线卡死怎么办?
大家在使用MCU IIC通信时,若碰到设备复位或者总线干扰等情况,可能会导致IIC总线卡死,表现上总线上SDA或者SCL其中一根线为低电平,IIC总线一直处于busy状态。此时若代码上一直等待总线空闲,则可能导致软件死机ÿ…...

算法——动态规划:0/1 背包问题
文章目录 一、问题描述二、解决方案1. DP 状态的设计2. 状态转移方程3. 算法复杂度4. 举例5. 实现6. 滚动数组6.1 两行实现6.2 单行实现6.3 优缺点 三、总结 一、问题描述 问题的抽象:给定 n n n 种物品和一个背包,第 i i i 个物品的体积为 c i c_i …...

又是奇瑞,“统一下班时间”过去不久,最近又整新活了...
奇瑞 345 345 可不是奇瑞的汽车型号,而是奇瑞 7 月份会议文章中提出的新策略。 简单来说,要提高加班效率,实现 3 个人干 5 个人活,拿 4 个人的工资,要把员工当成家人一样看待,要对他们的健康幸福负责。 前面…...

ubuntu24.04lts cmake编译 opencv4.5.4 contrib的一些问题
编译之前一定要安装好必须的库,否则即使提示编译成功,调用opencv后也可能会有问题 sudo apt-get update sudo apt-get upgradesudo apt-get install -y g sudo apt-get install -y cmake sudo apt-get install -y make sudo apt-get install…...

大数据面试SQL(三):每分钟在线直播人数
文章目录 每分钟在线直播人数 一、题目 二、分析 三、SQL实战 四、样例数据参考 每分钟在线直播人数 一、题目 有如下数据记录直播平台主播上播及下播时间,根据该数据计算出平台每分钟的在线直播人数。 这里用主播名称做统计,前提是主播名称唯一…...

python中执行mysql操作并将python脚本共享
mysql下载路径: MySQL :: MySQL Community Downloads [root2 ~]# vim py001.py a3 b4 print(ab) print(a**2b**2) [root2 ~]# python py001.py 7 25 [root2 ~]# python3 >>> import random >>> random <module rando…...

HTTP、HTTPS、SOCKS5三种协议特点
在互联网通信中,HTTP、HTTPS和SOCKS5是三种至关重要的协议,它们各自具有独特的特点和应用场景。本文将详细探讨这三种协议的特点,帮助读者更好地理解它们在网络通信中的作用。 一、HTTP协议特点 HTTP(Hypertext Transfer Protoc…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...