软件开发SOLID设计原则

news2024/11/23 0:42:38

前言:SOLID设计原则,不管是软件系统还是代码的实现,遵循SOLID设计原则,都能够有效的提高系统的灵活和可靠性,应对代码实现的需求变化也能更好的扩展和维护。因此提出了五大原则——SOLID。   我是通过老师讲解以及老师分享的笔记,然后加上自己的理解把它整理成笔记形式,分享给各位小伙伴一起学习!,同时也提升自己,巩固知识!

 

文章目录:

  1. 单一职责原则(SRP)
  2. 开闭原则(OCP)
  3. 里氏替换原则(LSP)
  4. 接口隔离原则(ISP)
  5. 依赖倒置原则(DIP)

一、单一职责原则(SRP)

单一职责原则 (SRP)英文全称为 Single Responsibility Principle ,是最简单的原则,同样也是最难用好的原则之一,主要的原因是:‘单一职责原则的范围把控’;它的定义比较简单:‘对于一个类而言,应该仅有一个引起它变化的原因。引起了变化的原因就是表示了这个类的职责’;它可能是某个特定领域的功能,可能是某个需求的解决方案。这个原则表达的是不要让一个类承担过多的责任,一旦有了多个职责,那么它就越容易因为某个职责而被更改,这样的状态是不稳定的,不经意的修改很有可能影响到这个类的其他功能。因此,我们需要将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,不同类之间的变化互不影响。

简单理解就是: 鸡笼放的是鸡而不是鸭,鸡是鸡,鸭是鸭;鸡不能和鸭放到一起,放在一起就混淆了;这一混淆就违反单一原则;

看下面的刀叉图会更好的理解:

先写一个违反单一原则的代码例子:

//我在这里定义一个鸡类抽象类

public abstract class Chickents{

    
    //来个鸡叫
    public void cockcsrow(){
  
        System.out.println("我会鸡叫");
    }
    

     //来个鸭叫
        
    public void cockcsrow(){
  
        System.out.println("我会鸭叫");
    }

}

看上上面代码,有没有发现问题,我上面备注的是,专门给鸡类定义一个抽象类,也就是我这个抽象类必须是和鸡相关的,里面的方法也是只能和鸡相关的,不能包含其他的;但是,我这个类下面还有一个方法和鸭有关;这就说明了,我这个类违反了单一职责原则 ;

正确的写法:

//定义一个鸡类的抽象类
public abstract class Chickens{
     public void cockscrow(){

        System.out.println("我会鸡叫");
    }

}

一个类只有一个职责,(和一个原因被更改)

二、开闭原则(OCP)

开闭原则 (OCP) 英文全称为 Open-Closed Principle,基本定义是软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是对于修改是封闭的。这里的对扩展开放表示添加新的代码,就可以让程序行为扩展来满足需求的变化;对修改封闭表示在扩展程序行为时不要修改已有的代码,进而避免影响原有的功能。要实现不改代码的情况下,仍要去改变系统行为的关键就是抽象和多态,通过接口或者抽象类定义系统的抽象层,再通过具体类来进行扩展。这样一来,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,达到开闭原则的要求。

简单理解就是:我买一只鸡拿在手上,我又想买多只鸡,这时候,手拿不下,我们应该买个麻袋,放到一起,提着麻袋,而不是简单的全拿在手上。

看下面图有助于理解:

下面我写个加减违反开闭原则的代码例子

  /***
     * 
     * @param a
     * @param b
     * @param po
     * 
     * 
     * 这里完成了加和减
     * @return
     */
    public Double Calculator(Double a ,Double b ,String po){
        //转换一下防止精度丢失
        BigDecimal s1 = new BigDecimal(String.valueOf(a));
        BigDecimal s2 = new BigDecimal(String.valueOf(b));
        if("+".equals(po)){
            return s1.add(s2).doubleValue() ;
        }else if("-".equals(po)){
            return s1.subtract(s2).doubleValue() ;
        }else {
          throw new RuntimeException("没有别的符号了");
        }

    };

