首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >软件设计的七大原则

软件设计的七大原则

作者头像
用户3147702
发布于 2022-06-27 08:53:45
发布于 2022-06-27 08:53:45
2.3K0
举报

1. 引言

写出高质量、易维护、高可用的代码是每一个程序员的追求,那么,究竟什么样的代码才是易于维护与扩展的好代码呢?本文,我们就来介绍软件开发过程中的七大原则,一同来管窥一二。

2. 软件设计的七大原则

为了指导程序员写出优秀的代码,前人已经为我们总结出了七个原则,他们侧重点不同、关注的角度各异,是我们在日常开发中要时刻思考、尽量遵循的优秀设计:

  1. 单一职责原则(SRP)
  2. 开闭原则(OCP)
  3. 里氏替换原则(LSP)
  4. 接口隔离原则(ISP)
  5. 依赖倒置原则(DIP)
  6. 迪米特法则(LoD)
  7. 合成复用原则(CRP)

其中前五个原则就统称为大名鼎鼎的 SOLID 原则。

3. 开闭原则

3.1 原则描述

开闭原则(Open Closed Principle,OCP)是勃兰特·梅耶在他 1988 年的著作《面向对象软件构造》中首次提出的:

软件实体应当对扩展开放,对修改关闭。 Software entities should be open for extension,but closed for modification.

具体含义是:

当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。

3.2 原则实现

这一原则的重要性是不言而喻的,谁都希望自己的代码能够在反复迭代的需求中仅通过扩展已有功能就可以满足新的需求,而不是一个简单的新需求就让整个代码必须发生重构。

那么,如何实现这一原则呢?“抽象约束、封装变化”是一个很好的方法。

简单的来说,就是通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

只要抽象层能够覆盖足够多的场景,那么,即使实现层无法满足新的需求,也只需要扩展实现层即可,而不需要改变底层的抽象设计。

4. 里氏替换原则

4.1 原则描述

里氏替换原则(Liskov Substitution Principle,LSP)是由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来的:

继承必须确保超类所拥有的性质在子类中仍然成立。 Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.

简单的来说,这个原则是说子类在继承父类时,不要改变父类原有的功能。

具体的来说,这个原则可以被总结为以下四点:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  2. 子类中可以增加自己特有的方法;
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松;
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等。

如果程序违背了里氏替换原则,那么在多态环境下,代码维护者由于遇到父子类行为的不一致,很容易陷入迷惑的境地,程序运行的出错概率也就随即大幅提升。

4.2 原则实现

我们常常会结合日常生活中对事物的感知来进行软件架构的设计,但这有时会因为违背里氏替换原则而埋下隐患,比如我们设计一个鸟类,他包含几个属性:体重、飞行速度,并且有这两个方法对应的 get、set 方法。那么,对于任何一个鸟类的对象,我们都可以通过路程/飞行速度计算出飞行所需的时间。

然而,对于维护者来说,此时他需要创建一个“鸡”类,由于鸡不会飞,而“鸟”类中没有是否会飞的属性,意即默认“鸟”类的实现均会飞,此时,“鸡”类就不能够作为“鸟”类的子类,否则对于 getFlySpeed() 方法来说,如果“鸡”类的实现返回为 0,那么,就会造成在计算飞行时间时出现除 0 异常。

5. 依赖倒置原则

5.1 原则描述

依赖倒置原则(Dependence Inversion Principle,DIP)是 Object Mentor 公司总裁罗伯特·马丁(Robert C.Martin)于 1996 年在 C++ Report 上发表的文章中首次提出的:

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。 High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions.

简单的总结,这个原则就是:

要面向接口编程,不要面向实现编程。

在实际的软件设计中,实现是多变的,抽象层是稳定的,因此,让模块间都通过抽象的接口或是抽象类来描述依赖,可以很大程度上降低开发风险,提升稳定性,同时也是模块间解耦的有力方法。

5.2 原则实现

