当前位置: 首页 > news >正文

架构-设计原则

1、面向对象的SOLID

1.1 概述

        SOLID是5个设计原则开头字母的缩写,其本身就有“稳定的”的意思,寓意是“遵从SOLID原则可以建立稳定、灵活、健壮的系统”。5个原则分别如下:

  • Single Responsibility Principle(SRP):单一职责原则。
    • 一个类,只做一件事,并把这件事做好,其只有一个引起它变化的原因。
  • Open Close Principle(OCP):开闭原则。
  • Liskov Substitution Principle(LSP):里氏替换原则。
  • Interface Segregation Principle(ISP):接口隔离原则。
  • Dependency Inversion Principle(DIP):依赖倒置原则。

        SOLID最早由Robert C. Martin在2000年的论文“Design Principles and Design Patterns”中引入。在2004年前后,Michael Feathers提醒Martin可以调整一下这些原则的顺序,那么它们的首字母的缩写就可以排列成SOLID。这个新名字的确促进了SOLID思想的传播,再一次证明了命名
的重要性。

1.2 关系

        SOLID原则之间并不是相互孤立的,彼此间存在着一定关联,一个原则可以是另一个原则的加强或基础;违反其中的某一个原则,可能同时违反了其他原则。

  • 设计目标:开闭原则和里氏代换原则。
  • 设计方法:单一职责原则、接口分隔原则和依赖倒置原则

1.3 职责单一原则(SRP-Single Responsibility Principle)

        任何一个软件模块中,应该有且只有一个被修改的原因。

        SRP要求每个软件模块职责要单一,衡量标准是模块是否只有一个被修改的原因。职责越单一,被修改的原因就越少,模块的内聚性(Cohesion)就越高,被复用的可能性就越大,也更容易被理解。

示例

非SRP

        例如,有一个Rectangle类(如图1-1所示),该类包含两个方法,一个方法用于把矩形绘制在屏幕上,另一个方法用于计算矩形的面积。

                                                                1-1

        按照SRP的定义,Rectangle类是违反了SRP原则的。因为Rectangle类具有至少两个职责,不管是改变绘制逻辑,还是面积计算逻辑,都要改动Rectangle类。

SRP-贫血

        为了遵从SRP原则,我们需要把两个职责分离出来,放在两个不同的类中,这样就可以互相不影响了。最简单的解决方案是将数据与函数分离,如图1-2所示。设计两个用来做逻辑处理的类,每个类只包含与之相关的函数代码,互相不可见,这样就不存在互相依赖的情况了。

                                                                 1-2

SRP-充血

        1-2的方式有点“贫血”模式的味道。我们也可以采用面向对象的做法,把重要的业务逻辑与数据放在一起,然后用Rectangle类来调用其他没那么重要的函数,如图1-3所示。

                                                               1-3 

        另外,SRP不仅在模块和类级别适用,在函数级别同样适用。

函数单一职责

        下面是一个给员工发工资的简单方法

public void pay(List<Employee> employees){for (Employee e: employees){if(e.isPayDay()){Money pay = e.calculatePay();e.deliverPay(pay);}}
}

做了3件事情

  • 遍历所有雇员
  • 检查是否该发工资
  • 支付薪水。

按照SRP的原则,以下面的方式改写更好

//遍历所有雇员
public void pay(List<Employee> employees) {for (Employee e : employees) {payIfNecessary(e);}
}//检查是否该发工资
private void payIfNecessary(Employee e) {if (e.isPayDay()) {calculateAndDeliverPay(e);}
}// 支付薪水
private void calculateAndDeliverPay(Employee e) {Money pay = e.calculatePay();e.deliverPay(pay);
}

        虽然原来的方法并不复杂,但按照SRP分解后的代码显然更加容易让人读懂,这种拆分是有积极意义的。基本上,遵循SRP的函数都不会太长,再配上合理的命名,就不难得到我们想要的短小的函数。

