大话设计模式:七大设计原则

目录

一、单一职责原则(‌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)


设计模式的七大原则是软件设计和开发中的重要指导原则,‌它们帮助开发者创建可扩展、‌可维护和灵活的软件系统。‌这些原则包括:‌

  1. 单一职责原则(‌Single Responsibility Principle, SRP)‌:‌一个类应该只有一个引起变化的原因。‌这意味着每个类应该有一个明确的职责,‌并且仅负责完成一项功能,‌这样可以使类更加模块化和可维护。‌
  2. 开放封闭原则(‌Open-Closed Principle, OCP)‌:‌软件实体(‌如类、‌模块或函数)‌应该对扩展开放,‌对修改关闭。‌这意味着当软件需要适应新的环境或需求时,‌应该通过添加新的代码来扩展系统的行为,‌而不是修改现有的代码。‌
  3. 依赖倒置原则(‌Dependency Inversion Principle, DIP)‌:‌高层模块不应该依赖于低层模块,‌它们都应该依赖于抽象。‌抽象不应该依赖于细节,‌细节应该依赖于抽象。‌这意味着代码应该依赖于接口或抽象类,‌而不是具体的实现类。‌
  4. 里氏替换原则(‌Liskov Substitution Principle, LSP)‌:‌子类型必须能够替换其基类型而不会产生任何问题。‌这确保了继承关系的正确使用,‌子类应该能够保持与父类相同的接口和行为,‌从而保持系统的稳定性和可维护性。‌
  5. 接口隔离原则(‌Interface Segregation Principle, ISP)‌:‌客户端不应该依赖于它不需要的接口。‌这意味着接口应该被细分为更小的、‌更具体的接口,‌这样客户端只需要知道和使用它感兴趣的方法。‌
  6. 合成复用原则(‌Composite Reuse Principle, CRP)‌:‌优先使用对象组合而不是继承来达到复用的目的。‌这意味着在面向对象设计中,‌应该优先考虑通过组合或聚合关系来复用已有的设计和实现,‌而不是通过继承。‌
  7. 迪米特法则(‌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");}
}

优点:

  1. 提高了代码的重用性,子类拥有父类的方法和属性;
  2. 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;

缺点:侵入性、不够灵活、高耦合

  1. 继承是侵入的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
  2. 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成非常糟糕的结果,要重构大量的代码。 

五、接口隔离原则(‌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());}
}

明显职责变得更清晰了 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/397461.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java事务失效

目录 传送门一、概念1、事务的传播类型2、isolation3、Transactionnal注解属性 二、事务失效场景1、异常捕获2、异步处理3、final修饰事务方法4、非public5、T范围小了6、不加T或者事务传播用了NOT_SUPPORTED这种不支持事务7、数据库MyISAM不支持事务8、事务方法未被Spring管理…

Unity URP 曲面细分学习笔记

学百人时遇到了曲面着色器的内容&#xff0c;有点糊里糊涂&#xff0c;于是上知乎找到了两篇大佬的文章 Unity URP 曲面细分 和 Unity曲面细分笔记&#xff0c;本文只是自己做学习记录使用 1.曲面细分与镶嵌 曲面细分或细分曲面&#xff08;Subdivision surface&#xff09;是…

字节跳动发Seed-TTS语音合成模型,可模仿任意人的声音,效果逼真

前期我们介绍过很多语音合成的模型&#xff0c;比如ChatTTS&#xff0c;微软语音合成大模型等&#xff0c;随着大模型的不断进步&#xff0c;其合成的声音基本跟真人没有多大的区别。本期介绍的是字节跳动自家发布的语音合成模型Seed-TTS。 Seed-TTS 推理包含四个功能模块&…

无人机之热成像篇

一、定义 无人机热成像技术是指将热成像相机安装在无人机云台上&#xff0c;通过无人机的高空飞行能力和云台的稳定性&#xff0c;结合红外热成像技术对目标区域进行非接触式的温度测量和图像采集。该技术利用物体发出的红外辐射来生成图像&#xff0c;通过测量物体表面温度分布…

Leetcode JAVA刷刷站(8)字符串转换整数

一、题目概述 二、思路方向 要实现这个功能&#xff0c;我们可以遵循以下步骤来编写 myAtoi 函数&#xff1a; 去除前导空格&#xff1a;使用循环或字符串的 trim() 方法&#xff08;虽然直接操作字符串更高效的方式是使用循环&#xff09;。检查符号&#xff1a;记录第一个非…

nodejs 生成随机邮箱

首先安装依赖&#xff1a; npm install faker 示例代码&#xff1a; const faker require(faker); const fs require(node:fs) function generateRandomEmail(num){let str for (let i 0; i < num; i) {str faker.internet.email() &:focus:&;}fs.writeFil…

魔众文库系统v7.0.0版本推荐店铺功能,管理菜单逻辑优化

推荐店铺功能&#xff0c;管理菜单逻辑优化 [新功能] RandomImageProvider 逻辑升级重构&#xff0c;支持更丰富的随机图片生成 [新功能] 资源篮订单参数字段 [新功能] 首页推荐店铺功能&#xff0c;需要在后台 文库系统 → 文库店铺 开启推荐 [系统优化] Grid 快捷编辑请求…

