封装
对于封装的理解就是提供有限必要的方法给调用者,而不是全部让调用者自行选择,随意修改任何东西。比如在 Java 中, public 、protected、private 就可以实现对变量的访问控制,实现封装。函数也是封装。
举例: 封装的好处,比如在一个类中,不加以任何限制,调用者可以随便修改类的属性,这样不仅会增加调用者上手的难度,并且还容易出错,某些变量可能和具体业务有关,并不能随意修改。就好比一个冰箱,只给你常见的几个按键让你使用,和给你一堆按键让你使用,后者出错的概率大大增加,也会增加上手的难度。
邓庄也叫做信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。
抽象
抽象就是隐藏方法的具体实现,让使用者只需要关心提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类方法实现。抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动返回;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。
定义类或命名类的方法的时候,也需要有抽象思维,不要再方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。
举例: 某个函数名为 getCdnPictureUrl()
就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储到 cdn 是哪个,而是存储在私有云商,那这个命名也要随之被修改。相反,如果我们定义一个比较抽象的函数,比如 getPictureUrl()
,那即便内部存储方式修改了,我们也不需要修改命名。
继承
继承可分为单继承和多继承。继承主要是用来解决代码复用的问题。
继承虽然可以解决代码复用的问题,但是如果继承层次过深,也会影响到代码的可维护性,在这种情况下,我们应该尽量少用,甚至不用继承。另一方面,我们鼓励多用组合少用继承。
组合并非完美,继承并非一无是处。在实际的开发中,根据具体情况进行选择,如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们可以大胆的使用继承。反之,我们尽量使用组合来替代继承。
多态
多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种类型也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。
基于接口而非实现编程
函数的命名不能暴露任何实现细节。比如
upload_to_aliyun()
就不符合要求,应该去掉aliyun
这样的字眼,改为更加抽象的命名方式,比如:upload()
封装具体的实现细节。比如,跟阿里云相关的特殊上传(或下载)流程不应该暴露给调用者。我们对上传(或下载)流程进行封装,对外提供一个包含所有上传(或下载)细节的方法,给调用者使用。
为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现来编程。
总结:在定义接口的时候,不要暴露任何实现希捷。接口的定义只表明做什么,而不是怎么做。而且,在设计接口的时候,我们要多思考一下,这样的接口设计是否足够通用,是否能够做到在替换具体的接口实现的时候,不需要任何接口定义的改动。
单一职责原则
一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计力度小、功能单一的类。单一职责是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
不同的应用场景、不同阶段的背景需求、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。但是如果出现以下情况则不满足单一职责原则:
- 类中的代码行数、函数或者属性过多
- 类依赖的其他类过多,或者依赖类的其他类过多
- 私有方法过多
- 比较难给类起一个合适的名字
- 类中大量的方法都是集中操作类中的某几个属性
对扩展开放、对修改关闭
添加一个新功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。在同样代码改动,粗代码粒度下,可能被认定为“修改”;在细粒度代码下,可能又被认为“扩展”。对扩展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性。
里式替换(LSP)
父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但是它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法,是一种代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。
接口隔离原则
对于“接口”可以有不同的理解,可以是一组接口集合、也可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不是强迫其他调用者也依赖这部分不会被用到的接口。
如果把“接口”理解为单个API接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
如果把“接口”理解为OOP中的接口,也可以理解为面向对象编程语言中的接口语法。那么接口设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。
控制反转
控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制, 而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
依赖注入
依赖注入和控制反转恰恰相反,它是一种具体的编程技巧。我们不通过new的方式在类中创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
依赖注入框架
我们通过依赖注入框架提供的扩展点,简单配置一下所有需要的类及其与类之间依赖关系,就可以实现由框架来自动创建对象、管理对象的声明周期、依赖注入等原本需要程序员来做的事情。
依赖反转原则
依赖反转原则也叫做依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,他们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
KISS 原则 && YANGI 原则
KISS原则是保持代码可读性和可维护的重要手段。KISS 原则中的 “简单” 并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。 YANGI 原则告诉我们不要过度设计
- 不要使用同事可能不懂的技术来实现代码
- 不要重复造轮子,要善于使用已有的工具类库
- 不要过度优化
DRY 原则
实现逻辑重复、功能语义重复、代码执行重复。实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违背了 DRY 原则。除此之外,代码执行重复也算是违背了 DRY 原则。
- 提高代码可复用性的一些方法:
减少代码耦合
满足单一职责原则
模块化
业务与非业务逻辑分离
通用代码下沉
继承、多态、抽象、封装
应用模板等设计模式
我们在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本较高,那我们就不需要考虑代码的复用性。在之后开发新功能的时候,发现可以复用之前写的这段代码,那我们就重构这段代码,让其变得更加可复用,Rule of Three 原则。
相比于代码的可复用性,DRY原则适用性更强一些。我们可以不写可复用的代码,但一定不能写重复的代码。
高内聚、低耦合
高内聚、低耦合是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“低耦合”用来指导类与类之间依赖关系的设计。
所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓低耦合指的是,在代码中,类与类之间的依赖关系简单清晰。即使有两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。
迪米特法则
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。
Web 开发总结
我们目前做的 Web 项目开发,大部分都是基于贫血模型的 MVC 三层架构,是传统的开发方式,之所以称之为“传统”,是相对于比较新兴的基于充血模型的 DDD 开发模式来说。基于贫血模式的传统开发模式,是典型的面向过程的编程风格。相反,基于充血模型的 DDD 开发模式,是典型的面向对象的编程风格。
对于业务不复杂的系统来说,基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD 开发模式有点大材小用,无法发挥作用。相反,对于业务复杂的系统开发来说,基于充血模型的 DDD 开发模式,因为在前期需要在设计上投入更多时间和精力来提高代码的复用性和可维护性,所以相较于贫血模型的开发模式,更加有优势。
以上仅作为理论知识,不包含例子,不适合初学者学习,适合复习。