1.4 开闭原则(OCP-Open Close Principle)

        其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

  • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。这样可以保证稳定性和延续性。

        OCP 建议我们应该对系统进行重构,那么以后再进行同样改动,只需添加新代码而不必改动已正常运行的代码。

        在很多方面,OCP 都是面向对象设计的核心所在,可增强灵活性、可重用性、可维护性等。

        OCP 的关键是抽象,其背后的主要机制是抽象和多态。模块应该依赖于一个固定的抽象体,因此,它对于更改可以是关闭的,同时,通过从这个抽象体派生,也可以扩展此模块的行为。

        实际上,很多的设计模式都以达到OCP目标为目的。例如,装饰者模式,可以在不改变被装饰对象的情况下,通过包装(Wrap)一个新类来扩展功能;策略模式,通过制定一个策略接口,让不同的策略实现成为可能;适配器模式,在不改变原有类的基础上,让其适配(Adapt)新的功能;观察者模式,可以灵活地添加或删除观察者(Listener)来扩展系统的功能。

注意
        当然,要想做到绝对地“不修改”是比较理想主义的。因为业务是不确定的,没有谁可以预测到所有的扩展点,因此这里需要一定的权衡,如果提前做过多的“大设计”,可能会犯YAGNI(You Ain’t Gonna NeedIt)的错误。

1.5 里氏替换原则(LSP-Liskov Substitution Principle)

        软件工程大师罗伯特·马丁(Robert C. Martin)把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for their base types”。也就是,子类必须能够替换成它们的基类。即子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作

        另外,不应该在代码中出现 if/else 之类对子类类型进行判断的条件。里氏替换原则 LSP 是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。

注意:
一般而言,无论模块是多么的“封闭“,都会存在一些无法对之封闭的变化,没有对于所有的情况都贴切的模型。所以,必须有策略地对待这个问题。
设计人员必须对他所设计的模块应该对哪种变化封闭做出选择,必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。但大多数情况,猜测都是错误的。后续即使不使用这些抽象也必须去支持和维护它们,这不是一件好事,所以,通常我们会一直等到确实需要那些抽象时再去进行抽象

1.6 接口隔离原则(ISP-Interface Segregation Principle ) 

        不能强迫用户去依赖那些他们不使用的接口。换句话说就是使用多个专门的接口比使用单一的总接口要好。

        举个例子,我们对电脑有不同的使用方式,比如:写作、通讯、看电影、打游戏、上网、编程、计算和数据存储等。如果我们把这些功能都声明在电脑的抽象类里面,那么,我们的上网本、PC 机、服务器和笔记本的实现类都要实现所有的这些接口,这就显得太复杂了。所以,我们可以把这些功能接口隔离开来,如工作学习接口、编程开发接口、上网娱乐接口、计算和数据服务接口,这样,我们的不同功能的电脑就可以有所选择地继承这些接口。

        同时,小接口更容易实现,提升了灵活性和重用的可能性。由于很少的类共享这些接口,相应接口的变化而需要变化的类数量就会降低,增加了鲁棒性。

1.7 依赖倒置原则(DIS-Dependency Inversion Principle)

        高层模块不应该依赖于底层模块(高层与低层是相对而言,也就是调用者与被调用者的关系),二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

        举个例子:墙面的开关不应该依赖于电灯的开关实现,而是应该依赖于一个抽象的开关的标准接口

        这样,当我们扩展程序的时候,开关同样可以控制其它不同的灯,甚至不同的电器。也就是说,电灯和其它电器继承并实现我们的标准开关接口,而开关厂商就可以不需要关于其要控制什么样的设备,只需要关心那个标准的开关标准。这就是依赖倒置原则。

2、其他

