【设计模式】六大原则-上
首先什么是设计模式?
相信刚上大学的你和我一样,在学习这门课的时候根本不了解这些设计原则和模式有什么用处,反而不如隔壁的C++更有意思,至少还能弹出一个小黑框,给我个hello world。
如何你和我一样也是这么想,那接下来咱们以贴合生活实际的方式来看看设计模式到底有什么神奇的地方?
【设计模式】六大原则
- 设计模式存在的意义是什么
- 六大原则
- 开闭原则
- 里氏代换原则
- 依赖倒转原则
- 应用样例
- 业务简介
- 怎么设计呢
- 实现
- 总结
设计模式存在的意义是什么
在了解设计模式之前我们还是先考虑一下设计模式的意义是什么?
不然光知道了结论,不知道是干啥的,这和没学好像也没啥区别。
咱们还是从以做系统为例来聊吧。
我们实现了一个很简单的仓库系统。早期的时候你只设计了存储胡萝卜的过程,大致流程就是拿起胡萝卜=》丢到仓库=》关闭仓库门,功能很快就实现了。
但是之后加需求了,仓库里可以存储液体了,可以认为多了一口大缸,比如现在我要存啤酒,那么与胡萝卜不同的是我不能直接把啤酒丢进去,我需要把啤酒倒进缸中,那之前的代码就不适用了,那咋办呀?
此时,一个大聪明提议,加个判断嘛,这不就ok了,然后风风火火的写完了,功能依旧正常,代码也还可以看得懂。
但是时间一长就有问题了,后续又需要往仓库中放更多种类的东西,并且存放过程并不一致,大家还是if else的堆叠。这个时候你看着现在的代码陷入了沉思,这好几千行,看都得看一会。幸好的是,目前还能跑得动,功能也正常。
但谁能想到,仓库搬家了,这就意味着我们原来把东西放到仓库步骤里存放过程都要改,好家伙,这可是个大工程,你看这好几千行的代码,只能开始了加班,心里想着要是最开始的项目架构设计的合理一点就好了,这样就不至于加班这么晚了,这个时候你才想起来了大学时期学习的那一门叫做设计模式的课程。
没错,设计模式简单来说就是前人总结的软件开发经验,让我们在后期需求增加,需求变动时不至于那么被动,并且可以极大的提高代码的可读性和重用性。
那我们怎么才能优化上面的仓库系统呢?
别着急,了解完设计模式你就清楚了。
六大原则
开闭原则
对扩展开放,对修改关闭。
相信大家已经读完一遍了,咋样,有没有豁然开朗的感觉!
反正我没有,这都是啥!!!
言归正传,咱还得看看到底咋回事。
这里的扩展可以理解为增加新的业务逻辑代码,修改则可以认为修改原有的代码。
那为啥得这么做呢?
试想一下,一个极其复杂逻辑的业务代码,经过你无数次的debug和修改之后终于可以运行了,并且功能也没问题。这个时候一个并不清楚你的实现逻辑的一个楞头小子要扩展你的业务,并且扩展的方式还是修改你的源代码,不出意外,又跑不起来了。
那如果我们在最初设计的时候就严格遵守了开闭原则,也就是说让后面的人添加新业务的时候只添加新代码而不修改源代码,那会不会好一点?
那要咋做呢?
看看下面的代码可以吗?
public interface Example {public void operate();public void setNextOne(Example example);
}public class OneExample implements Example {Example nextOne;@Overridepublic void operate() {System.out.println("一系列处理");if(nextOne!=null)nextOne.operate();}@Overridepublic void setNextOne(Example example) {nextOne = example;}
}public class ExampleTest {public static void main(String[] args) {Example oneExample = new OneExample();oneExample.operate();}
}
看到上面的代码,你可能会疑惑,这就能在满足开闭原则的条件下增加功能嘛?
是的,可以的,为啥呢?
这里我们首先要明确一个点哈,这里我们要满足的开闭原则并不是严格意义上的开闭原则,只是对核心业务代码保证开闭原则。
听到这有点绕,这是啥意思呢?
也就是说我们并不会修改原来的核心业务代码,也就是OneExample 的operate方法,因为这里面的代码逻辑我们并不清楚,随意修改容易出现问题。但是我们会修改ExampleTest中的方法,把我们添加的新功能放进去即可。
那怎么放呢?添加代码如下
public class TwoExample implements Example {Example nextOne;@Overridepublic void operate() {System.out.println("新添加的业务逻辑");if(nextOne!=null)nextOne.operate();}@Overridepublic void setNextOne(Example example) {nextOne = example;}
}public class ExampleTest {public static void main(String[] args) {Example oneExample = new OneExample();Example twoExample = new TwoExample();oneExample.setNextOne(twoExample);oneExample.operate();}
}
看到这里你会发现,利用下面这个代码就可以很优雅的执行新添加的流程。
if(nextOne!=null)nextOne.operate();
这样确实方便后续功能的添加,并且不会修改咱们原来的写的代码,这样就避免了大聪明把我们好不容易调好的代码又改成bug的问题。
这时候有小伙伴就会说了,那我怎么能想到这么做呢?
那就要好好学设计模式的那些设计模式了,然后灵活应用了,上面的这种模式就是责任链模式,后面我们会仔细的讲,大家只要了解这种思想就够了。
到现在为止,再回顾一下,你就会发现设计模式的妙处,确实可以帮我们有效的提升开发和维护效率,不知道是不是提起你对设计模式的兴趣了呢。
里氏代换原则
所有引用基类 (父类)的地方必须能透明地使用其子类的对象。
ok,不出意外,估计大家和我一样,还是看的一头雾水。
我们用通俗的话解释一下。其实就是要求子类必须要实现父类的所有方法。
这时候你不禁想问,这有啥好处呢?
我们可以把它作为开闭原则的扩展。具体有什么应用呢,我们来看看哈。
这里我们有一个汽车类Car,然后有三个实现类,比亚迪,蔚来,小鹏。Car有start,move,drift方法。现在我们有一辆比亚迪,我们要开出去,我们先看看不满足里氏代换的形式。
比亚迪 byd=new 比亚迪();
byd.start();
byd.move();
byd.drift();
由于父类无法完全调用子类的方法,我们只能使用子类来写代码,看着也好像还可以哈。
但是问题来了,我们换车了,开上蔚来了,那原来byd的代码不能用了呀,需要完全的写一遍,如下
蔚来 wl=new 蔚来();
wl.start();
wl.move();
wl.drift();
但如果遵循里氏代换之后呢?
Car car=new 比亚迪();
car.start();
car.move();
car.drift();
这个时候Car与比亚迪,蔚来,小鹏子类的方法一致,这个时候就可以直接使用父类接收子类对象,然后执行相应的操作。那我们怎么换成蔚来呢?
Car car=new 蔚来();
car.start();
car.move();
car.drift();
是不是这样就可以了,很轻松的进行修改。
结合上述两个例子,相信你也会发现所有开闭原则只是给了我们一个启发,但并不是说完全对修改关闭,而是核心业务代码对修改关闭即可。
看完这个之后,不知道你对设计模式是不是有一点新的体会了呢
依赖倒转原则
抽象不应该依赖于细节,细节应当依赖于抽象。
看得出来,这概念就挺抽象。
这又是啥意思呢?
以做菜为例子哈,我们想一下,我们要做一份西红柿炒鸡蛋,怎么写代码呢?
大致过程是不是这样
切西红柿,打鸡蛋=》炒西红柿和鸡蛋
逻辑非常合理,并且代码写的也很简单。
但现在出现一个问题,我们换口味了,想吃胡萝卜炒鸡蛋,怎么做呢?
是不是原来的流程就不可以了,我们需要写一个新的流程,需要推翻原来的流程,变成下面这样
切胡萝卜,打鸡蛋=》炒胡萝卜和鸡蛋
随着需求的更改需要大幅度修改代码,这就说明我们的结构设计并不合理,并不稳定。
那我们怎么才能再更改需求的时候并不会大幅度修改我们原来的架构呢?
这时候就可以聊聊依赖倒转原则了。
首先,我们要明白一个概念,越抽象,越稳定。
啥意思嘞
大家可能对抽象这个概念理解的就很抽象的,我们来通俗的聊聊。
其实,所谓的抽象的过程可以理解为总结事物或则流程的共同特征的过程。
例如哈,我们怎么抽象比亚迪,蔚来,小鹏,他们有一个共同点,都是汽车,还有什么共同点呢?都可以移动。
这样我们就生成了他们的抽象,也就是一个包含移动方法的汽车类。
这是总结事物,那怎么总结流程呢?
像我们刚才聊的做菜,切胡萝卜,打鸡蛋和切西红柿本质上是不是都可以抽象为备菜,炒胡萝卜,西红柿和鸡蛋是不是可以抽象为炒菜。那我们就可以改一下做菜的逻辑了。
备菜=》炒菜
很明显看出来,我们现在的流程实现就是依赖于抽象,而不是依赖实现了。
这样无论我们是要西红柿炒蛋还是胡萝卜炒蛋,都不需要修改原来的大体流程,只需要给备菜对象和炒菜对象赋值对应的对象即可,我们的项目结构就是很稳定的。这样就可以很轻松的应对需求的变化,并且对源代码的修改是极小的。
应用样例
看了这么多原则,我们举的例子都是尽量方便大家理解,并没有结合实际业务,接下来咱们看看设计模式结合业务,代码会不会变得更优雅。
业务简介
你需要写一个请假流程,请假流程由两部分组成,第一部分是申请流程,可能需要多人批准,第二部分是排班。需要考虑后续申请和排班业务的需求的变动。
怎么设计呢
我们首先可以想到的是依赖倒转,就是依赖抽象实现任务,都不需要我们抽象了,题目都给好了,就是俩抽象,申请和排班。
接下来就是考虑怎么可以满足开闭原则的前提下实现申请和排班业务?
我们是不是还是可以使用刚才用到的责任链模式去做,这样就可以保证不修改主要代码的情况下,对功能进行变更了。
这里简单讲一下责任链模式,不然大家容易看的云里雾里的。
我们画一张简单的图
他其实就是第一个链对象执行完毕,就会由下一个对象执行,直到执行到最后一个对象。
为什么说满足开闭原则呢?
因为我们在添加新功能的时候并不需要修改原来对象的业务逻辑,只需要把新添加的对象链接到链上即可,这样就增加了新的业务。
具体体现在代码是怎么弄的呢?
这样,就把twoexample链接到oneExample之后了,并且由于operate方法是这样的
这样,只要nextOne,也就是责任链的下一个链对象不为空,就会调用下一个对象的operate,这就实现了责任链。
那里氏代换呢
别着急,看看实现就知道了怎么回事了。
实现
申请
public interface Apply {public void apply();public void setNextOne(Apply apply);
}public class GroupApply implements Apply {Apply apply;@Overridepublic void apply() {System.out.println("组长批准请假流程");// 这里就是可以像链路一样调用的关键if (apply != null)apply.apply();}@Overridepublic void setNextOne(Apply apply) {this.apply = apply;}
}public class BossApply implements Apply {Apply apply;@Overridepublic void apply() {System.out.println("老板批准请假流程");// 这里就是可以像链路一样调用的关键if (apply != null)apply.apply();}@Overridepublic void setNextOne(Apply apply) {this.apply = apply;}
}
调班
public interface Adjust {public void adjust();public void setNextOne(Adjust adjust);
}public class FirstAdjust implements Adjust {Adjust adjust;@Overridepublic void adjust() {System.out.println("进行排班");// 这里就是可以像链路一样调用的关键if(adjust!=null)adjust.adjust();}@Overridepublic void setNextOne(Adjust adjust) {this.adjust=adjust;}
}
请假流程
public class MTest {public static void main(String[] args) {// 如果Apply或则Adjust的具体实现类变化了,直接修改引用即可,下面的业务逻辑代码不需要修改,这就是里氏代换和依赖倒置结合的好处。Apply groupApply = new GroupApply();Apply bossApply = new BossApply();// 把bossApply链接到groupApply之后,形成一条责任链// 后期有新的添加,像这种方式一样,链接到groupApply后即可,不需要修改原有的代码groupApply.setNextOne(bossApply);Adjust adjust = new FirstAdjust();groupApply.apply();adjust.adjust();}
}
可以看到哈,所有对象都是用的抽象接口,而不是实现,也就是我们之前提到的里氏代换。
总结
初次了解设计模式,很难理解为什么需要这些准则和规范,但真正到了开发项目的时候就会突然了解前人的智慧。这种智慧不仅仅可以应用到项目设计中,同样也可应用到我们的做人做事上。
后续的三大原则很快更新,先写到这了。
相关文章:

【设计模式】六大原则-上
首先什么是设计模式? 相信刚上大学的你和我一样,在学习这门课的时候根本不了解这些设计原则和模式有什么用处,反而不如隔壁的C更有意思,至少还能弹出一个小黑框,给我个hello world。 如何你和我一样也是这么想…...

CRC16循环冗余校验
代码: #include<stdio.h> #include <stdint.h>#define uchar unsigned char #define uint unsigned int static const uint8_t auchCRCHi[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x0…...

Mysql80主从复制搭建;遇到问题 Slave_IO_Running: Connecting和Slave_SQL_Running以及解决过程
总结主要步骤 1.配置一个提供复制的账号; 创建用户 CREATE USER replication% IDENTIFIED BY your_password; GRANT REPLICATION SLAVE ON *.* TO replication%; FLUSH PRIVILEGES;2.修改配置 选择模式 主库配置; windows的得话是my.ini文件 默认这个目…...

Yarn网络代理配置指南:在受限网络环境中优化依赖管理
Yarn是一个现代的包管理器,用于JavaScript项目,它提供了快速、可靠和安全的依赖管理方式。然而,在某些受限的网络环境中,例如公司内网或某些国家地区,直接连接到公共npm仓库可能不可行或效率低下。这时,配置…...

AOE网及其求解关键路径
全称 Activity on Edge Network 边活动网 特点 仅存在 有向无环图 作用 用于记录完成整个工程至少花费的时间 > 哪条路径最耗时?也就是“ 关键路径 ” AOE网元素介绍 关键活动 关键路径上的活动称为关键活动 , 关键活动是不允许拖延的&#x…...

【FPGA】modelsim编译verilog代码产生错误集合
错误1: LHS in procedural continuous assignment may not be a net 可能是一些变量不能放在一些begin和end中,改下assign的位置 新手求助 LHS in procedural continuous assignment may not be a net - 数字IC设计讨论(IC前端|FPGA|ASIC) - EETOP 创…...

Rabbitmq的持久化机制
我们通过手动应答处理了在消费者出故障消息丢失的情况,但是如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失。默认情况下 RabbitMQ 退出或由于某种原因崩溃时,它会清空队列和消息,除非告知它不要这样做。确保消息不会丢失可…...

Unity UnityWebRequest封装类
简化api调用流程,非常奈斯。 RestWebClient.cs using System; using System.Collections; using UnityEngine; using UnityEngine.Networking;namespace MYTOOL.RestClient {/// <summary>/// UnityWebRequest封装类/// </summary>public class RestW…...

JVM内存划分
Java虚拟机(JVM)的内存划分是指JVM在运行时所使用的内存区域的组织和管理方式。JVM内存主要分为以下几个区域: 堆区(Heap): 用途:用于存储所有对象实例和数组,是JVM中最大的一块内存…...

c++ 全排列
在C中,全排列(permutation)可以使用递归算法或标准库函数来实现。以下是使用递归和STL库std::next_permutation来生成一个集合的全排列的两种方法。 方法一:递归算法 递归方法通过交换元素来生成所有可能的排列组合。 #include…...

未授权访问漏洞系列详解⑤!
Kubernetes Api Server未授权访问漏洞 Kubernetes 的服务在正常启动后会开启两个端口:Localhost Port(默认8080)Secure Port(默认6443)。这两个端口都是提供 Api Server 服务的,一个可以直接通过Web 访问,另一个可以通过 kubectl 客户端进行调用。如果运…...

【CONDA】库冲突解决办法
如今,使用PYTHON作为开发语言时,或多或少都会使用到conda。安装Annaconda时一般都会选择在启动终端时进入conda的base环境。该操作,实际上是在~/.bashrc中添加如下脚本: # >>> conda initialize >>> # !! Cont…...

【网络世界】数据链路层
目录 🌈前言🌈 📁 初识数据链路层 📂 概念 📂 协议格式 📁 MAC地址 📂 概念 📂 与IP地址的区别 📁 MTU 📂 对IP协议的影响 📂 对UDP协议的影响…...

AllReduce通信库;Reduce+LayerNorm+Broadcast 算子;LayerNorm(层归一化)和Broadcast(广播)操作;
目录 AllReduce通信库 一、定义与作用 二、常见AllReduce通信库 三、AllReduce通信算法 四、总结 Reduce+LayerNorm+Broadcast 算子 1. Reduce 算子 2. LayerNorm 算子 3. Broadcast 算子 组合作用 LayerNorm(层归一化)和Broadcast(广播)操作 提出的创新方案解析 优点与潜在…...

2024.8.5 作业
使用有名管道实现,一个进程用于给另一个进程发消息,另一个进程收到消息后,展示到终端上,并且将消息保存到文件上一份 代码: /*******************************************/ 文件名:create.c /********…...

MySQL数据库——数据库的基本操作
目录 三、数据库的基本操作 1.数据库中库的操作 ①创建数据库 ②字符集和校验规则 ③操纵数据库 ④备份与恢复 2.数据库中表的操作 ①创建表 ②查看表 1> 查看表位于的数据库 2>查看所有表 3>查看表中的数据 4>查看创建表的时候的详细信息 ③修改表 …...

SQL数据库语句练习
1、mysql常用的数据类型是_整数(int)__、_小数(decimal)__、_字符串(varchar)__、_日期时间(datetime)___。 2、mysql的约束有__主键(primary key)_、_非空&…...

【Python】常用的pdf提取库介绍对比
提取PDF内容的Python库有多种选择,每个库都有其独特的优缺点。以下是一些常用的库以及它们的优缺点和示例代码: pdfplumberPyMuPDF (fitz)PyPDF2PDFMinerCamelot 1. pdfplumber 优点: 易于使用,提供简单直观的API。能提取文本…...

sbatch提交并行作业 运行python程序 指定输入参数从1到100
#!/bin/bash #SBATCH --job-namemy_python_job #SBATCH --outputmy_python_job_%j.out #SBATCH --errormy_python_job_%j.err #SBATCH --ntasks100# 载入所需模块 # module load python/3.8.5# 执行Python脚本并传递任务ID作为参数 for i in {1..100}; dosrun python my_script…...

OD C卷 - 中庸行者
中庸行者 (200) 给一个m*n的整数矩阵作为地图,矩阵数值为地形的高度,选择图中任意一点作为起点,向左右上下四个方向移动: 只能上坡、下坡,不能走相同高度的点;不允许连续上坡 或者连…...

最新CSS3横向菜单的实现
横向菜单 原始代码: <nav class"list1"><ul><li><a href"#">Shirts</a></li><li><a href"#">Pants</a></li><li><a href"#">Dresses</a>…...

(2024,LlamaGen,Llama,自回归下一token预测,模型扩展)自回归模型优于扩散:Llama 用于可扩展图像生成
Autoregressive Model Beats Diffusion: Llama for Scalable Image Generation 目录 0. 摘要 1. 引言 2. 自回归模型在图像生成中的应用 2.1 概述 2.2 图像 tokenizer 2.3 自回归模型生成图像 2.4 规模扩展 2.5 服务 3. 实验 5. 结论 0. 摘要 我们介绍 LlamaGen&…...

重新安装操作系统的软件都有哪些?
重新安装操作系统关键步骤 重新安装操作系统通常涉及到几个关键步骤:创建可启动媒体、备份现有数据、安装操作系统、以及系统初始化和配置。以下是一些常用工具和软件,它们可以帮助你完成这些步骤: 创建可启动媒体 Rufus:用于W…...

深圳水务展|2025深圳国际水务科技博览会
2025深圳国际水务科技博览会 展会主题: 新质生产力赋能水务产业高质量发展 展会时间:2025年7月24-26日 展会地点:深圳会展中心(福田) 主办单位: 广东省水利学会 深圳市水务学会 协办单位: 中国水利…...

OpenAI not returning a result?
题意:OpenAI 没有返回结果吗? 问题背景: Im trying to use the OpenAI beta but I cant seem to get a result. Im accessing the API via an NPM package (openai-api - npm). I have that setup and working but when I make a request th…...

[Windows]_[初级]_[GetVersionEx获取系统版本错误的原因]
场景 开发WTL/ATL/Win32程序时, 有时候需要获取系统版本号,以便判断用户在使用软件时的系统。在某一天突然发现获取的系统版本号是错的, 一直是版本号6.2.x,什么情况? 说明 如果软件没有针对Win8.1或Win10以上的系统做兼容处理,返回的是Wi…...

2024,Java开发在中国市场还有发展前景吗?
随着2024年的到来,Java作为一种经典而强大的编程语言,依然在中国的软件开发市场中扮演着重要角色。然而,许多人对Java的未来发展前景持有不同的看法。让我们来探讨一下当前情况和未来的走向。 Java程序员真的过剩了吗? 2023年, 各…...

gcc: string.c_str gcc-8.5的一个问题
https://en.cppreference.com/w/cpp/string/basic_string/c_str https://sourceforge.net/p/cppcheck/wiki/ListOfChecks/ common mistakes when using string::c_str()string的这个成员是返回c类型的一个字符数组指针。但是这个指针所对应的地址有赖于string对象的生命周期。所…...

一道笔试题 - 无重复字符的最长子串
老生常谈的一道题,常见并 文章目录 描述预期结果Java代码 描述 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。 预期结果 Java代码 import java.util.HashSet; import java.util.Set;public class Demo2 {public static void main(S…...

C#反射的NullReferenceException
背景 xml文件中有些元素的属性被删除,导致文件无法被读取(C#)。 调试之后发现,因为属性被删除,读进来会保持默认值null,在后续的反射中如果用这个null给字符串属性赋值,会抛异常。 另外发现前面…...