文章目录
本章的整体结构与上一章相似,只是内容从可复用性变为了可维护性,也是先进行介绍度量和原则,然后介绍一些设计模式和构造技术。本章内容较多,这篇博客是第一部分,重点是讲解度量和原则。
一.可扩展性的度量与构造原则
可扩展性,可维护性,灵活性,可适应性,可管理性,支持性等等这些词,含义类似,都是我们这章所讨论的内容。
1.软件的维护和运维
软件维护主要是修复错误和改善性能,是软件生命周期的最后一部分,一般都在软件发布之后,软件的大部分成本都来自于维护阶段。一个好的软件一般有很好的反馈系统。由于软件总是在不断变化和生长的,因此质量会逐渐降低,复杂性会逐渐增加。
负责处理这些的是运维工程师,是非常苦的工作。
进行修改后要进行回归测试(需要足够的文档和测试用例)
2.可维护性的度量
圈/环复杂度:不同代码独立路径的数量。测试时要将所有独立路径覆盖到。这里有一篇很好的讲解环形复杂度的博客:点此查看
代码行数:代码行数越多一般说明复杂度越高。但是目前已经是一个非常粗略的判断标准了。
当然,现在对于可维护性的度量,现在已有可维护性指数了,但是公式复杂,考虑了很多的指标。
另外还有一些不可量化的指标:例如继承的层次数(越深越不好),类之间的耦合度(越低越好,总之就是一个类的修改最好不会对其他类产生影响),单元测试的覆盖度(覆盖度越高越好)。
3.模块化设计思想和准则
总的来说,模块化设计的目标是希望模块内高内聚,模块之间高耦合,同时实现分离关注点和信息隐藏。
A.五个评价模块化效果的指标
我们可以从以下五个方面评价模块化设计是否比较好:
(1). 可分解性:模块是不是可以分解成多个更小的相互独立的模块,如果可以的话,分解成多个更小的模块更符合模块化设计原则,也就是分而治之的思想。
(2). 可组合性:可以很容易的将模块之间组合起来形成一整个大的模块(也就是说明了模块之间尽量相互独立)。
(3). 可理解性:其实就是自己设计出的模块自己非常好理解。
(4). 可持续性:小的变化只会影响模块的一小部分,不会影响这个系统。例如统一接入模式——模块提供的所有服务应该通过统一标识提供,可以提高可持续性。
(5). 出现异常之后的保护:运行时出现不正常时尽量把异常局限于小范围内,例如出现问题就抛出异常。
B.模块化设计的五个规则
我们确定了我们最终要实现的效果之后,接下来我们就要在更底层确定规则,从而实现这些效果。
(1). 直接映射:模块的结构应该尽量和现实中问题领域的结构保持一致。这有点像之前ADT的设计,其实很多时候尽量保持那种“十分明显的AF”让我们应用起来十分方便。
(2). 尽可能少的接口:这里的接口不是java中的interface,主要指的是模块与模块之间应该尽量少的通讯。(当然一个系统不可能有孤立的模块,否则就完全没用)
(3). 尽可能小的接口:同理,就是模块和模块之间应该尽量少的交流;交流太多比如参数什么的过多,一旦修改,就会互相产生很多影响。
(4). 显式接口:就是两个模块通讯时,就让它发生在这两个模块的接口之间,总之就是不要拐弯抹角,再在其他模块操作。
(5). 信息隐藏:经常发生变化的部分应该在底层,也就是隐藏在抽象接口的后面(接口在上层)。
C.耦合与内聚
模块化设计的目标是低耦合高内聚,这两者是相辅相成的,前文也多次提到过,就是模块之间联系少,模块之内内容联系十分紧密,一张形象的符合指标的图如下图所示。
4.OO设计原则:SOLID
SOLID为五个原则的首字符缩写,下面我们一一介绍。
A.单一责任原则(SRP)
内容:ADT中不应该有超过一个原因让其变化,否则就应该拆分开。也就是:一个类,一个责任。
总的来说,这其实就是让我们把“多功能”的类去拆成“单功能”的类。但一般说来,如果我们的代码比较少,也不需要有频繁的改动,那不必强制遵守这条原则,但这条原则在软件很大且需要频繁的时候,会特别有用,不遵守很有可能是的可扩展性和性能这两方面的大幅度降低。
B.开放/封闭原则(OCP)
内容:对扩展性开放,即模块的行为是可扩展的;对修改封闭,扩展不应该修改原先已有的代码。
这也是java中出现的经典的抽象类和接口的使用的“万恶之源”。我们之所以层层抽象,目的就是我们在新增一些类的时候,只需要实现一些接口,本来的调用时调用接口类型,我们在实际传入的参数为这个子类型即可。这种例子数不胜数,是初学java开始就一直被强烈推荐的。
C.Liskov替换原则(LSP)
已经在前面的博客中详细介绍过了:我是传送门
D.接口隔离原则(ISP)
内容:不能强迫客户端依赖于它们不需要的接口,只提供必需的接口。
也就是说,接口要高内聚和精炼,不贪图接口中方法很多。这样类也可以很舒服的按照自己需求实现对应的接口。
E.依赖转置原则(DIP)
内容:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
总之就是实现都是依赖于抽象,面向接口编程而不是面向应用编程。这样明显对可复用性和可扩展性都有很大的提升,与OCP相辅相成。
SOLID原则总结
其实就是如何很好的实现抽象与分离,使得类责任单一,接口稳定。
抽象:模块之间通过抽象隔离开来,将稳定部分和容易变化部分分开,对应的原则有LSP,DIP,OCP
分离: Keep It Simple, Stupid,对应的原则有SRP,ISP。
5.OO设计原则:GRASP
GRASP是关于如何为“类”和“对象”指派“职责”的一系列原则:总的来说是根据类的责任(能得到什么,能做什么等等)来确定职责的搭配。
一共有9个原则,这里就不细讲了。详细内容可参考《Applying UML and Patterns》