2.1 DRY 原则(Don’t Repeat Yourself)

        DRY原则可理解为不要写重复的代码。简单来讲,写代码的时候,如果出现雷同片段,就要想办法把他们提取出来,成为一段独立的代码。

        DRY 是一个最简单的法则,也是最容易被理解的,但它也可能是最难被应用的(因为要做到这样,我们需要在泛型设计上做相当的努力,这并不是一件容易的事)。它意味着,当在两个或多个地方发现一些相似代码的时候,我们需要把它们的共性抽象出来形成一个唯一的新方法,并且改变现有地方的代码让它们以一些合适的参数调用这个新的方法。

        代码重复有三种典型情况

  • 实现逻辑重复
    • 重复的代码被敲了两遍或者简单复制粘贴一下代码。
  • 功能语义重复
    • 功能重复。代码可能不同,但是实现的功能是相同的。
      • 例如:两个同事写的同一个工具方法。
  • 代码执行重复。
    • 例如,多个地方对同样的参数做参数校验。

2.2 YAGNI原则(You Ain’t Gonna Need It)

        你是否有个这样的经历,臆想某个功能以后可能会用到,然后就顺手把它实现了,实际到了后面并没用上,反而造成了代码冗余。

        这个原则只考虑和设计必须的功能,避免过度设计。只实现目前需要的功能,在以后你需要更多功能时,可以再进行添加。如无必要,勿增复杂性。软件开发是一场 取舍(trade-off)的博弈。

        因此,我们不能闭门臆想需要的功能,但是在架构上又要洞察趋势。

2.3 Rule of Three

        Rule  of  Three也被称为“三次原则”,是指当某个功能第三次出现时,就有必要进行“抽象化”了。这也是软件大师Martin  Fowler在《重构》一书中提出的思想。
        三次原则指导我们可以通过以下步骤来写代码。

  1. 第一次用到某个功能时,写一个特定的解决方法。
  2. 第二次又用到的时候,复制上一次的代码。
  3. 第三次出现的时候,才着手“抽象化”,写出通用的解决方法。

        这3个步骤是对DRY原则和YAGNI原则的折中,是代码冗余和开发成本的平衡点。同时也提醒我们反思,是否做了很多无用的超前设计、代码是否开始出现冗余、是否要重新设计。软件设计本身就是一个平衡的艺术,我们既反对过度设计(Over  Design),也绝对不赞成无设计(No Design)。

2.4 KISS 原则(Keep It Simple, Stupid)

        保持每件事情都尽可能的简单,用最简单的解决方案来解决问题。

        KISS 原则在设计上可能最被推崇,在家装设计、界面设计和操作设计上,复杂的东西越来越被众人所鄙视了,而简单的东西越来越被人所认可。

  • 宜家简约、高效的家居设计和生产思路;
  • 微软“所见即所得”的理念;
  • 谷歌简约、直接的商业风格,无一例外地遵循了“KISS”原则。
  • 而苹果公司的 iPhone 和 iPad 将这个原则实践到了极至。 也正是“KISS”原则,成就了这些看似神奇的商业经典。

2.5 好莱坞原则(Hollywood Principle)

        好莱坞原则就是一句话:“don’t call us, we’ll call you.”。意思是,好莱坞的经纪人不希望你去联系他们,而是他们会在需要的时候来联系你。也就是说,所有的组件都是被动的,所有的组件初始化和调用都由容器负责。

        简单来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。

        这也就是所谓“控制反转”的概念所在:

  1. 不创建对象,而是描述创建对象的方式。
  2. 在代码中,对象与服务没有直接联系,而是容器负责将这些联系在一起。控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。

        好莱坞原则就是IoC(Inversion of Control) 或DI(Dependency Injection)]的基础原则。

3、总结

  • 原则是指导我们写出更好的代码,但不要教条,任何东西都是适用场景的。
  • 原则不是目的,实现业务逻辑才是目的,不要本末倒置。
  • 原则是降低复杂度,不是增加复杂度。

相关文章:

架构-设计原则

