第一章 前言
第一章 前言
- 1. 初学者常犯的错误[^1]
- 2. 好的代码
- 3. UML
- 4. SOLID设计原则
-
- 4.1 单一职责原则
- 4.2 开放封闭原则
- 4.3 里氏替换原则
- 4.4 接口隔离原则
- 4.5 依赖倒置原则
- 4.6 总结
- 5. 其它实用原则
-
- 5.1 迪米特法则
- 5.2 KISS原则
- 5.3 DRY原则
- 5.4 YAGNI原则
- 5.5 三次法则
- 5.6 CQS原则
1. 初学者常犯的错误1
- 代码要规范;
- 不要做无用功;
- 考虑边界值,异常值;
- 遇到问题,习惯性地用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实使用计算机的方式去思考,比如计算器这个程序,先要求输入两个数和符 ,然后根据运算符 判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只能满足当前的需求,程序不易维护,不容易扩展,更不容易复用,从而达不到高质量代码的需求;
2. 好的代码
刻版印刷、活字印刷与面向对象
- 第一,要改,只需更改要改之字,此为可维护;
- 第二,这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
- 第三,此诗若要加字,只需另刻字加入即可,这是可扩展;
- 第四,字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好 。
- 通过封装、继承、多态把程序的耦合度降低,用设计模式使得程序更加的灵活,容易修改,并且易于复用。
3. UML
Unified Modeling Language,统一建模语言
泛化(Generalization) 是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。如:哺乳动物具有恒温、胎生、哺乳等生理特征,猫和牛都是哺乳动物,也都具有这些特征,但除此之外,猫会捉老鼠,牛会耕地。
实现(Realization) 是一种类与接口的关系,表示类是接口所有特征和行为的实现。如:蝙蝠也是哺乳动物,它除具有哺乳动物的一般特征之外,还会飞,我们可以定义一个IFlyable的接口,表示飞行的动作,而蝙蝠需要实现这个接口。
组合(Composition) 也表示整体与部分的关系,但部分离开整体后无法单独存在。因此,组合与聚合相比是一种更强的关系。如:我们的电脑由CPU、主板、硬盘、内存组成,电脑与CPU、主板、硬盘、内存是整体与部分的关系,但如果让CPU、主板等组件单独存在,就无法工作,因此没有意义。
聚合(Aggregation) 是整体与部分的关系,部分可以离开整体而单独存在。如:一个公司会有多个员工,但员工可以离开公司单独存在,离职了依然可以好好地生活。
依赖(Dependency) 是一种使用的关系,即一个类的实现需要另一个类的协助,所以尽量不要使用双向的互相依赖。如:所有的动物都要吃东西才能活着,动物与食物就是一种依赖关系,动物依赖食物而生存。