通过上面的例子,加和减已经写完了,这个时候,老王跑过来说:我要加两个个乘除的功能,这个时候我们是不是又要在下面 else if ,那么问题来了,开闭原则 ‘对修改封闭 ,就是我们在原有的代码,不能修改,我们做了修改,那么就是违反了开闭原则,我们该如何解决呢?我们创建一个接口就好了,注意,这个接口只针对计算,如果这个计算接口,还写了和计算不相关的代码,也就违反了单一原则。单一原则开闭原则它们是同时存在的 ;

下面我写个不违反开闭原则的代码例子

1)定义一个针对计算的接口

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 *
 * 计算器接口
 */
public interface NewCalculator {
    
    //做加减乘除的方法

    public Double NewCal(Double number1,Double number2) ;



}

这个接口,创建好了,我们在们在创建一个类(可以是加法类,可以是减法类...),实现这个接口

2)创建个类

  2.1、加法类,进行加法运算

package com.lx.impl;

import com.lx.dom.NewCalculator;

import java.math.BigDecimal;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 *
 * 加法运算
 */
public class ADDCalculator  implements NewCalculator {
    @Override
    public Double NewCal(Double number1, Double number2) {
        //转换BigDecimal防止双精度丢失
        BigDecimal a = new BigDecimal(String.valueOf(number1)) ;
        BigDecimal b = new BigDecimal(String.valueOf(number2)) ;

        return a.add(b).doubleValue();
    }
}

2.2、减法类,进行加法运算

package com.lx.impl;

import com.lx.dom.NewCalculator;

import java.math.BigDecimal;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 *
 * 减法运算
 */
public class SubtractionCalculator implements NewCalculator {
    @Override
    public Double NewCal(Double number1, Double number2) {
        //转换BigDecimal防止双精度丢失
        BigDecimal a = new BigDecimal(String.valueOf(number1)) ;
        BigDecimal b = new BigDecimal(String.valueOf(number2)) ;

        return a.subtract(b).doubleValue();
    }
}

一个计算接口,我们通过类实现接口,再实现它的方法,当我们想实现加法功能的时候,我们创建一个加法类实现这个接口,通过接口的方法再实现相应的功能,想实现一个减法功能的时候,我们创建一个减法类实现这个接口再通过接口方法实现相应功能;这时老王过来说:实现一个乘除的功能;我们再创建两个乘除类实现接口,再通过接口方法实现相应功能 ;

三、里式替换原则 (LSP)

里式替换原则 (LSP) 英文全称为 Liskov Substitution Principle,基本定义为:在不影响程序正确性的基础上,所有使用基类的地方都能使用其子类的对象来替换。这里提到的基类和子类说的就是具有继承关系的两类对象,当我们传递一个子类型对象时,需要保证程序不会改变任何原基类的行为和状态,程序能正常运作。

简单理解就是:任何基类可以出现的地方,子类一定可以出现

看下图有助于理解:

这里我写一段违反里式替换原则代码实例:

1)创建一个类,实现某接口,类实现了所有接口的方法

package com.lx.violate.impl;

import com.lx.violate.TestInf;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public class TestIn implements TestInf {
    @Override
    public String si1() {
        return null;
    }

    @Override
    public String si2() {
        return null;
    }

    @Override
    public String si3() {
        return null;
    }

   @Override
    public String si3(name) {
       System.out.println(name+"你最帅");
        return null;
    }
}

通过上面的代码,我们可以看到,我们重载了一个si3()方法,问题来了,根据里式替换原则:‘尽量不要重载父类的方法,换句话就是子类可以扩展父类的功能,但不能改变父类原有的功能

所以,我们这里不应该有重载方法的出现,如果想要添加新功能,可以在父类扩展一个方法。

正确的写法代码例子

package com.lx.violate.impl;

import com.lx.violate.TestInf;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public class TestIn implements TestInf {
    @Override
    public String si1() {
        return null;
    }

    @Override
    public String si2() {
        return null;
    }

    @Override
    public String si3() {
        return null;
    }

}

这里只演示了一种最简单的例子,还有很多例子,就不一样展示了!

四、接口隔离原则(ISP)

接口隔离原则 (ISP) 英文全称为 Interface Segregation Principle,基本定义:客户端不应该依赖那些它不需要的接口。客户端应该只依赖它实际使用的方法,因为如果一个接口具备了若干个方法,那就意味着它的实现类都要实现所有接口方法,从代码结构上就十分臃肿。

简单理解就是:类所要实现的接口应该分解成多个接口,然后根据你所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明这个方法!

看图有助于理解:

写一个违反接口隔离原则的代码例子:

1)创建接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public interface TestInf {
    
    public void si1() ;
    public void si2() ;
    public void si3() ;
    
}

2)创建User1类实现接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public class Users1 implements TestInf{
    @Override
    public void si1() {

    }

    @Override
    public void si2() {

    }

    @Override
    public void si3() {

    }
}

3)创建User2类实现接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 家辉
 */
public class Users2 implements TestInf{
    @Override
    public void si1() {

    }

    @Override
    public void si2() {

    }

    @Override
    public void si3() {

    }
}

通过上面的代码,我们可以分析,我们只要有一个类要实现这个接口,我们就必须实现这个接口的所有方法,问题来了,如果我User2类只想实现这个接口的一个方法,这时候该怎么办呢?如果我这个User2类还要实现别的功能,该怎么办呢?在接口隔离原则不可能在这个接口中在扩展一个方法啊,如果扩展了,那么就违反了口隔离原则。

  • JDK8提供了默认方法,相当于在接口中可以有一个默认的空实现,我们可以创建默认方法

  • 当子类实现这个接口时,只需要按需实现即可,意思就是需要实现哪个

  • 在创建一个新的接口

接口隔离原则:‘类所要实现的接口应该分解成多个接口,然后根据你所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明这个方法

正确的接口隔离原则的代码例子:

 1、创建TestInf接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public interface TestInf {

    /**
     * JDK8提供了默认方法,相当于在接口中可以有一个默认的空实现,
     * 当子类实现这个接口时,只需要按需实现即可,意思就是需要实现哪个
     * 方法就重写哪个方法
     */
    default void methodA(){}
    default void methodB(){}
    default void methodC(){}

}

 2、创建User2Inf接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 */
public interface User2Inf {
    public void ma() ;
}

3、创建User2类实现TestInf,User2Inf两个接口

package com.lx.dom;

/***
 * @Date(时间)2023-05-30
 * @Author 半杯可可
 * 
 * 在实现多一个User2Inf接口,针对User2类的接口
 */
public class User2 implements TestInf,User2Inf{

    @Override
    public void methodB() {
        TestInf.super.methodB();
    }

    @Override
    public void ma() {
        System.out.println("你们最帅/美");
    }
}

基于接口隔离原则,我们需要做的就是减少定义大而全的接口,类所要实现的接口应该分解成多个接口,然后根据所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明,这样可以解除调用方与对象非相关方法的依赖关系

五、依赖倒置原则 (DIP)

依赖倒置原则 (DIP) 英文全称 Dependency Inversion Principle, DIP),基本定义是:

  • 高层模块不应该依赖低层模块,两者应该依赖抽象;

  • 抽象不应该依赖细节,细节应该依赖抽象。

这里的抽象就是接口和抽象类,而细节就是实现接口或继承抽象类而产生的类。如果高层模块依赖于低层模块,那么低层模块的改动很有可能影响到高层模块,从而导致高层模块被迫改动,这样一来让高层模块的重用变得非常困难。因此可以在高层模块构建一个稳定的抽象层,并且只依赖这个抽象层;而由底层模块完成抽象层的实现细节。这样一来,高层类都通过该抽象接口使用下一层,移除了高层对底层实现细节的依赖。

简单理解就是:高层、底层两个模块依赖抽象,抽象不依赖细节,细节则依赖抽象

看图更好理解:

总结:

  • 单一职责原则(SRP)
    • 控制类的颗粒大小,减少不相关代码功能的耦合,让类更加健壮;
  • 开闭原则(OCP)
    • 有了开闭原则,面向需求的各种变化能够进行快速的调整实现功能,提高了系统的灵活性、维护性、重用性;缺点就是会增加一定的‘复杂性’;
  • 里氏替换原则(LSP)
    • 里氏替换原则目的就是要保证继承关系的正确性,所有子类的行为功能必须和使用者对其父类的期望保持一致,如果子类达不到这一点,那么必然违反里氏替换原则 ;
  • 接口隔离原则(ISP)
    • 接口隔离原则主要功能就是控制接口的粒度大小,防止暴露给客户端无相关的代码和方法,保证了接口的高内聚,降低与客户端的耦合 ;
  • 依赖倒置原则(DIP)
    • 依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性 ;
    • 依赖倒置原则是框架设计的核心原则,善于创建可重用的框架和富有扩展性的代码 ;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深入浅出Spring Security(三)】默认登录认证的实现原理

Spring Security 默认登录认证的实现原理 一、默认配置登录认证过程二、流程分析登录页面的由来表单登录认证过程(源码分析) 三、UserDetailsServiceSpring Security 中 UserDetailsService 的实现默认的 UserDetailsService 配置(源码分析&a…

【送书福利-第七期】《分布式中间件核心原理与RocketMQ最佳实践》

大家好,我是洲洲,欢迎关注,一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我! 本文目录 一、前言二、内容介绍三、作者介…

孪生诱捕网络在欺骗防御领域的应用

随着以数字化、网络化和智能化为特征的信息化浪潮的蓬勃兴起,信息已经成为重要的战略资源与重要生产要素,在国家的发展和人们的生产生活中起到至关重要的作用。信息化在给人们带来便利的同时,网络信息安全问题也日益凸显。经过多年的网络安全…

【Linux】Linux 文件系统与设备文件

Ref: 《Linux设备驱动开发详解:基于最新的Linux4.0内核》中的第5章《Linux 文件系统与设备文件》 基于Linux 5.10 本文结合源码和实例分析了Linux 文件系统与设备文件,主要介绍文件系统的构成和发展,以及三种虚拟文件系统debugfs procfs sysf…

《微服务实战》 第二十七章 CAS

前言 本章节介绍CAS 1、CAS的概念 CAS的全称为:CompareAndSwap,直译为对比和交换。 CAS实际是普遍处理器都支持的一条指令,这条指令通过判断当前内存值V、旧的预期值A、即将更新的值B是否相等来对比并设置新值,从而实现变量的原子性。 Syn…

二进制安装Kubernetes(K8s)集群---从零安装教程(带证书)

一、实验环境 1、安装说明 selinux iptables off 官方网站:https://kubernetes.io/zh-cn/docs/home 主机名IP系统版本安装服务master0110.10.10.21rhel7.5nginx、etcd、api-server、scheduler、controller-manager、kubelet、proxymaster0210.10.10.22rhel7.5nginx、…

【细读Spring Boot源码】Spring如何获取一个Bean?BeanFactory的getBean方法

前言 在读refresh(context);时,finishBeanFactoryInitialization(beanFactory);中beanFactory.preInstantiateSingletons();用于实例化剩余所有的non-lazy-init的单例。这里包含了前置处理器和后置处理器 这里面就用到了本文主题getBean(beanName); 下面具体看看这…

4.数据结构期末复习之树

1.树的概念(一对多)(要求会写遍历序列) 1)n>个节点,n0时为空树2)仅有一个根节点3)左右节点互不相交,子节点一对多2.树的特点 1.子树之间没有关系 2.节点不属于多个子树(一个节点只能在一颗子树里面)3.没有回路(就是成环)4.有层次性3.术语 1.节点的度:拥有子树的个数…

ArcGIS教程——ArcGIS快速入门

实例数据:https://pan.baidu.com/s/184wwCmWrJdb-qjxsT614EQ 密码:dowv ArcGIS for Desktop是一套完整的专业GIS应用程序,包含有ArcMap、ArcCatalog、ArcToolbox、ArcScense、ArcGlobe和Model Builder等。其中ArcMap、ArcCatalog、ArcToolbo…

Unreal Niagara粒子入门3 - 根据模型顶点发射粒子

在一些游戏中经常会出现根据模型顶点位置发射粒子的情况,这次就来实现一下该效果: 1.基础创建操作 首先从空粒子发射器开始创建,右键NiagaraSystem->Create empty system,打开粒子系统后再右键Add empty emitter&#xff1a…

【智能软件安全】上海道宁为您带来智能软件安全平台——​Veracode,帮助您全面地保护您构建和管理地应用程序

Veracode可以全面地 保护您构建和管理地应用程序 在现代软件 开发生命周期的 每个阶段不断发现并修复缺陷 Veracode通过 建立一种在安全和开发团队之间 架起桥梁并授权 开发人员成为 安全倡导者的积极文化 从一开始就防止常见的安全漏洞 开发商介绍 Veracode成立于20…

腾讯云8核16G服务器18M带宽CPU性能可支撑多少人同时在线?

腾讯云8核16G轻量服务器CPU性能如何?18M带宽支持多少人在线?轻量应用服务器具有100%CPU性能,18M带宽下载速度2304KB/秒,折合2.25M/s,系统盘为270GB SSD盘,月流量3500GB,折合每天116.6GB流量&…

【CocosCreator入门】CocosCreator组件 | TiledMap(地图)组件

Cocos Creator是一款流行的游戏开发引擎,具有丰富的组件和工具,其中TiledMap组件可以帮助开发者快速创建、加载和渲染地图。 目录 一、组件介绍 二、组件属性 三、脚本控制 3.1加载地图 3.2渲染地图 四、详细说明 五、关闭裁剪 六、节点遮挡 一、…

Linux中与进程间通信相关的内核数据结构

【摘要】本文详细讲述了Linux内核中与进程间通信概念相关的内核数据结构及其内在联系。 九、进程间通信(IPC)相关数据结构 9.1 ipc_namespace 从内核版本2.6.19开始,IPC机制已经能够意识到命名空间的存在,但管理IPC命名空间比较简单,因为它…

VR教育:让教育“可视化”,开启元宇宙教学之路

放眼世界,有不少高等教育学校都已经开始了元宇宙教学之路,为了从根本上解决目前课堂教学中存在的问题,进一步提高课堂教学质量,VR教育就可以很好地完善这些方面。 传统教育并不能让学生很好地沉浸在真实知识环境中,在一…

连锁门店运营管理系统有哪些功能?该如何选购?

连锁门店运营管理过程中,面临诸多难题,比如不同门店分布在不同地区,管理分散;各门店的人员管理、绩效考核、销售数据等工作进行困难;很难保证产品和服务的标准化管控。 连锁店只有不断适应市场变化,趁早选择…

不用运算符的加法运算

一.不用运算符的加法 1.题目描述 设计一个函数把两个数字相加。不得使用 或者其他算术运算符。 力扣:力扣 2.问题分析 1.知识预备 注意:下面所有的结论的进位是考虑二进制的进位,因为我们使用的位运算符,是针对二进制进行的. 结论一:在不考虑进位的情况下,其无…

【group by】mysql分组查询的案例和原理

【group by】mysql分组查询的案例和原理 【一】group by的使用场景【二】group by的基本语法【1】基本语法【2】常用的聚合函数(1)max函数:取出分组中的最大值(2)avg函数:取出分组中的平均值(3&…

斩获大奖!「智办事绩效」荣获钉钉年度奖项-「含钉量新锐奖」

近日,钉钉发布「含钉量年度奖」榜单,「智办事绩效」凭借与钉钉的深度融合斩获钉钉年度奖项-「含钉量新锐奖」。 ​作为钉钉优质合作伙伴,智办事绩效致力于用数字化、智能化的产品与专业的服务,解决传统绩效管理、人才培养痛点&…

【JOSEF约瑟 JDL-5200A 电流继电器 过负荷或短路启动元件 导轨安装】

名称:电流继电器;品牌:JOSEF约瑟;型号:JDL-5200A触点容量:250V2A;返回时间:≤35ms;整定范围:0.03-19.9A;特点:返回系数高、安装方便。…