1、面向对象的SOLID 1.1 概述 SOLID是5个设计原则开头字母的缩写&#xff0c;其本身就有“稳定的”的意思&#xff0c;寓意是“遵从SOLID原则可以建立稳定、灵活、健壮的系统”。5个原则分别如下&#xff1a; Single Responsibility Principle&#xff08;SRP&#xff09;&am…...

在 Python 3 中释放 LightGBM 的力量:您的机器学习大师之路

机器学习是 Python 占据主导地位的领域,它一直在给全球各行各业带来革命性的变化。要在这个不断变化的环境中脱颖而出,掌握正确的工具是关键。LightGBM 就是这样一个工具,它是一个强大且快速的梯度提升框架。在这份综合指南中,我们将通过实际示例和示例数据集从基础知识到高…...

Spring学习笔记(2)

Spring学习笔记&#xff08;2&#xff09; 一、Spring配置非定义Bean1.1 DruidDataSource1.2、Connection1.3、Date1.4、SqlSessionFactory 二、Bean实例化的基本流程2.1 BeanDefinition2.2 单例池和流程总结 三、Spring的bean工厂后处理器3.1 bean工厂后处理器入门3.2、注册Be…...

cmd使用ssh连接Linux脚本

前言 在开发过程中&#xff0c;由于MobaXterm&#xff0c;我不知道怎么分页&#xff08;不是屏内分页&#xff09;&#xff0c;用crtlTab&#xff0c;用起来不习惯&#xff0c;主要是MobaXterm连接了多个服务器&#xff0c;切换起来很麻烦。我是比较习惯使用altTab&#xff0c…...

Python万圣节蝙蝠

目录 系列文章 前言 蝙蝠 程序设计 程序分析 运行结果 尾声 系列文章 序号文章目录直达链接1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want5…...

TCP流套接字编程

文章目录 前言TCP 和 UDP 的特点对比TcpEchoServer 服务端实现1. 创建 ServerSocket 类实现通信双方建立连接2. 取出建立的连接实现双方通信3. 服务端业务逻辑实现关闭资源服务端整体代码 TcpEchoClient 客户端实现1. 创建出 Socket 对象来与服务端实现通信2. 实现客户端的主要…...

Python迭代器创建与使用:从入门到精通

一、可迭代对象 1、 什么是可迭代对象&#xff1f; 表示可以逐一迭代或者遍历的对象&#xff0c;序列&#xff1a;列表、元组、集合、字符串。非序列&#xff1a;字典、文件。自定义对象&#xff1a;实现了__iter__()方法的对象&#xff1b;实现了使用整数索引的 getitem()方…...

mac虚拟机安装homebrew时的问题

安装了mac虚拟机&#xff0c;结果在需要通过“brew install svn”安装svn时&#xff0c;才注意到没有下载安装homebrew。 于是便想着先安装homebrew&#xff0c;网上查的教程大多是通过类似以下命令 “ruby <(curl -fsSkL raw.github.com/mxcl/homebrew/go)” 但是都会出现…...

学信息系统项目管理师第4版系列32_信息技术发展

1. 大型信息系统 1.1. 大型信息系统是指以信息技术和通信技术为支撑&#xff0c;规模庞大&#xff0c;分布广阔&#xff0c;采用多级 网络结构&#xff0c;跨越多个安全域&#xff1b;处理海量的&#xff0c;复杂且形式多样的数据&#xff0c;提供多种类型应用 的大系统 1.1.…...

Vue3 + Nodejs 实战 ,文件上传项目--大文件分片上传+断点续传

目录 1.大文件上传的场景 2.前端实现 2.1 对文件进行分片 2.2 生成hash值&#xff08;唯一标识&#xff09; 2.3 发送上传文件请求 3.后端实现 3.1 接收分片数据临时存储 3.2 合并分片 4.完成段点续传 4.1修改后端 4.2 修改前端 5.测试 博客主页&#xff1a;専心_前端…...

宏(预编译)详解