4. SOLID设计原则
SOLID是面向对象设计(OOD)的五大基本原则的首字母缩写组合,由俗称“鲍勃大叔”的RobertC.Martin在《敏捷软件开发:原则、模式与实践》一书中提出来。这些原则结合在一起能够指导程序员开发出易于维护和扩展的软件。这五大原则分别是:S—单一职责原则,O—开放封闭原则,L—里氏替换原则,I—接口隔离原则,D—依赖倒置原则。
4.1 单一职责原则
单一职责原则,即Single Responsibility Principle,简称SRP。
一个类应该有且仅有一个原因引起它的变更。这句话这样说可能不太容易理解,解释一下。类T负责两个不同的职责(可以理解为功能):职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,可能会导致原本运行正常的职责P2功能发生故障,这就不符合单一职责原则。这时就应该将类T拆分成两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2存在故障风险;同理,当修改T2时,也不会使职责P1存在故障风险。
一个类只负责一项功能或一类相似的功能。当然这个“一”并不是绝对的,应该理解为一个类只负责尽可能独立的一项功能,尽可能少的职责。就好比一个人的精力、时间都是有限的,如果什么事情都做,那么什么事情都做不好;所以应该集中精力做一件事,才能把事情做好。
4.2 开放封闭原则
开放封闭原则,即Open Close Principle,简称OCP。
软件实体(如类、模块、函数等)应该对拓展开放,对修改封闭。
在一个软件产品的生命周期内,不可避免会有一些业务和需求的变化,我们在设计代码的时候应该尽可能地考虑这些变化。在增加一个功能时,应当尽可能地不去改动已有的代码;当修改一个模块时不应该影响到其他模块。
4.3 里氏替换原则
里氏替换原则,即Liskov Substitution Principle,简称LSP。
所有能引用基类的地方必须能透明地使用其子类的对象。
一个类T有两个子类T1、T2,凡是能够使用T对象的地方,就能使用T1的对象或T2的对象,这是因为子类拥有父类的所有属性和行为。通俗来讲,只要父类能出现的地方子类就能出现(就可以用子类来替换它)。反之,子类能出现的地方父类不一定能出现(子类拥有父类的所有属性和行为,但子类拓展了更多的功能)。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
4.4 接口隔离原则
接口隔离原则,即Interface Segregation Principle,简称ISP。
客户端不应该依赖它不需要的接口。用多个细粒度的接口来替代由多个方法组成的复杂接口,每一个接口服务于一个子模块。
类A通过接口interface依赖类C,类B通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类C和类D必须去实现它们不需要的方法。
通俗来讲建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为不同类别的类建立专用的接口,而不要试图建立一个很庞大的接口供所有依赖它的类调用。接口尽量小,但是要有限度。当发现一个接口过于臃肿时,就要对这个接口进行适当的拆分。但是如果接口过小,则会造成接口数量过多,使设计复杂化。所以接口大小一定要适度。
4.5 依赖倒置原则
依赖倒置原则,即Dependence Inversion Principle,简称DIP。
高层模块不应该依赖低层模块,二者都该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类,细节是指具体的实现类。也就是说,我们只依赖抽象编程。把具有相同特征或相似功能的类,抽象成接口或抽象类,让具体的实现类继承这个抽象类(或实现对应的接口)。抽象类(接口)负责定义统一的方法,实现类负责具体功能的实现。
面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。
面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。
4.6 总结
- 单一职责原则告诉我们实现类要职责单一。用于指导类的设计,增加一个类时使用单一职责原则来核对该类的设计是否纯粹干净。也就是让一个类的功能尽可能单一,不要想着一个类包揽所有功能。
- 里氏替换原则告诉我们不要破坏继承体系。用于指导类继承的设计,设计类之间的继承关系时,使用里氏替换原则来判断这种继承关系是否合理。只要父类能出现的地方子类就能出现(就可以用子类来替换它),反之则不一定。
- 依赖倒置原则告诉我们要面向接口编程。用于指导如何抽象,即要依赖抽象和接口编程,不要依赖具体的实现。
- 接口隔离原则告诉我们在设计接口的时候要精简单一。用于指导接口的设计,当发现一个接口过于臃肿时,就要对这个接口进行适当的拆分。
- 开放封闭原则告诉我们要对扩展开放,对修改封闭。开放封闭原则可以说是整个设计的最终目标和原则!开放封闭原则是总纲,其他四个原则是对这个原则的具体解释。
5. 其它实用原则
5.1 迪米特法则
LoD原则(Law of Demeter)每一个逻辑单元应该对其他逻辑单元有最少的了解:也就是说只亲近当前的对象。只和直接(亲近)的朋友说话,不和陌生人说话。简单地说就是:一个类对自己依赖的类知道的越少越好,这个类只需要和直接的对象进行交互,而不用在乎这个对象的内部组成结构。例如,类A中有类B的对象,类B中有类C的对象,调用方有一个类A的对象a,这时如果要访问C对象的属性,不要采用类似下面的写法:,而应该是。
至于getCProperties怎么实现是类A要负责的事情,我只和我直接的对象a进行交互,不访问我不了解的对象。
大家都知道大熊猫是我们国家的国宝,而为数不多的熊猫大部分都生活在动物园中。动物园内的动物种类繁多,展馆布局复杂,如有鸟类馆、熊猫馆等。假设某国外领导人来访华,参观我们的动物园,他想知道动物园内叫“贝贝”的大熊猫年龄多大,体重多少。他难道要先去调取熊猫馆的信息,然后去查找叫“贝贝”的这只大熊猫,再去看它的信息吗不用,他只要问一下动物园的馆长就可以了。动物园的馆长会告诉他所有需要的信息,因为他只认识动物园的馆长,而且他并不了解动物园的内部结构,也不需要去了解。
5.2 KISS原则
Keep It Simple and Stupid
保持简单和愚蠢。
这一原则正如这句话本身一样容易理解。“简单”就是要让你的程序能简单、快速地被实现;“愚蠢”是说你的设计要简单到傻瓜都能理解,即简单就是美!
如果你的程序设计得太复杂,有些成员可能无法理解这种设计的真实意图,而且复杂的程序讲解起来也会增加沟通成本。为什么说愚蠢呢同样需求的一个软件,每个人都有自己独特的思维逻辑和实现方式,因此你写的程序对于另一个人来说就是个陌生的项目。所以你的代码要愚蠢到不管是什么时候,不管是谁来接手这个项目,都能很容易地被看懂。
5.3 DRY原则
Don’t repeat yourself
不要重复自己:不要重复你的代码,即多次遇到同样的问题,应该抽象出一个共同的解决方法,不要重复开发同样的功能。也就是要尽可能地提高代码的复用率。
要遵循DRY原则,实现的方式非常多。
- 函数级别的封装:把一些经常使用的、重复出现的功能封装成一个通用的函数。
- 类级别的抽象:把具有相似功能或行为的类进行抽象,抽象出一个基类,并把这几个类都有的方法提到基类去实现。
- 泛型设计:Java中可使用泛型,以实现通用功能类对多种数据类型的支持;C++中可以使用类模板的方式,或宏定义的方式;Python中可以使用装饰器来消除冗余的代码。
5.4 YAGNI原则
You aren’t gonna need it, don’t implement something until it is necessary.
你没必要那么着急,不要给你的类实现过多的功能,直到你需要它的时候再去实现。这个原则简而言之为—只考虑和设计必需的功能,避免过度设计。只实现目前需要的功能,在以后需要更多功能时,可以再进行添加。如无必要,勿增加复杂性。软件开发首先是一场沟通博弈。它背后的指导思想就是尽可能快、尽可能简单地让软件运行起来。
5.5 三次法则
Rule of three
“三次法则”,指的是当某个功能第三次出现时,再进行抽象化,即事不过三,三则重构。这个准则表达的意思是:第一次实现一个功能时,就尽管大胆去做;第二次做类似的功能设计时会产生反感,但是还得去做;第三次还要实现类似的功能做同样的事情时,就应该去审视是否有必要做这些重复劳动了,这个时候就应该重构你的代码了,即把重复或相似功能的代码进行抽象,封装成一个通用的模块或接口。
5.6 CQS原则
Comm and Query Separation
查询(Query):当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质;
命令(Command):当一个方法要改变对象的状态的时候,它就具有命令的性质。
通常,一个方法可能是单纯的查询模式或者是单纯的命令模式,也可能是两者的混合体。在设计接口时,如果可能,应该尽量使接口单一化(也就是方法级别的单一职责原则)。保证方法的行为严格的是命令或者查询,这样查询方法不会改变对象的状态,没有副作用;而会改变对象的状态的方法不可能有返回值。也就是说,如果我们要问一个问题,就不应该影响到它的答案。
原则的实际应用要视具体情况而定,需要权衡语义的清晰性和使用的简单性。将Command和Query功能合并入一个方法,方便了客户的使用,但是降低了清晰性。
这一原则尤其适用于后端接口设计,在一个接口中,尽量不要又有查数据又有更新(修改或插入)数据的操作。在系统设计中,很多系统也是以这样的原则去设计的(如数据库的主从架构),查询功能和命令功能的系统分离,有利于提高系统的性能,也有利于增强系统的安全性。
[1]程杰.大话数据模式[M].北京:清华大学出版 ,2007.
[2]罗伟富.人人都懂设计模式:从生活中领悟设计模式:Python实现[M].北京:电子工业出版 ,2019.
文章知识点与官方知识档案匹配,可进一步学习相关知识Python入门技能树首页概览211739 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!