设计模式-六大设计原则详解(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、…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...

Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...