告别DockerHub 镜像下载难题:掌握高效下载策略,畅享无缝开发体验

告别DockerHub 镜像下载难题:掌握高效下载策略,畅享无缝开发体验 1. 介绍 1.1 DockerHub简介 Docker Hub 是 Docker 提供的一项服务,用于与您的团队查找和共享容器映像。 它是世界上最大的容器映像存储库,其中包含一系列内容源,包括容器社区开发人员,开源项目和独立软…

【Kubernetes】Service 类型

Service 类型 1.NodePort2.ClusterlP3.LoadBalance4.ExternalName 在《Service 概念与实战》一文中&#xff0c;Service 的发布使用的是 NodePort 类型。除此之外&#xff0c;Service 的发布还支持 ClusterlP、LoadBalancer 和 ExternalName 这 3 种类型。 1.NodePort 在把 Se…

基于微信小程序的小区业主服务系统(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

SOMEIP_ETS_033:echoUINT8ArrayMinSize_too_short

测试目的&#xff1a; 验证DUT是否能够正确处理小于最小尺寸&#xff08;少于3个元素&#xff09;的UINT8数组参数&#xff0c;并返回相应的错误消息。 描述 本测试用例旨在检验DUT在接收到长度不足3个元素的UINT8数组参数时&#xff0c;是否能够返回错误消息MALFORMED_MESS…

【电路笔记】-L 型衰减器

L 型衰减器 文章目录 L 型衰减器1、概述2、等阻抗L型衰减器3、不等阻抗的 L型衰减器4、L型衰减器示例25、总结L型衰减器是一个简单的电阻分压器网络,可用作固定无源衰减器以降低信号幅度。 1、概述 就其基本形式而言,L 型衰减器只不过是一个非常简单的分压器网络,用于许多电…

数据结构实验:排序算法(附c++源码:冒泡、选择、希尔、快速、堆排序)

实验内容&#xff1a; 输入一组关键字序列&#xff0c;分别实现下列排序算法: 1.编写函数&#xff0c;实现简单选择排序、直接插入排序和冒泡排序算法。 2.编写函数&#xff0c;实现希尔排序算法。 3.编写函数&#xff0c;实现快速排序算法。 4.编写函数&#xff0c;实现堆…

入门 PyQt6 看过来(项目)26 在线购物-主页面

功能导航页面很简单&#xff0c;就几个按钮功能。效果如下图&#xff1a; 1 主界面 ​ 包含 “商品选购”、”下单结算“、”销售分析“四个按钮以及“功能导航”标题。 2 工程目录 首先先创建工程目录及子目录&#xff1a; ​ 3 代码 主窗口文件为Main.py&#xff0c;其…

字体识别验证码的介绍!

字体识别验证码 ​是一种安全机制&#xff0c;‌通过要求用户识别特定字体来验证用户的身份或防止自动化攻击。‌这种验证码通常包含一些经过特殊设计的字符&#xff0c;‌需要用户根据这些字符的特定样式&#xff08;‌如字体、‌字形等&#xff09;‌来进行识别和输入。‌字…

【日常开发】 java返回ECharts数据结构封装

java返回ECharts数据结构封装 一、前端页面示例图如下&#xff1a; 二、准备测试数据&#xff1a; 三、后端 格式封装代码&#xff1a; 四、最终结果&#xff1a; &#x1f388;边走、边悟&#x1f388;迟早会好 一、前端页面示例图如下&#xff1a; 二、准备测试数据&am…

LVS实战演练

目录 一.LVS简介 <1>.工作原理 <2>.相关术语 <3>.lvs集群的常用转发类型 二.部署NAT模式集群实验 <1>.实验环境 1.调度器 2.真实服务器 3.客户端 <2>.实验配置 1.VS中启用内核路由器功能 2.RS装上http服务 3.VS安装ipvsadm软件 4.…

删掉Elasticsearch6.x 的 .security-6索引会怎么样?

背景 玩了下 Elasticsearch 的认证&#xff0c;启动 ES 并添加认证后&#xff0c;看到索引列表额外多了一个 .security-6 。以为是没用的&#xff0c;手欠就给删掉了&#xff0c;然后 Elasticsearch 就访问不了了。 只好再重新部署&#xff0c;再看索引内容&#xff0c;发现这…

VMWare虚拟机磁盘扩容

文章目录 环境背景虚拟机磁盘扩容配置参考 环境 VMWare Workstation 17 ProRHEL 9.4 背景 一个RHEL虚拟机&#xff0c;其 /home 目录大小为30GB。 [ding192 ~]$ df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 4.0M 0…

Multisim 用LM358 运放模拟线性稳压器 - 运放输出饱和 - 前馈电容

就是拿运放搭一个可调的LDO 稳压器&#xff0c;类似下面这个功能框图里的感觉。本来应该非常简单&#xff0c;没什么好说的&#xff0c;没想到遇到了两个问题。 原理 - 理想运放 我用PNP 三极管Q2 作为输出&#xff0c;运放输出电压升高时&#xff0c;流过PNP 三极管BE 的电流变…