遵循以下四点,就能在项目中满足依赖倒置规则:

  1. 每个类尽量提供接口或抽象类,或者两者都具备。
  2. 变量的声明类型尽量是接口或者是抽象类。
  3. 任何类都不应该从具体类派生。
  4. 使用继承时尽量遵循里氏替换原则。

6. 单一职责原则

6.1 原则描述

单一职责原则是由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的:

一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。 There should never be more than one reason for a class to change.

该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  1. 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
  2. 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。

不仅是类的设计,单一职责同样也适用于方法。一个方法应该尽可能做好一件事情。如果一个方法处理的事情太多,其颗粒度会变得很粗,不利于重用。

6.2 原则实现

当一个类只具有一个职责时,那么当这个职责发生变化,我们可以非常肯定地说没有其他类需要变更,这在可维护性上来说是很强的。

例如在创建“程序员”类时,你可能会考虑到程序员需要具有写代码、测试、调试、运行维护等方法,但事实上,写代码的职责与其他职责并不相关,测试、调试的职责也非常独立,运行维护的职责也是同样,因此,如果将“程序员”类拆分为“程序员”类、“测试”类、“运维”类三个类,项目的可维护性便可以大为加强。

7. 接口隔离原则

7.1 原则描述

接口隔离原则(Interface Segregation Principle,ISP)是 2002 年罗伯特·C.马丁提出的:

客户端不应该被迫依赖于它不使用的方法。 Clients should not be forced to depend on methods they do not use.

这是不是非常类似于奥卡姆剃刀原理:如无必要,勿增实体。

这个原则也被描述为:

一个类对另一个类的依赖应该建立在最小的接口上。 The dependency of one class to another one should depend on the smallest possible interface.

如果能够遵循这一原则,模块间的依赖粒度得以最小化,那么,如果某个模块发生变更,其他模块受到的影响就会显著变小,外来变更就不会因此扩散到过大的范围,从而让项目的可维护性提升至非常高的维度。

这个原则也很有力的保障了模块间的低耦合与模块内的高内聚。

7.2 原则实现

在具体的实现中,我们可以尽量遵循以下四个规则:

  1. 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
  2. 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
  3. 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
  4. 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

如果系统中某个模块随着业务的发展越来越庞大,适时地拆分是扼杀危险的有效手段。对模块功能进行归类,从而拆分出几个高度内聚的模块,非常有利于系统的发展。

8. 迪米特法则

8.1 原则描述

迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP),产生于 1987 年美国东北大学(Northeastern University)的一个名为迪米特(Demeter)的研究项目,由伊恩·荷兰(Ian Holland)提出,被 UML 创始者之一的布奇(Booch)普及,后来又因为在经典著作《程序员修炼之道》(The Pragmatic Programmer)提及而广为人知:

只与你的直接朋友交谈,不跟“陌生人”说话。 Talk only to your immediate friends and not to strangers.

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

8.2 原则实现

在实际的开发过程中,复杂的依赖关系网往往令人无比抓狂。迪米特法则就是为了解决这一问题而诞生的。

如果你设计每个类的每个方法时都问问自己:我引入的依赖是该依赖的对象吗?我暴露的方法是应该暴露的吗?那么,你的设计一定会有显著提升。

即便是必要依赖,也要尽量降低依赖的次数,尤其是尽量不要在模块间传递序列化后的数据,由于过度泛化造成的类型不清晰,可能会让你的系统产生难以预想的问题。

9. 合成复用原则

9.1 原则描述

合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP),它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

继承复用虽然有简单和易实现的优点,但它也存在以下缺点:

  1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  2. 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

9.2 原则实现

当需要扩展一个类时,优先通过类属性来进行扩展,而不是通过派生新的子类。

考虑一个实现,假设有一个“职员”类,他有四个子类:“HR”类、“RD”类、“QA”类、“SRE”类,这时,新的需求来了,要求每个职员都要拥有性别,那么,此时你如果选择将四个子类进一步派生出“男HR”类、“女HR”类、“男RD”类、“女RD”类……,你所需要维护的类继承层次就有三层,总计九个类,而如果你仅仅在父类中增添一个 gender 属性,整个继承层次并没有发生变化,相较于增加一层类继承层次,可维护性显然要高得多。

