这部分是软构复习篇的最后一篇啦!主要讲述的如何面向另一个质量指标:可维护性实现以很小的代价适应软件需要的变化。并结合之前所讲的可复用性将二者结合,给出面向这两个质量指标的一些设计模式
可维护性的常见度量指标
圈/环复杂度
衡量代码结构的复杂性
CC = Edge – Node + 2 = P +1 = 区域数
代码行数
可维护性指数
继承的层次数
类之间的耦合度
单元测试的覆盖度
聚合度与耦合度(力求高内聚、低耦合)
耦合度(Coupling)
衡量模块间的独立性(独立性指两个模块如果其中一个改变也许另一个也要改变的性质)
决定模块聚合度的两个指标
- 模块间的接口数目
- 每个接口的复杂度
聚合度(Cohesion)
衡量一个模块中函数或表示之间相关度的强弱
如果一个模块中所有的元素都向着一个共同的目标努力则具有很高的聚合度
上述这张图很形象的帮我们图像化了高内聚和低耦合的概念
SOLID(面向对象的设计规则)
抽象:模块之间通过抽象分离,将稳定部分和容易变化的部分分开
分离:保持责任单一
单一责任原则(SRP)
含义:ADT中不应有多与一个原因让其发生变化,否则就应该拆分
开放-封闭原则(OCP)
- 类应该开放扩展性
- 模块行为可拓展,从而可以表现出新的行为以满足需求变化
- 但是对修改封闭
- 模块自身的代码不应被修改
- 扩展模块行为的一般途径是修改模块的内部实现
- 如果一个模块不能被修改,那通常被认为是具有固定的行为
通过抽象来实现!!!
Liskov替换原则(LSP)
子类型必须可替换其基类型
派生类必须可以通过其基类的接口使用,客户端无需了解二者差异
依赖转置原则(DIP)
高层模块不应该依赖于低层模块,二者都应该依赖于抽象
抽象不应该依赖于实现细节,实现细节应该依赖于抽象
委托的时候要通过接口建立联系,而非具体子类!!!
接口聚合原则(ISP)
不能强迫客户端依赖于不需要的接口,只提供必须的接口
将接口进行进一步剖分
语法、正则表达式
语法操作符
- 连接:x ::= y z
- 重复:x ::= y* (x匹配至少0个y)
- 选择:x ::= y | z (x匹配y或z)
- 可选:x ::= ybsp;(x匹配y或者空串)
- 出现至少一次:x ::= y+ (x匹配至少1个y)
- 一个字符类(只能选给出的字符):x ::= [a-c] (x匹配a/b/c)
- 一个反字符类(除了其中的字符其他都可以选)::x ::= [^a-c](除a/b/c都可选)
*先级最高,连接次之,|最低
正则语法:简化之后可以表达未一个产生式而不包含任何非终止节点
正则表达式:去除引 和空格,让表达更简洁
正则表达式中的特殊操作符
- . :任何单个的字符
- d:任何数字,等同于[0-9]
- s:任何一个空白符,包括空格、tab、newline
- w:任何一个词符,包括下划线,等同于[a-zA-Z_0-9]
- ., ,, *, +, … :避免与操作符混淆,转义符
设计模式
设计模式是对于在软件设计中给定上下文下而经常发生的问题的一种通用的、可复用的解决方案
面向对象设计模式更强调多个类/对象之间的关系和交互过程——比接口/类复用的粒度更大
这一部分中,我们要根据启发问题出发,这样可以更好的理解我们设计模式的初衷
创建型模式
关心对象创建的过程
Factory Method
定义一个创建对象的接口,让子类决定实例化哪种类
适用条件:当客户不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例
有新的具体产品类时,可以增加新的工厂类或修改现有工厂类而不会影响客户端代码(OCP)
优点
剔除不必要的指定应用绑定
用户只需要处理对应的抽象接口,并可以直接应用于所用用户定义的子类
缺点
每增加一种产品就需要增加一个新的工厂子类
结构型模式
处理类或对象间的组成(composition)
Adapter(适配器模式)
将某个类/接口转换为client期望的其他形式,解决类之间接口不兼容的问题。
通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类
其实就是开发者定义一个新的抽象类,Adapter通过实现这个抽象类从而作为已有类和用户端需求之间的一个适配
Decorator(装饰器模式)
启发问题:为对象增加不同侧面的特性
解决方案:对每一个特性构造子类,通过委派机制增加到对象上
既使用子类型也使用委派,以递归的方式实现
接口定义装饰物执行的公共操作
Decorator抽象类是所有装饰类的基类,里面包含的成员变量component指向被装饰的对象
ConcreteDecorator类是实际可用增加特性的装饰器类。
与继承的比较:
- 装饰器在运行时构建特征;而继承在编译时构建
- 装饰器由多种复合对象组成;而继承只产生单个类型清晰的对象
- 装饰器可以混合、匹配多个装饰器;而多层继承很困难
行为类模式
Strategy(策略模式)
启发问题:有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法
实现方式:为不同的实现算法构造抽象接口,利用delegation,在运行时动态传入client倾向的算法类实例
Template method(模板模式)
启发问题:客户共享相同的步骤但是具体方法不同。通用的步骤不应该在子类中被复制但是需要被复用。
解决方法:共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
利用继承和重写实现模板模式
模板模式被广泛用于框架当中
Iterator(迭代模式)
启发问题:客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型。不管对象被放入哪里,都应该提供同样的遍历方式
解决方法:迭代
模式结构
- 抽象迭代器:定义了遍历协议
- 实例迭代器:实现的具体遍历类(实现接口中的三个方法)
- Aggregate接口:创建一个迭代器对象的实例
- Aggregate实例:保持了对迭代器对象的引用
hasNext()
next()
remove()
Visitor()
启发问题:对特定类型对象的特定操作,在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类
解决方案:将数据和作用于数据上的某些特定操作分离开来
Visitor与Iterator
迭代器:以遍历方式访问集合数据而无需暴露其内部表示,将”遍历“这项功能delegate到外部的迭代器对象
Vistor:在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法而不影响ADT
Strategy与Vistor
Strategy强调的是对ADT内部某些要实现的功能的相应算法的灵活替换,这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类
而Vistor强调外部对于ADT的操作,ADT内部只需要开放即可
设计模式间的共性和差异性
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!