目录 一、程序的编译环境 二、运行环境 三、预编译详解 3.1预定义符号 3.2.1 #define 定义标识符 3.2.2 #define 定义宏 3.2.3#define替换规则 3.2.4 #和## 2)##的作用&#xff1a; 3.2.5宏和函数的对比 3.2.6宏的命名约定和#undef指令 一、命名约定&#xff1a; …...

hue实现对hiveserver2 的负载均衡

如果你使用的是CDH集群那就很是方便的 在Cloudera Manager中&#xff0c;进入HDFS Service 进入Instances标签页面&#xff0c;点击Add Role Instances按钮&#xff0c;如下图所示 点击Continue按钮&#xff0c;如下图所示 返回Instances页面&#xff0c;选择HttpFS角色…...

SkyWalking 告警规则配置说明

Skywalking告警功能是在6.x版本新增的,其核心由一组规则驱动,这些规则定义在config/alarm-settings.yml 文件中。告警规则定义分为两部分: 1、告警规则:它们定义了应该如何触发度量警报,应该考虑什么条件 2、webhook(网络钩子):定义当告警触发时,哪些服务终端需要被…...

HTML 表单笔记/练习

表单 概述 表单用于收集用户信息&#xff0c;用户填写表单提交到服务器 一般传参方式&#xff1a; GETPOSTCookie 传参要素 传参方式 GETPOST 参数的名字目标页面内容的数据类型&#xff08;只有在上传文件的时候&#xff09; 提示信息 一个表单中通常还包含一些说明性的文…...

关于Java Integer和Long使用equals直接比较