附录 -- 参考资料

https://en.wikipedia.org/wiki/SOLID

http://c.biancheng.net/view/1320.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
通过简单代码示例了解七大软件设计原则
本文档设计源码地址:https://gitee.com/daijiyong/SoftwareDesignPrinciples
你好戴先生
2020/09/03
6850
面向对象的七大设计原则
1. 单一职责原则(Single Responsibility Principle) 每一个类应该专注于做一件事情。 每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都需要遵循这一重要原则。 2. 里氏替换原则(Liskov Substitution Prin
房上的猫
2018/03/14
2K0
设计模式——七大原则
设计模式——七大原则
Java架构师必看
2021/05/14
3400
设计模式六大原则
有言曰,“无规矩不成方圆”,有“规”才能画“圆”,那设计模式要遵循的六大原则要画一个什么的“圆”呢?
mingmingcome
2021/11/29
3670
设计模式六大原则
回归设计模式的本质:设计原则
作为开发人员,或多或少都会熟悉或了解一些设计模式,如单例模式、工厂模式、观察者模式等等。但并非都能理解这些设计模式背后的本质,从而可能会导致对模式单纯的套用或滥用的情况出现。不要为了模式而模式,要明白使用模式的目的,要正确理解模式背后的设计原理,要理解背后的基本设计原则。
Keegan小钢
2019/07/30
5330
回归设计模式的本质:设计原则
接口设计六大原则
一. 单一职责原则 Single Responsibility Principle, 简称SRP。 定义 There should never be more than one reason for a class to change 应该有且仅有一个原因引起类的变 准则 职责的划分?单一的定义和级别? 应该根据实际业务情况而定。关注变化点。 实际使用时,类很难做到职责单一,但是接口的职责应该尽量单一。 二. 里氏替换原则 Liskov Substitution Principle, 简称
子勰
2018/05/31
5.7K0
设计模式之软件设计七大原则
点关注,不迷路;持续更新Java架构相关技术及资讯热文!!! 1.开闭原则 定义:一个软件实体如类,模块和函数应该对扩展开放,对修改关闭 要点: 当变更发生时,不要直接修改类,而是通过继承扩展的方式完成变更 用抽象构建框架,用实现扩展细节 预先设计好抽象 优点:提高软件系统的可复用性及可维护性 面向抽象编程,实现抽象化,抽象出业务模型 提高内聚,降低耦合 2.依赖倒置原则 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象 这个设计原则比较好理解,首先高层模块只接口或者父类,接口或父类不该依赖其派生子
本人秃顶程序员
2019/05/30
8300
设计模式之软件设计七大原则
设计模式的六大原则
设计模式的学习,可以增强自己的代码复用意识。同时,也可以清晰地表达自己的编程思路。本文将介绍设计模式的六大原则:
sunsky
2022/05/11
4770
设计模式的六大原则
设计模式的七大原则:打造更高效的软件系统
设计模式是我们构建软件系统的基石,它们提供了通用的解决方案来解决常见的设计问题。在这篇文章中,我们将探讨设计模式的七大原则,它们能够指导我们如何更好地使用设计模式,帮助我们构建更高效,更灵活的软件系统。
运维开发王义杰
2023/08/10
3820
设计模式的七大原则:打造更高效的软件系统
设计模式六大原则
  单一职责原则又称为单一功能原则,即不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
那一叶随风
2018/08/22
3150
设计模式六大原则
设计模式八大原则知多少
设计模式是一种通用的解决问题的经验,可以帮助我们设计出可重用、可维护和可扩展的软件。
音视频牛哥
2023/07/09
3180
一文带你读懂:设计模式的六大原则
软件设计最大的难题就是应对需求的变化,但是纷繁复杂的需求变化又是不可预料的,我们要为不可预料的变化做好准备,这本身是一件非常痛苦的事情,但好在有大师们已经给我们提出了非常好的六大设计原则和23种设计模式来“封装”未来的变化。
后台技术汇
2022/05/28
1K0
一文带你读懂:设计模式的六大原则
一篇文章让你读懂设计模式的七大原则;不要错过
主要是为了在编码过程中面临着来自耦合性、内聚性以及可维护性、重用性、灵活性等多方面的挑战
@派大星
2023/06/28
1560
一篇文章让你读懂设计模式的七大原则;不要错过
设计模式之六原则一法则详解
六原则一法则是指开闭原则、里氏替换原则、依赖倒置原则、单一职责原则、接口隔离原则、合成复用原则、迪米特法则。
Java技术债务
2022/08/09
6630
设计模式之六原则一法则详解
面向对象编程中的六大原则
1. 单一职责原则(Single Responsibility Principle) 2. 里氏替换原则(Liskov Substitution Principle) 3. 依赖倒置原则(Dependence Inversion Principle) 4. 接口隔离原则(Interface Segregation Principle) 5. 迪米特法则(Law Of Demeter) 6. 开闭原则(Open Close Principle)
進无尽
2018/09/12
1.3K0
面向对象编程中的六大原则
JavaScript设计模式经典-面向对象中六大原则
主要学习JavaScript中的六大原则。那么六大原则还记得是什么了吗?六大原则指:单一职责原则(SRP),开放封闭原则(OCP),里氏替换原则(LSP),依赖倒置原则(DIP),接口分离原则(ISP),最少知识原则(LKP)。
达达前端
2019/11/21
8540
JavaScript设计模式经典-面向对象中六大原则
软件程序设计原则
软件也像人一样,具有生命力,从出生到死亡,会经历多种变化。软件架构设计也不是一蹴而就的,是不断地演进发展。每个程序员都可以从理解编程原则和模式中受益。
Abalone
2022/07/14
5800
软件程序设计原则
【设计模式前置课】软件设计的七原则
所以我们需要将其解耦思想为自己所用,从而提升自己编码能力,使自己的代码更加容易维护、扩展。
韩旭051
2020/12/08
5250
设计模式七大原则(1.2)
设计模式原则是设计设计模式的原则,也就是设计模式应当如何设计所遵守的原则;换句话说,设计模式的设计是基于设计模式原则的。
Noneplus
2019/09/24
4290
设计模式七大原则(1.2)
java代码设计的6+1大原则
1.开闭原则(Open Close Principle) 定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 开放-封闭原则的意思就是说,你设计的时候,时刻要考虑,尽量让这个类是足够好,写好了就不要去修改了,如果新需求来,我们增加一些类就完事了,原来的代码能不动则不动。这个原则有两个特性,一个是说“对于扩展是开放的”,另一个是说“对于更改是封闭的”。面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。这就是“开放-封闭原则”的精神所在 比如,刚开始需求只是写加法程序,很快在client类中完成后,此时变化没有发生,需求让再添加一个减法功能,此时会发现增加功能需要修改原来这个类,这就违背了开放-封闭原则,于是你就应该考虑重构程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承、动态等来隔离具体加法、减法与client耦合,需求依然可以满足,还能应对变化。此时需求要添加乘除法功能,就不需要再去更改client及加减法类,而是增加乘法和除法子类即可。 绝对的修改关闭是不可能的,无论模块是多么的‘封闭‘,都会存在一些无法对之封闭的变化,既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。在我们最初编写代码时,假设变化不会发生,当变化发生时,我们就创建抽象来隔离以后发生同类的变化。 我们希望的是在开发工作展开不久就知道可能发生的变化,查明可能发生的变化所等待的时候越长,要创建正确的抽象就越困难。开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出现频繁变化的那些部分做出抽象,然而对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意,拒绝不成熟的抽象和抽象本身一样重要。开放-封闭原则,可以保证以前代码的正确性,因为没有修改以前代码,所以可以保证开发人员专注于将设计放在新扩展的代码上。 简单的用一句经典的话来说:过去的事已成历史,是不可修改的,因为时光不可倒流,但现在或明天计划做什么,是可以自己决定(即扩展)的。
lyb-geek
2018/07/26
9920
相关推荐
通过简单代码示例了解七大软件设计原则
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档