当前位置:网站首页>软件设计的七大原则
软件设计的七大原则
2022-06-24 19:34:00 【Nice2cu_Code】
软件设计原则
一、开闭原则
对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,想要达到这样的效果,需要使用接口和抽象类。
软件中易变的细节可以从抽象派生的实现类中进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类即可。
举例:
【例】搜狗输入法的皮肤设计
分析:皮肤是搜狗输入法(SouGouInput)的属性,用户可以根据自己的喜爱更换输入法的皮肤。这些皮肤有共同的特点,比如都由图片、输入窗口等组成。可以为其定义一个抽象类的皮肤(AbstractSkin),而每个具体的皮肤(DefaultSpecificSkin 和 HeimaSpecificSkin)是其子类。如下图所示:

如果有新的皮肤,只需要定义新的实现类即可,无需修改抽象皮肤类。
二、里氏代换原则
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,会导致整个继承体系的可复用性降低
举例:
【例】正方形属于长方形
在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形,如下图所示:
RectangleDemo 类是软件系统中的一个组件,它有一个 resize 方法依赖基类Rectangle,resize方法是RectandleDemo类中的一个方法,用来实现宽度逐渐增长直到大于长度的效果。

代码如下:
正方形(Square):
由于正方形的长和宽相同,所以在方法 setLength 和 setWidth 中,对长度和宽度都需要赋相同值:
public class Square extends Rectangle {
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) {
super.setLength(width);
super.setWidth(width);
}
}
RectangleDemo 类:
resize() 方法如下:
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
运行一下这段代码就会发现,假如把一个长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合预期。
假如把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长(正方形重写的 setWidth 方法将会使长宽一致),代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。
进行改进:
创建一个接口 Quadrilateral ,定义四边形通用的获取长宽的抽象方法,长方形和正方形分别实现这个接口,定义各自具体的设置长宽的方法,如下图:

此时,resize方法只可以计算长方形,无法计算正方形,解决了之前的问题。
三、依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
依赖:A类依赖B类,也就是在A类中声明了B类型的成员变量。
举例:
【例】组装电脑
现要组装一台电脑,需要配件cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常的运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿,海盗船等,类图如下:
Computer 类中直接依赖了具体的硬件:

这种方式最大的缺陷是,电脑的 cpu 只能是 Intel 的,内存条只能是金士顿的,硬盘只能是希捷的。
根据依赖倒转原则进行改进:
让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类,类图如下:

后期如果有不同品牌的产品,只需要实现对应的接口即可,无需修改Computer类。
四、接口隔离原则
- 一个类不应该被迫依赖于它不使用的方法:

- 一个类对另一个类的依赖应该建立在最小的接口上:

举例:
【例】安全门案例
有一个 HeiMa 品牌的安全门,该安全门具有防火、防水、防盗的功能。可以将防火,防水,防盗功能提取成一个接口,形成一套规范。类图如下:

现在如果还需要创建一个其他品牌的安全门,该安全门只具有防盗、防水功能,很显然如果实现 SafetyDoor 接口就违背了接口隔离原则,需要改进,将不同的功能提取到不同的接口,类图如下:

这样,不同的安全门,需要什么样的功能实现对应的接口即可。
五、迪米特法则
迪米特法则又叫最少知识原则。
如果两个软件实体之间无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性:

可以理解为,只和朋友交谈,不和陌生人交谈。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
举例:
【例】明星与经纪人的关系实例
明星的许多日常事务由经纪人负责处理,如和粉丝的见面会,和媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是明星的陌生人,所以适合使用迪米特法则,类图如下:

六、合成复用原则
尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
继承的缺点:
- 继承破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的。
- 子类与父类的耦合度高。父类的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
组合或聚合的优点:
- 对象间的耦合度低。可以在类的成员位置声明。
举例:
【例】汽车分类管理程序
汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下(继承复用):

从上面的类图可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。可以试着将继承复用改为聚合复用,如下图:

二者的对比,假设要新添加一种光能汽车类:
对于继承复用(添加了三个类)

对于聚合复用(仅添加了一个类)

七、单一职责原则
不要存在多于一个导致类变更的原因。
比如,Class 类有两个职责 a 和 b,a变更会导致类的变更,进而可能导致对职责b功能的影响;同理,b变更会导致类的变更,进而可能导致对职责a功能的影响。也就是说职责 a 和 b 都可以对类进行变更,不满足单一职责原则。
解决方案:一个类或接口只负责一项职责。
好处:降低类的复杂度,提高类的可读性、可维护性,降低变更带来的风险。
举例:
用一个类描述动物呼吸这个场景:
class Animal{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("牛");
}
}
运行结果:牛呼吸空气。
此时出现一个问题,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,需要将 Animal 类细分为陆生动物类 Terrestrial ,水生动物 Aquatic ,代码如下:
class Terrestrial{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
class Aquatic{
public void breathe(String animal){
System.out.println(animal+"呼吸水");
}
}
public class Client{
public static void main(String[] args){
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe("牛");
Aquatic aquatic = new Aquatic();
aquatic.breathe("鱼");
}
}
运行结果:牛呼吸空气,鱼呼吸水。
如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类 Animal 来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:
class Animal{
public void breathe(String animal){
if("鱼".equals(animal)){
System.out.println(animal+"呼吸水");
}else{
System.out.println(animal+"呼吸空气");
}
}
}
但是这种方式有一个缺点,动物类型越多,if 判断越多。
所以,是否使用单一职责原则,需要根据具体的情况来决定,比如上述,逻辑比较简单,可以违背单一职责原则。
边栏推荐
- Heartless sword Chinese English bilingual poem 003 The sea of books
- In the first year of L2, arbitrum nitro was upgraded to bring more compatible and efficient development experience
- A girl has been making hardware for ten years. 。。
- 降低pip到指定版本(通過PyCharm昇級pip,在降低到原來版本)
- socket(1)
- socket done
- ansible基本配置
- Process communication mode
- Raspberry pie preliminary use
- SAP interface debug setting external breakpoints
猜你喜欢

Ideal L9, new trend of intelligent cockpit

Balanced binary search tree

Redis+caffeine two-level cache enables smooth access speed

leetcode:45. 跳跃游戏 II【经典贪心】

L2 元年,Arbitrum Nitro 升级带来更兼容高效的开发体验

Guava中这些Map的骚操作,让我的代码量减少了50%

First order model realizes photo moving (with tool code) | machine learning

YGG 近期游戏合作伙伴一览

Want to be a test leader, do you know these 6 skills?

CV2 package guide times could not find a version that satisfies the requirement CV2 (from versions: none)
随机推荐
60 个神级 VS Code 插件!!
C language - keyword 1
In the first year of L2, arbitrum nitro was upgraded to bring more compatible and efficient development experience
Detailed installation and use of performance test tool wrk
Description of software version selection of kt6368a Bluetooth dual-mode transparent chip
“阿里健康”们的逻辑早就变了
是真干不过00后,给我卷的崩溃,想离职了...
磁盤的結構
学习笔记23--多传感器信息融合基础理论(上)
In the era of industrial Internet, there is no Internet in the traditional sense
The leader of ERP software in printing industry
KT6368A蓝牙芯片的主从机之前透传功能说明,2.4G跳频自动连接
Machine learning: linear regression
Binary search tree template
leetcode:45. 跳跃游戏 II【经典贪心】
[notes of Wu Enda] convolutional neural network
想当测试Leader,这6项技能你会吗?
Flutter: Unsupported value: false/true
How to extract dates from web pages?
第二批入围企业公示!年度TOP100智能网联供应商评选