大话设计模式:七大设计原则
目录
一、单一职责原则(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…...

在ubuntu、centos、openEuler安装Docker
目录 ubuntu、centos、openEuler安装Docker 1.在 Ubuntu 上安装 Docker 1. 1 更新软件包 1. 2 安装必要的依赖 1.3 添加 Docker 的 GPG 密钥 1.4 添加 Docker 仓库 1.5 更新软件包 1.6 安装 Docker 1.7 启动并启用 Docker 服务 1.8 验证安装 1.9 运行测试容器 1.10…...

公共命名空间的例子3
有这样一个句子 用x语言解释[12*3]。 在x语言中,不符合“先乘除后加减”,这个句子应该怎样解释呢? 第一步,进行词法分析,目的是识别出注释和字符串,其中可能包括任意符号,干扰编译过程。 第二步…...

【云存储】SDS软件定义存储,数据存储的类型与技术方案(块/文件/对象,Ceph、RBD等)
【云存储】SDS软件定义存储,数据存储的类型与技术方案(块/文件/对象,Ceph、RBD等) 文章目录 1、分布式存储架构(软件定义存储SDS,超融合基础架构HCI)2、存储类型(块存储,…...

第31课 Scratch入门篇:小画家(舞台上画画)
小画家(舞台上画画) 故事背景: 在舞台上选择画笔和颜色,进行画画 程序原理: 这节课我们继续练习画笔功能,通过画笔功能我们设计一个小画板,碰到哪种颜色画笔就切换成哪种颜色。 开始编程 1、绘制一大一小的黑色圆形,小的命名为画笔,大的圆形命名为black(黑色) 2、鼠…...

QT UI界面之ListView
文章目录 概述源码怎么用代码qt design 小结 概述 本来把布局文件那块写了一遍,但是看看都跟之前那篇差不多,就换了一个稍微有点难度的,也很常用的listview来写了。来看看,有什么好玩的。 源码 先看下源码,如下&…...

freeRTOS互斥量(mutex)
目录 前言 一、互斥量概述 二、互斥量函数 1.创建 2.其他函数 三、优先级反转示例 1.概念 2.代码示例 四、优先级继承 1.概念 2.代码示例 五、递归锁 1.死锁的概念 2.自我死锁 3.函数 4.递归锁代码示例 前言 在之前的信号量中,我们想要实现互斥的…...

基于GeoTools使用JavaFx进行矢量数据可视化实战
目录 前言 一、JavaFx展示原理说明 二、GeoTools的Maven依赖问题 三、引入Geotools相关的资源包 四、创建JavaFx的Canvas实例 五、JavaFx的Scene和Node的绑定 六、总结 前言 众所周知,JavaFx是Java继Swing之后的又一款用于桌面应用的开发利器。当然࿰…...

zabbix的setup无法进入第二步
注意-部署时,报错要看的日志不止一个,php日志的报错也要看的,nginx接收到请求后是转发到php-fpm的 [rootweb01-84-41 ~]# chmod -R 777 /var/lib/php/session chmod: 无法访问"/var/lib/php/session": 没有那个文件或目录 [rootweb…...

代码随想录算法训练营第四十六天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇
一、115. 不同的子序列 题目链接:115. 不同的子序列 - 力扣(LeetCode) 文章讲解:代码随想录 (programmercarl.com)——115. 不同的子序列 视频讲解:动态规划之子序列,为了编辑距离做铺垫 | LeetCode&#x…...

宝塔安装nginx失败报错“检测到系统组件wget不存在,无法继续安装”
宝塔安装nginx失败报错“检测到系统组件wget不存在,无法继续安装” 问题描述解决方案 问题描述 在宝塔中安装lnmp环境时,安装nginx失败报错:检测到系统组件wget不存在,无法继续安装 如下图所示 通过检查发现系统是已经安装了wge…...