设计模式-六大设计原则详解(java 版)
设计模式-六大设计原则
- 单一职责原则
- 里氏替换原则
- 开闭原则
- 接口隔离原则
- 依赖倒置原则
- 迪米特法则
初次接触设计模式是在就读大学期间,或许那时候进入实验室有较好的导师及厉害点的同学,接的校外的商业代码都较为规范整洁,拗口的设计模式在学习中便没有下太大的精力,后续进入社会工作,发现尤其是新老系统在维护工程中重视出现各种奇怪的、难搞的bug,基本都是代码不遵循设计规范,不进行前期好的设计,出现了各种高耦合低内聚的垃圾产物。大改一番的成本是谁都不愿意承担的…由此学习设计模式是十分重要的事情,或许你有良好的代码习惯,但是这种习惯也只是知其然而不知其所以然的产物,故学习设计模式便是十分重要的东西。
设计模式的学习重在设计方面,所谓工欲善其事必先利其器,好的设计能够大大降低更多的生产、运维成本,也能提高自己的思维能力,故本单元主要是对设计模式在整体中进行一次阐述,这里便从设计模式的六大设计原则开始说起:
- 单一职责原则(Single Responsibility Principle);
- 开闭原则(Open Closed Principle);
- 里氏替换原则(Liskov Substitution Principle);
- 迪米特法则(Law of Demeter),又叫“最少知道法则”;
- 接口隔离原则(Interface Segregation Principle);
- 依赖倒置原则(Dependence Inversion Principle)。
把这 6 个原则的首字母(里氏替换原则和迪米特法则的首字母重复,只取一个)联合起来就是:SOLID(稳定的),其代表的含义也就是把这 6 个原则结合使用的好处:建立稳定、灵活、健壮的设计。
单一职责原则
单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
简而言之就是进行项目开发工程中,我们对类的设计及其定义上,一个类应该只承担一个职责,如何定义这个职责呢?每个人有不同的想法,我这里可以做一次统一的设计,即当如果这个类是对外的接口,那么接口路径公共命名上的区分即可作为一个类的划分。
eg:
- 简单的系统都有用户模块,我们对外提供接口一般是在管理层面来说是 /user/****[add/update/list/delete] ,对于其权限来说可以是 /user/role/ **** [add/update/list/delete],对于其登陆、登出、注册来说是 /{system}/*****[login/register/logout]等等,那么这三个方式便可以分成了三个controller类处理。
- 对于service进行分层处理的时候,确实有点仁者见仁智者见智的意味了,我们往往要进行一些联合查询等相关操作,在进行单一职责原则的同时也会涉及到职责重复的部分,则我个人在这认为方法以何为主则放到哪里即可,在进行接口设计的时候,需要考虑单一职责原则,譬如通用的update可以分为updateAll、updateNotNull,但是涉及到两种更新逻辑的时候,还是分开不放置一个方法较好。
- 对于中台系统的搭建,对于数据中台则进行丰富的数据处理,对于业务的处理则在业务中台进行组合即可,这里要区分单体架构中业务和数据混合过程。(一般这些不会处理的,系统就算是搞成了微服务,也只会增加维护成本,职责不进行很好的设计,容易出现高耦合低内聚的代码)
里氏替换原则
里氏替换原则的定义是:子类可以扩展父类的功能,但不能改变父类原有的功能。
简而言之,就是子类可以对父类的功能进行一些扩展而不能对父类的方法功能进行重写后对其作用进行变更。最为直观的体现就是Collection集合类,我们定义了List接口来确保其中的实现,对其进行实现时不管是ArrayList、LinkList还是其他的list,进行add操作时基本功能是将元素放到集合之中,其中因为其底层逻辑结构不同而使用不同的处理流程罢了。
eg:public class test{public void test(){List arrayList = new ArrayList();List linkedList = new LinkedList();arrayList.add(new Object());linkedList.add(new Object());}
违法原则示例一:使用基类没有的异常:
public class Person{public void eatFood(Object food){if(food == null) throw new NoFoodException("there no food exception");// eat}public class Father extend Person{@Overridepublic void eatFood(Object food){//出现基类不存在的异常if(food == null) throw new NeedFoodException("there no food exception");//eat}}}
违反原则示例二:擅自变更基类方法的处理逻辑
public class Person{public void eatFood(Object food){if(food == null) throw new NoFoodException("there no food exception");// eat}public class Father extend Person{@Overridepublic void eatFood(Object food){if(food == null) throw new NoFoodException("there no food exception");//eat}}public class son extend Person{private Object toy;@Overridepublic void eatFood(Object food){// 违反了里氏替换原则if(toy== null) throw new NoToyException("there no toy exception ,son was crying");// eat}} }
开闭原则
开闭原则的定义是:子类可以扩展父类的功能,但不能改变父类原有的功能。
简而言之就是进行父类功能扩展的时候,我们不能够直接去修改父类的功能。具体落在实际如何处理呢?简而言之则是在进行设计时,譬如定义一个统一的操作接口,操作接口根据其实现子类进行实现其功能,这样我们扩展则只需要在一个新类进行实现即可。故这就需要我们在前期设计之中进行扩展设计考量了,后续进行处理的时候发现不符合开闭原则的话,需要进行架构的重新设计。
如何使用开闭原则
抽象约束
第一,通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;(继续参考集合)
第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;(使用List list 而不是ArrayList list进行定义)
第三,抽象层尽量保持稳定,一旦确定即不允许修改。元数据(metadata)控制模块行为
元数据就是用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
Spring容器就是一个典型的元数据控制模块行为的例子,其中达到极致的就是控制反转(Inversion of Control)制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程中指定了所有人员都必须遵守的约定,对项目来说,约定优于配置。
复制封装变化
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口或者抽象类中;
第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
最好的示例就是集合类,Collection集合,List接口进行规范,慢慢演变出ArrayList LinkedList CopyOnRightArrayList…等
接口隔离原则
定义:客户端不应该依赖它不需要的接口,多数客户端特定接口是比单一总接口更好的。这里客户端不只是说用户使用的,比如说我们定义一个接口,那么对应的实现类便是接口的客户端。接口隔离原则其实也表明了面向对象的规范性。
eg:
反例public interface IPerson{public void eatFood();public void drinkWriter();public void sleep();public void goSchool(); }public interface Father implement IPerson{.......@Overridepublic void goSchool(){// 多余方法,违反了接口隔离原则 }}
修改后:对业务进行接口上的拆分
public interface IPerson{public void eatFood();public void drinkWriter();public void sleep();}public interface IStudent{public void goSchool(); }public class Father implement IPerson{....... }public class StudentFather extend Father implement IStudent{....... }public class Son implement IPerson,IStudent{....... }
依赖倒置原则
面向接口编程,不要面向实现编程。高层模块不应该依赖低层模块,二者都应该依赖其抽象。简而言之,就是父类(基类)不应该对其子类产生依赖,也就是我们在进行类扩展增强的时候,对于基类来说不管如何增强,其不应该被更改。这里做一个对比,里氏替换原则教会了我们不要篡改父类的逻辑,开闭原则教会了我们不要使用非类功能的public方法(使用接口约束,对功能增强可以考虑使用接口隔离原则),依赖倒置原则教会了我们基类不要依赖子类。
示例:
这里不写代码了吧,和之前的都差不多,如果是在基类进行条件适配的时候,不要再基类写if else语句,可以通过接口,通过type(譬如在Abstract 基类声明一个 abstract方法 getType() 处理等都可以)
迪米特法则
迪米特法则也就是最少知道原则,对于这个法则,其目的就是为了降低代码的耦合性来提高内聚,对于最少知道法则的实际使用上,基本上有两个原则遵守就可以满足,即:1. 只和直接朋友进行交流 2. 尽量减少不必要的交流(直达目的)
eg:
1.只和直接朋友交流:这个原则在管理学的体现就是不越级进行工作安排
场景:譬如我们进行房租租赁工作,一般有房子,房东,中介、房客四个角色进行参与,实际过程中我们可以找中介帮忙看房子,也可以自己找房子(这里既可以找到中介,也会找到房东直租)这里如果我们按照业务场景进行处理。
分析:进行租赁活动的时候既可以找中介,也可以直接找房东;如果按照这个思路处理就违反了迪米特法则。虽然业务确实可以直接找房东租赁,但是要知道房东在这里既担任了房主的角色,也担任着房屋出租者的角色,而中介仅仅只是扮演了房屋出租者的角色。故进行系统分析的时候需要进行权限的界定,外卖也算如此,需要有用户、食物、商家、骑手。商家自送则是将商家即承担了食品售卖者的角色,也承担了配送人员的角色。用户自取则是用户承担了购买者的角色,也承担了配送者的角色,软件系统需要对此分析到位。public interface IShop{public Food earnFood(); }/**** 商家制作食物***/ public class Shop implement IShop{public IShop makeFood(){return this;}public Food earnFood(){// to doreturn food;}}public interface ISendLunchPerson{public void getFood(IShop shop); }/****外卖员进行取餐和送餐--这里甚至可以将外卖员封装成接口,上家和平台各种实现***/ public class sendLunchPerson implement ISendLunchPerson{public void getFood(IShop shop){shop.makeFood()}public Food sendFood(...){// 核对用户信息并给出对应食物} }/****购买者只需要从外卖员手里获取食物***/ public class Buyer{public Food getFood(ISendLunchPerson sender){return sender.sendFood(...)}}
- 尽量减少和朋友不必要的交流:这里旨在不需要管自己不归自己管的事情,譬如外卖员取餐只需要取餐,不需要管商家进行制作。生活中确实有帮商家炒菜配餐的外卖小哥,但是这个行为毕竟并非系统的约束行为,而且他帮人炒菜的同时也会降级自己的配送效率。
public interface IShop{public Food makeFood(); }/**** 商家制作食物***/ public class Shop implement IShop{public IShopbuyIngredients(...){// buy Ingredientsreturn this;} public IShopwash(){// wash Ingredients}public IShop makeFood(){// to doreturn food;}public Food earnFood(){return food;}// 这里包含了订单逻辑,为了防止把一个东西搞得太复杂,只是简单写在这里了public Food work(){// 1. 购买食材this.buyIngredients(...);// 2. 清洗食材 this.wash();// 3. 制作食物makeFood();}}public interface ISendLunchPerson{public void getFood(IShop shop); }/****外卖员进行取餐和送餐--这里甚至可以将外卖员封装成接口,上家和平台各种实现***/ public class sendLunchPerson implement ISendLunchPerson{// 反例--骑手不需要管食物制作过程public void getFoodWarn(IShop shop){shop.buyIngredients().wash().earnFood()}// 正例,骑手只需要催商家出餐即可public Food getFoodTrue(IShop shop){shop.work();} }
基本上这几个原则使用的比较广泛,如果上文有不当之处还望指出,一起学习,共同成长。
相关文章:

设计模式-六大设计原则详解(java 版)
设计模式-六大设计原则单一职责原则里氏替换原则开闭原则接口隔离原则依赖倒置原则迪米特法则初次接触设计模式是在就读大学期间,或许那时候进入实验室有较好的导师及厉害点的同学,接的校外的商业代码都较为规范整洁,拗口的设计模式在学习中便…...

Linux下Nginx安装使用
一、下载解压nginx # 进入要放安装包的目录 cd /opt/software # 下载安装包 wget https://nginx.org/download/nginx-1.20.2.tar.gz # 解压缩 tar -zxvf nginx-1.20.2.tar.gz -C /opt/modules # 进入解压后的目录 cd /opt/modules/nginx-1.20.2/二、安装nginx 1、安装编译器 …...

推动汽车业务向前发展的混合云战略:汽车数据解决方案
推动汽车业务向前发展的混合云战略 无论您的数据是位于内部还是公有云中,与 NetApp 合作都可以帮助您的汽车业务充分发挥它们的潜能 前有混合动力汽车,后有混合云 通过精心考虑的混合多云战略,汽车制造商可以根据不同需求和环境移动应用程序…...

Boosting三巨头:XGBoost、LightGBM和CatBoost(发展、原理、区别和联系,附代码和案例)
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...

设计模式~模板方法模式(Template method)-10
目录 (1)优点: (2)缺点: (3)使用场景: (4)注意事项: (5)应用实例: (6)Servlet Api & Spring 中的应用 代码 (钩子函数)在模板模式(Template Pattern)中,一个抽象类公开定…...

【WebSocket】在SSM项目中配置websocket
在SSM项目中配置websocket 最近在ssm项目中配置了websocket,踩了很多坑,来分享一下 本文暂不提供发送消息等内容的代码逻辑(后续也许会补充),如果你直接复制这类可能会对配置造成更大的麻烦(博主就是复制…...

node-red中创建自定义节点 JavaScript 文件API编写详解
前言 在node-red中如果你没有找到自己需要的节点时,那么你可以自定义一个节点来满足自己的需求。之前的文章中,我有简单介绍过如何创建一个节点,并以转换大小写来举例。例子虽然简单,但可以让大家了解创建自定义节点的步骤以及一个节点的组成部分。那么本篇将会聚焦在自定…...

华为OD机试 - 寻找路径 or 数组二叉树(C 语言解题)【独家】
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 使用说明本期题目:寻找路径…...

YOLOv7、YOLOv5改进之打印热力图可视化:适用于自定义模型,丰富实验数据
💡该教程为改进YOLO高阶指南,属于《芒果书》📚系列,包含大量的原创改进方式🚀 💡更多改进内容📚可以点击查看:YOLOv5改进、YOLOv7改进、YOLOv8改进、YOLOX改进原创目录 | 唐宇迪老师联袂推荐🏆 💡🚀🚀🚀内含改进源代码 按步骤操作运行改进后的代码即可�…...

【Java代码与架构之完美优化】篇1:代码质量优化通用准则
工欲善其事,必先利其器 1. 避免使用空块 常见空块一般有以下几种情况: 多余的分号:if(xxx);多余的大括号:if(xxx){这里没有内容}空finall语句:try{...}catch(...){...}finally{这里没有内容} 空块的存在࿰…...

Linux进程间通信详解(最全)
进程间的五种通信方式介绍 进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、Socket(套接字&a…...

ROS 摄像头的使用
参考: youtubeArticulated Robotics 作者Josh Newans博客 建议: 这个只是我的看法,强烈建议看原视频或博客 png:无损压缩 jpeg:有损压缩 Driver Node 负责连接硬件设备,读取摄像头数据"ima…...

VR全景云展厅,实现7*24小时的线上宣传能力!
数字化时代,虚拟现实技术的应用越来越广泛,其中VR全景云展厅是一种新兴的展示方式,具有独特的展示优势。随着VR技术的不断发展,越来越多的企业、机构和个人开始使用VR全景云展厅来展示他们的产品和服务。一、展厅营销痛点1、实地到…...

RK3568平台开发系列讲解(显示篇) DRM显示系统组成分析
🚀返回专栏总目录 文章目录 一、DRM Framebuffer二、CRTC三、Planes四、Encoder五、Connector沉淀、分享、成长,让自己和他人都能有所收获!😄 📢让我们分析一下绿框中的五个部件,以及他们的联动。 一、DRM Framebuffer 与 framebuffer一样,是一片存放图像的内存区域,…...

WPF DataGrid控件的使用 使用列模板来进行数据格式的美化
<Grid><Grid.RowDefinitions><RowDefinition Height"0.1*" /><RowDefinition /></Grid.RowDefinitions><Button Content"刷新"FontSize"25"Command"{Binding ExecuteRefreshCommand}" /><Dat…...

elasticsearch自定义企业词典
我们中文分词用的是ik,但是ik只是对基本的中文词进行了分词,而对于企业或者人名没有进行分词。比如,我搜索中国平安,那么ik只能分成中国、平安如果这样,这肯定是不行滴!接下来,俺就教你…...

【AcWing】学了一坤时才明白的一道题
🎆音乐分享 (点击链接可以听哦) The Right Path - Thomas Greenberg 这道题小吉花了一坤时才弄明白,虽然花的时间有点长 但是至少是明白了 😎😎😎😎😎😎 …...

ES6的export和import
ES6中的模块加载ES6 模块是编译时加载,编译时就能确定模块的依赖关系,以及输入和输出的变量,相比于CommonJS 和 AMD 模块都只能在运行时确定输入输出变量的加载效率要高。严格模式ES6 的模块自动采用严格模式,不管你有没有在模块头…...

ASEMI高压MOS管20N60参数,20N60尺寸,20N60体积
编辑-Z ASEMI高压MOS管20N60参数: 型号:20N60 漏极-源极电压(VDS):600V 栅源电压(VGS):30V 漏极电流(ID):20A 功耗(PDÿ…...

【备战面试】TCP的三次握手与四次挥手
本篇总结的是计算机网络知识相关的面试题,后续也会更新其他相关内容 文章目录1、TCP头部结构2、三次握手3、四次挥手4、为什么TCP连接的时候是三次?两次是否可以?5、为什么TCP连接的时候是三次,关闭的时候却是四次?6、…...

【模板进阶】
目录 1. 非类型模板参数 2. 模板的特化 2.1 概念 2.2 函数模板特化 2.3 类模板特化 2.3.1 全特化 3 模板分离编译 3.1 什么是分离编译 3.2 模板的分离编译 4. 模板总结 有需要的老哥可以先看看模板的介绍:http://t.csdn.cn/2TkUYhttp://t.csdn.cn/2TkUY 1. …...

Tech Talk | 电致变色技术带来的智能AR体验
2023年2月27日,小米在2023MWC世界移动通信大会上,正式发布了小米无线AR眼镜探索版。这款产品搭载了创新的数控电致变色镜片,能适应不同光环境,遮光模式可以在观影时更沉浸,通透模式又能让AR虚实结合的体验更生动。“ 本…...

ACWING蓝桥杯每日一题python(持续更新
ACWing蓝桥杯每日一题 一直没时间去总结算法,终于有空可以总结一下刷的acwing了,因为没时间所以最近只刷了ACWING的蓝桥杯每日一题。。。真是该死 1.截断数组 首先我们要知道,如果sum(a)不能被3整除或者len(a) < 3 ,那么他肯…...

【Linux】进程状态(阻塞、挂起、僵尸进程)
文章目录1 阻塞与挂起1.1 阻塞1.2 挂起2 进程状态前言: 当我们在Windows下双击运行一个程序,或是在Linux下通过 ./ 加载运行一个程序,是否就代表对应的进程就一直处在运行状态呢?其实不然,一个进程有许多不同的状态。当…...

规约第二章
文章目录有限域的定义Definition of Finite Field单位元运算举例素数域群阿贝尔群阿贝尔循环群循环子群阿贝尔循环群且阶是素数的有限域的定义Definition of Finite Field 单位元 这里一般只需要记住2个0,1 。0是加法的单位元,1是乘法的单位元。以及逆…...

2019年MathorCup数学建模C题汽配件制造业中的生产排程问题解题全过程文档及程序
2019年第九届MathorCup高校数学建模挑战赛 C题 汽配件制造业中的生产排程问题 原题再现: 整体求解过程概述(摘要) 随着市场竞争日趋激烈,企业开始更加注重低费高效,因此生产排程问题成为众多制造企业关注的热点之一。其中,制造行…...

ARM uboot 的移植3 -从 uboot 官方标准uboot开始移植
一、选择合适的官方原版 uboot 1、官方原版 uboot 的版本 (1) 版本号。刚开始是 1.3.4 样式,后来变成 2009.08 样式。 (2) 新版和旧版的差别。uboot 的架构很早就定下来了,然后里面普遍公用的东西(common 目录下、drivers 目录下、fs 目录…...

华为OD机试 - 快递货车(C 语言解题)【独家】
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 使用说明本期题目:快递货车…...

连接微信群、Slack 和 GitHub:社区开放沟通的基础设施搭建
NebulaGraph 社区如何构建工具让 Slack、WeChat 中宝贵的群聊讨论同步到公共领域。 要开放,不要封闭 在开源社区中,开放的一个重要意义是社区内的沟通、讨论应该是透明、包容并且方便所有成员访问的。这意味着社区中的任何人都应该能够参与讨论和决策过…...

数据中台架构体系理解
目前,大部分企业更倾向于数据集中采集、存储,并应用分层建设。这种方式一方面有利于应用系统的快速部署,另一方面也保证了数据的集中管理与运营,体现数据的资产、资源属性。 数据中台的出现弥补了数据开发和应用开发之间由于开发…...