目录
第10,11讲考点如下:
一、面向可维护性的构造技术
1. 可维护性的常见度量指标(10.2)
1. 圈复杂度 - 度量代码的结构复杂性。
通过计算程序流中不同代码路径的数量创建的。具有复杂控制流的程序将需要更多的测试来实现良好的代码覆盖率,并且可维护性较差。
CC = E-N+2, CC=P+1, CC=number of areas
2. 代码行数 - 指示代码中的大致行数。
非常高的计数可能表示某个类型或方法正在尝试做太多的工作,应该进行拆分。它还可能表示类型或方法可能很难维护。
3. 可维护性指数- 计算一个介于0和100之间的索引值,该值表示代码的相对易维护性。
高值意味着更好的可维护性。计算依据: 1. 霍尔斯特德容积(HV)2. 圈复杂度(CC)3. 每个模块的平均代码行数(LOC) 4. 每个模块的注释行百分比(COM)
其中,Halstead Volume:基于源代码中(不同的)运算符和操作数数量的复合度量。
公式为:
4. 继承的层次数 - 指示扩展到类层次结构根的类定义数。层次结构越深,就越难理解在何处定义或/或重新定义特定的方法和字段。
5. 类之间的耦合度 - 通过参数、局部变量、返回类型、方法调用、泛型或模板实例化、基类、接口实现、在外部类型上定义的字段和属性修饰来度量与唯一类的耦合。良好的软件设计要求类型和方法具有高聚合度和低耦合度。高耦合表示由于其与其他类型的许多相互依赖性而难以重用和维护的设计。
6. 单元测试的覆盖度 - 指示自动单元测试覆盖了代码库的哪一部分。
2. 聚合度与耦合度(10.3.3)
(1)耦合是模块之间依赖性的度量。如果一个模块中的更改可能需要另一个模块中的更改,则两个模块之间存在依赖关系。
模块之间的耦合度由以下因素决定:
模块之间的接口数量(数量),以及每个接口的复杂性(由通信类型决定)(质量)
(2)聚合度是衡量模块的功能或职责有多紧密相关的一个指标。
如果模块的所有元素都朝着同一个目标工作,那么模块就具有很高的聚合度。
3. SOLID(10.4)
SOLID5个设计原则:
1. (SRP) The Single Responsibility Principle 单一责任原则
2. (OCP) The Open-Closed Principle 开放-封闭原则
3. (LSP) The Liskov Substitution Principle Liskov替换原则
4. (DIP) The Dependency Inversion Principle 依赖转置原则
5. (ISP) The Interface Segregation Principle 接口聚合原则
(1)SRP:不应该有多于1个原因让你的ADT发生变化,否则就拆分开(一个类,一个责任)
如果一个类包含多个责任:引入额外的包,占据资源;导致频繁的重新配置、部署等
(2)OCP:
对扩展性的开放:模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
对修改的封闭:但模块自身的代码是不应被修改的,扩展模块行为的一般途径是修改模块的内部实现。如果一个模块不能被修改,那么它通常被认为是具有固定的行为
关键的解决方案:抽象技术,使用接口,委托。
(3)LSP:子类型必须能够替换其基类型,派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异。
(4)ISP:不能强迫客户端依赖于它们不需要的接口:只提供必需的接口
(5)DIP:抽象的模块不应依赖于具体的模块,具体应依赖于抽象
使用接口或者抽象类
4. 语法、正则表达式(10.5)
语法的组成:用语法定义一个“字符串”。语法中的文字字符串称为终止节点、叶节点。通常表示为字符串,用引号表示,如‘http’ 或 ‘:’。
语法中的非终结符和产生式:语法由一组产生式节点描述,其中每个产生式节点定义一个非终结节点。遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串。非终结符是表示字符串的树的内部节点。
语法中的表示为 :非终结符::=终结符、非终结符和运算符的表达式
语法中的一个非终结符被指定为根。语法识别的字符串集与根非终结符匹配。此非终结符通常称为根或开始,为根节点。
语法的运算符:
1. 连接(Concatenation):不是用符号表示,而是用空格表示
x ::= y z ——x与y匹配,后跟z
2. 重复(Repetition):使用 *
x ::= y* ——x匹配零或多个y
3. 选择(Union,alternation):使用 |
x ::= y | z ——x匹配y或z
后缀运算符*、?和+具有最高优先级,之后是连接,| 的优先级最低。
附加运算符等价于三大运算符的组合:
1. 可选(0或1次出现),用?:
x ::=y? ——x是y或者是空字符串
2. 1次或多次出现:用+:
x ::=y+ ——x是一个或多个y(相当于xx ::= y y*)
3. 字符类[…],表示包含方括号中列出的任何字符的长度为1的字符串:
x ::=[a-c]相当于x ::='a'|'b'|'c'
x ::=[aeiou]相当于x ::='a'|'e'|'i'|'o'|'u'
4. 一个倒排字符类[^…],表示长度为1的字符串,其中包含括号中未列出的任何字符:
x ::=[^a-c]等价于x ::='d'|'e'|'f'|。。。(所有其他字符)
语法中的递归
1. 主机名可以有两个以上的组件,并且可以有一个可选的端口号:
http://didit.csail.mit.edu:4949/
要处理此类字符串,语法如下:
url ::= 'http://' hostname (':' port)? '/'
hostname ::= word '.' hostname | word '.' word
port ::= [0-9]+
word ::= [a-z]+
主机名现在是根据自身递归定义的。使用repeation操作符,我们也可以不递归地编写主机名,如下所示:
hostname ::= (word '.')+ word
解析树:
将语法与字符串匹配可以生成一个解析树,显示字符串的各个部分如何对应于语法的各个部分。解析树的叶子用终端标记,表示已解析的字符串部分。
如果我们把叶子连接在一起,我们就得到原来的字符串。
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点
终止节点和运算符的简化表达式可以用更紧凑的形式编写,称为正则表达式。正则表达式去除引号和空格,从而表达更简洁。
正则表达式也被简称为regex。
. 任意单个字符
\d 任意数字,与[0-9]相同
\s 任何空格字符,包括空格、制表符、换行符
\w 任何单词字符,包括下划线,以[a-zA-Z_0-9]
\., \(, \), \*, \+, ... 转义运算符或特殊字符,使其符合字面意思
二、面向可复用性和可维护性的设计模式
1. Adapter(适配器)
将某个类/接口转换为client期望的其他形式。通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。
目标:将旧组件重用到新系统(也称为“包装器”)
2. Decorator(装饰器)
用每个子类实现不同的特性,为对象增加不同侧面的特性,对每一个特性构造子类,通过委派机制增加到对象上。——防止组合爆炸
Decorator在运行时组合特性,继承在编译时组合特性
Decorator由多个协作对象组成,继承产生一个单一的、类型明确的对象
可以混合搭配多种装饰,多重继承在概念上是困难的
3. Strategy(策略)
有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里,为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。
4. Template(模板)
做事情的步骤一样,但具体方法不同,共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。使用继承和重写实现模板模式。
5. iterator/iterable
客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型
解决方案:迭代的策略模式
Iterator pattern:让自己的集合类实现Iterable接口,并实现自己的独特Iterator迭代器(hasNext, next, remove),允许客户端利用这个迭代器进行显式或隐式的迭代遍历:
6. factory method
当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。 定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
Client使用 “工厂方法” 来创建实例, 得到实例的类型是抽象接口而非具体类。有新的具体产品类加入时,可以在工厂类里修改或增加新的工厂函数 (OCP),不会影响客户端代码。
7. visitor
对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT