软件设计 软件设计模式之SOLID原则

简介: 软件设计 软件设计模式之SOLID原则

软件设计模式之SOLID原则


#单一职责原则(SRP)

定义:任何一个软件模块都只对某一类行为者负责

说明:这里“软件模块”,在大部分情况下,可以简单定义为一个源代码文件、一个类、一组紧密相关的函数和数据结构、

 

#开闭原则(OCP)

定义:软件实体应当对扩展开放,对修改关闭

说明:这里的“软件实体”包含模块,类,接口,方法等

开闭原意在告诉我们,当应用的需求改变时,在不修改软件实体原有的源代码或者二进制代码的前提下,可以通过新增代码来满足新的需求,也就是说一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展,这是架构的根本目的,如果对原始需求的小小延伸就需要对原有的软件系统进行大幅修改,那么这个系统的架构设计显然是失败的。

 

在Java、C++这类语言中,可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。 因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

而在python中一切都是对象,可以指向任何类型,所以,不用定义接口变可实现类似接口。

 

#里氏替换原则(LSP)

第一种定义:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2 时,程序P的行为没有发生变化,那么类型 S 是类型 T 的子类型。

第二种定义:所有引用基类的地方必须能透明地使用其子类的对象。

 

第一种定义是最正宗的定义,而第二种定义则是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会 产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应

 

里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受到影响时,即基类随便怎么改动子类都不受此影响,那么基类才能真正被复用

因为继承带来的侵入性,增加了耦合性,也降低了代码灵活性,父类修改代码,子类也会受到影响,要让程序遵守里氏替换原则,实现继承时必须遵守以下几点:

1)子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。

2)当子类覆盖或实现父类的方法时,方法的的形参要比父类方法的输入参数更宽松。

3)当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

4)遵守以上几点的情况下,无法满足需求时,可以考虑在子类中增加自己特有的方法。

 

 

#接口隔离原则(ISP)

定义:

1、客户端不应该依赖它不需用的接口

2、类间的依赖关系应该建立在最小的接口上。

 

简单理解就是,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿,java接口类为例,继承接口的非抽象子类,都要实现接口类的拥有的所有方法,所以,当这些子类仅需要要接口类中的部分方法时还是需要去实现对其没有意义的接口方法,所以,接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加灵活轻便。但是需要注意的是:拆分要适度度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。

 

接口隔离原则和单一职责原则虽然很类似,但是两个原则还是存在着明显的区别。单一职责原则是在业务逻辑上的划分,注重的是职责。接口隔离原则是基于接口设计考虑。

 

 

#依赖反转原则(DIP)

依赖反转原则被称作依赖倒置原则,

定义:

1)高层策略性的代码不应该依赖实现底层细节的代码

2)抽象不应该依赖于细节,细节应该依赖于抽象

 

说明:

1、什么是“高层”,什么是“细节”?

对一个系统来说,业务逻辑是高层,其他是细节。业务逻辑是仅仅包括用例、业务实体部分,不包括任何框架、存储(数据库)、其他系统等部分,是纯粹的。其他细节,包括框架、数据库、消息队列,都是细节。业务逻辑应该不依赖任何细节。细节的实现可以任意替换而不影响业务逻辑。

 

依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定得多,其中心思想是面向接口编程

该原则告诉我们,如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用稳定的抽象类型,而非具体实现,特别注意不要在具体实现类上创建衍生类,不要覆盖包含具体实现的函数。Java中,抽象多指的是接口或抽象类,用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

显而易见,把这条设计原则当成金科玉律来加以严格执行是不现实的,因为在实际构造系统的过程中,不可避免的依赖一些具体实现,比如java的String类就是这样一个具体实现,我们将其强迫的转化为抽象类是不现实的。类似String类这种本身是非常稳定的类、模块,可以不用考虑,需要多关注的是经常会变动的具体实现模块。

 

在Python中,可以不通过抽象类的方式很轻松的实现依赖反转

 

例子:音乐玩具播放器模拟程序,要求可以播放各种动物的声音。

最开始,这个玩具的要求比较简单,一开始只要求播放一种动物声音,鸟叫声

 

Bird.java

publicclass Bird{

publicvoid call(){

System.out.println("bird call");

}

}

 

ToyPlayer.java

publicclass ToyPlayer{

publicvoid play(Bird bird){ #这里的入参,引用的是具体的实现类

bird.call();

}

}

 

Entry.java

publicclass Entry {

publicstaticvoid main(String[] args){

ToyPlayer player = new ToyPlayer();

Bird bird = new Bird();

player.play(bird);

}

}

 

image.png

如上,以上代码是不符合依赖反转原则的,播放器类,依赖具体的动物类(实现类),当需求变化时可能无法满足需求。比如,需要给玩具增加其它动物声,比如狗叫,这个时候就需要更改程序了。

改进版

Animal.java

interface Animal{

publicvoid call();

}

 

Bird.java

publicclass Bird implements Animal{

publicvoid call(){

System.out.println("bird call");

}

}

 

Dog.java

publicclass Dog implements Animal{

publicvoid call(){

System.out.println("dog call");

}

}

 

 

ToyPlayer.java

publicclass ToyPlayer{

publicvoid play(Animal animal){ #注意,这里替换了参数类型--替换具体类类型 Bird 为抽象类类型 Animal

animal.call();

}

}

 

 

Entry.java

publicclass Entry {

publicstaticvoid main(String[] args){

ToyPlayer player = new ToyPlayer();

Bird bird = new Bird();

player.play(bird);

 

Dog dog = new Dog();

player.play(dog);

}

}

 

image.png

 

 

 

 

目录
相关文章
|
图形学
Unity 不同Scene场景转换(简)
本文提供了Unity中实现场景转换的基本方法,包括编写传送脚本、创建传送门和玩家对象,并通过触发器实现玩家触碰传送门时切换到另一个场景的功能。
Unity 不同Scene场景转换(简)
|
12月前
|
存储 安全 数据安全/隐私保护
电脑突然就剩c盘了怎么恢复?
在日常使用电脑的过程中,许多人可能遇到过一个令人头疼的问题:打开“此电脑”时,发现原本分区明确的硬盘突然只剩下C盘,D盘、E盘甚至整个数据盘都“消失”了。这种情况看似棘手,但实际上,大多数情况下数据并未真正丢失,而是由于系统问题或设置错误导致分区不可见。本文将为大家详细分析可能的原因,并提供解决方法,帮助您恢复消失的分区和数据。
|
算法 前端开发 Java
支撑每秒数百万订单无压力,SpringBoot + Disruptor 太猛了!
本文详细介绍如何通过 Spring Boot 集成 Disruptor 实现每秒处理数百万订单的高性能系统。Disruptor 是一种无锁并发框架,采用环形缓冲区和无锁算法,提供极低延迟和高吞吐量。文章涵盖 Maven 配置、事件工厂、处理器及生产者实现,并通过 REST API 和 Thymeleaf 展示订单创建流程。Disruptor 在高并发场景下表现出色,是解决高性能并发处理的理想方案。
|
JavaScript API 调度
requestAnimationFrame在性能优化中的应用有哪些?
【5月更文挑战第29天】requestAnimationFrame在性能优化中的应用有哪些?
296 1
|
存储 缓存 算法
【专栏】探讨分布式限流所面临的挑战以及目前业界常用的解决方案
【4月更文挑战第27天】在互联网时代,分布式限流是应对高并发、保护系统稳定的关键。它面临数据一致性、算法准确性和系统可扩展性的挑战。常见限流算法有令牌桶、漏桶和滑动窗口。解决方案包括使用分布式存储同步状态、结合多种算法及动态调整阈值。定期压力测试确保策略有效性。随着系统规模增长,限流技术将持续发展,理解并应用限流原理对保障服务质量至关重要。
346 3
|
编译器 数据处理 Python
Python的xlrd模块在Anaconda中的安装
本文介绍在Anaconda环境下,安装Python读取.xls格式表格文件的库xlrd的方法~
974 1
Python的xlrd模块在Anaconda中的安装
Yum工具详解(一)-----Yum配置本地源
Yum工具详解(一)-----Yum配置本地源
626 0
|
机器学习/深度学习 存储 编解码
DETR系列大盘点 | 端到端Transformer目标检测算法汇总!(上)
自从VIT横空出世以来,Transformer在CV界掀起了一场革新,各个上下游任务都得到了长足的进步,今天就带大家盘点一下基于Transformer的端到端目标检测算法!
DETR系列大盘点 | 端到端Transformer目标检测算法汇总!(上)
|
安全 网络协议 JavaScript
如何测试CSRF
前言 阅读本文之前如果不了解什么是csrf,请先看一个视频: https://v.qq.com/x/page/c0877u4a1ei.html 为什么要谈这个漏洞?这种漏洞单独提交的话,厂家可能不会给你还多钱,但是它往往和其他脆弱点造成一些高危漏洞:比如账户劫持,Oauth相关的漏洞 本文主要总结了几种常见的测试(绕过)方法
809 0
|
存储 SQL 运维
如何应对数据库CPU打满?最优解在这里...
今天提前为大家揭秘数据库自治服务DAS的一个创新功能 —— AutoScale,基于数据库实例的实时性能数据作为输入,由DAS完成流量异常发现、合理数据库规格建议和合理磁盘容量建议,使数据库服务具备自动扩展存储和计算资源的能力。
2160 1
如何应对数据库CPU打满?最优解在这里...