Integer和Long不能直接equals比较会返回False Long.class源码 public boolean equals(Object obj) {if (obj instanceof Long) {return this.value (Long)obj;} else {return false;} }Integer.class源码 public boolean equals(Object obj) {if (obj instanceof Integer) {…...

nodejs+vue衣服穿搭推荐系统-计算机毕业设计

模块包括主界面&#xff0c;系统首页、个人中心、用户管理、风格标签管理、衣服分类管理、衣服穿搭管理、服装信息管理、我的搭配管理、用户反馈、系统管理等进行相应的操作。无论是日常生活&#xff0c;还是特定场景&#xff0c;诸如面试、约会等&#xff0c;人们都有展现自我…...

Java并发面试题:(七)ThreadLocal原理和内存泄漏

ThreadLocal是什么&#xff1f; ThreadLocal是线程本地存储机制&#xff0c;可以将数据缓存在线程内部。ThreadLocal存储的变量在线程内共享的&#xff0c;在线程间又是隔离的。 ThreadLocal实现原理&#xff1f; ThreadLocal的底层是ThreadLocalMap&#xff0c;每个Thread都…...

香港服务器在国内访问太慢怎么能提高?

​  一直以来&#xff0c;全球化业务需求的增长是跟随着蓬勃向上的互联网而发展的。有了网络&#xff0c;海外贸易就在鼠标的轻点中完成。而IDC市场中的香港服务器也因为免备案政策的特性&#xff0c;开始逐渐成为企业想要跨越地域壁垒而考虑的对象。但在使用过程中&#xff…...

使用Proxyman抓取Android的https请求

使用Proxyman抓取Android的https请求 有时&#xff0c;您可能需要测试您的移动应用程序并检查与其关联的所有网络请求。在网络上&#xff0c;此任务非常简单&#xff0c;只需按Ctrl Shift I打开开发人员工具即可。从那里&#xff0c;您可以导航到网络选项卡并检查与网页相关的…...

基础MySQL的语法练习

基础MySQL的语法练习 create table DEPT(DEPTNO int(2) not null,DNAME VARCHAR(14),LOC VARCHAR(13) );alter table DEPTadd constraint PK_DEPT primary key (DEPTNO);create table EMP (EMPNO int(4) primary key,ENAME VARCHAR(10),JOB VARCHAR(9),MGR …...

RAID和LVM配置指南:创建、扩容和管理RAID设备和逻辑卷的方法

文章目录 1. 简介1.1 什么是RAID和LVM1.2 RAID和LVM的作用和优势 2. RAID配置命令&#xff1a;mdadm2.1 安装mdadm2.2 创建RAID设备2.2.1 RAID 02.2.2 RAID 12.2.3 RAID 52.2.4 RAID 10 2.3 添加磁盘到RAID设备2.4 删除磁盘从RAID设备2.5 查看和管理RAID设备2.6 故障处理与恢复…...

MapStruct使用方法

一、用途 1.1 优势 与动态映射框架相比&#xff0c;MapStruct 具有以下优势&#xff1a; &#xff08;1&#xff09;通过使用普通方法getter、setter调用&#xff0c;而不是反射来快速执行&#xff0c;效率很高。 &#xff08;2&#xff09;编译时类型安全&#xff1a;只能映…...

【LeetCode】50. Pow(x, n)

1 问题 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c; x n x^n xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2.10000, n 3 输出&a…...

vue2技能树(2)-模板语法、vue的工具链、渐进式框架

目录 Vue2技能树Vue 2 简单的模板语法详解插值绑定属性指令v-if 和 v-elsev-forv-on 计算属性过滤器插槽 Vue 2 生态系统详解1. Vue Router2. Vuex3. Vue CLI4. Axios5. Vue Devtools6. Element UI、Vuetify、Quasar等UI框架7. Nuxt.js8. Vue Apollo、Vue Router、Vue Fire等插…...

【Git系列教程-目录大纲】

《Git系列教程-目录大纲》 完完全全从零开始深入学习Git&#xff0c;教程配图200张&#xff0c;其中包括包括Git基本命令、命令原理、Git底层命令、分支、分支的原理、Git代码冲突原理/解决、tag标签、Git存储状态、分支合并原理、典型合并、快进合并、同轴开发、非同轴开发、…...

【高等数学】导数与微分

文章目录 1、导数的概念1.1、引例1.1.1、变速直线运动瞬时速度1.1.2、曲线的切线 1.2、导数的定义1.3、证明常用导数1.4、导数的几何意义1.5、可导与连续的关系 2、函数的求导法则2.1、函数的和、差、积、商的求导法则2.2、反函数的求导法则2.3、复合函数的求导法则2.4、基本初…...

springboot之quartz动态可控定时任务

Quartz Quartz是一个开源的任务调度框架&#xff0c;可以用来实现定时任务的调度&#xff0c;如定时发送邮件、定时备份数据等。Quartz具有很高的可靠性和灵活性&#xff0c;支持集群部署和分布式调度&#xff0c;并且提供了丰富的API和插件&#xff0c;可以轻松实现复杂的调度…...

什么是CSS的外边距重叠?

区块的上下外边距有时会合并&#xff08;折叠&#xff09;为单个边距&#xff0c;其大小为两个边距中的最大值&#xff08;或如果它们相等&#xff0c;则仅为其中一个&#xff09;&#xff0c;这种行为称为外边距折叠。注意&#xff1a;有设定浮动和绝对定位的元素不会发生外边…...

设计模式之抽象工厂模式

前言 工厂模式一般指的是简单工厂模式、工厂方法模式、抽象工厂模式&#xff0c;这是三种工厂模式的最后一篇&#xff0c;其他两种的文章链接如下&#xff1a; 设计模式之简单工厂模式-CSDN博客 设计模式之工厂方法模式-CSDN博客 建议三种模式放在一起对比学习&#xff0c;…...

Compose预处理组件大比拼:性能、应用场景和可视化对比总结

在机器学习的世界里,预处理组件就像是厨师的烹饪工具。选择合适的工具不仅可以让整个烹饪过程更加顺畅,还能确保最终的菜肴更加美味。 本文将深入探讨四种“烹饪工具”:TransformedTargetRegressor、make_column_transformer、make_column_selector和ColumnTransformer。通…...