软件构造课复习笔记【5】

5-1可维护性的度量与构造原则

(1)什么是软件维护
(2)可维护性如何度量
(3)实现高可维护性的设计原则

软件维护和演化

软件维护:修复错误、改善性能,处理来自用户报告的故障/问题
除了修复问题,修改中不能引入新的故障
软件维护的评估占比:

25% 纠错性 
21% 适应性
50% 完善性  
4% 预防性 

软件演化:对软件进行持续的更新
(软件的大部分成本来自于维护阶段)

在设计与开发阶段就要考虑将来的可维护性
方法:

 模块化 
  OO设计原则 
  OO设计模式 
  基于状态 的构造技术 
  表驱动的构造技术 
  基于语法的构造技术

可维护性指标

关于可维护性的问题:
设计结构是否足够简单?
模块之间是否松散耦合?
模块内部是否高度聚合?
是否使用了非常深的继承树,是否使用了delegation替代继承?
代码的圈复杂度是否太高?
是否存在重复代码?

圈复杂度:程序独立路径的数量,即画出程序的流程图,循环等的分支的线将整个平面分为了几部分圈复杂度就是多少。
简单计算圈复杂度等于循环数+1

可维护性指数(MI)由下式计算:

其中:

模块化设计和模块化原则

模块化编程:
高内聚
低耦合
分离关注点
信息隐藏

软件由一个个模块拼接而成,模块间内部信息不互通,依靠预留的接口通信,达到信息隐藏,低耦合度的目标。

评价模块化的五个标准

1.Decomposability (可分解性)
将问题分解为各个可独立解决的子问题
目标:使模块之间的依赖关 系显式化和最小化

2.Composability (可组合性)
可容易的将模块 组合起来形成新的系统
目标:使模块可在 不同的环境下复用

3.Understandability (可理解性)
每个子模块 都可被系统设计者容易的理解

4.Continuity (可持续性) ——发生变化时受影响范围最小
规格说明小的变化将只影响一小部分模块,而不会影响整个体系结构
便于修改,迭代。

5、Protection (出现异常之后的保护)——出现异常后受影响范围最小
运行时的不正常将局限于小范围模块内
尽早发现运行错误并提示或处理

模块化设计的五大原则

1.Direct Mapping (直接映射)
模块的结 构与现实世界中问题领域的结构保持一致
影响持续性和可分解性

2.Few Interfaces (尽可能少的接口)
模块应尽可能少的与其他模块通讯
影响可持续性、保护性、可理解性、可组合性

3.Small Interfaces (尽可能小的接口)
如果两个模块通讯,那么它们应交换尽可能 少的信息
影响可持续性、保护性

4.Explicit Interfaces (显式接口)
当A与B通讯时,应明显的发生在A与B的接口之间
影响可分解性、可组合性、可持续性、 可理解性

5.Information Hiding (信息隐藏)
经常可能发生变化的设计决策应 尽可能隐藏在抽象接口后面
影响可持续性

低耦合,高聚合

耦合指模块之间的相互联系和影响。
聚合指一个模块内部的目标一致性。

SOLID设计原则

(SRP) The Single Responsibility Principle 单一责任原则
(OCP) The Open-Closed Principle 开放-封闭原则
(LSP) The Liskov Substitution Principle Liskov替换原则
(DIP) The Dependency Inversion Principle 依赖转置原则
(ISP) The Interface Segregation Principle 接口聚合原则

SRP

ADT中不应该有多于1个原因让其发生变化,否则就拆分开
一个类,一个责任

OCP

模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
但模块自身的代码是不应被修改的,如果一个模块不能被修改,那么它通常被认为是具有固定的行为
关键的解决方案:抽象技术
如一个客户端可能需要多种功能,则将通用的功能抽象成父类,对于不同的功能,使用继承,委托。包装等多种方式,产生功能不同的客户端。父类不需修改实现了修改封闭,可以派生各种子类实现了扩展开放。

LSP

子类型必须能够替换其基类型
派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异

ISP

不能强迫客户端依赖于它们 不需要的接口:只提供必需的接口
客户端不应依赖于它们不需要的方法,过于复杂的接口具有很多缺点

可分解为多个小的接口,不同的接口向不同的客户端提供服务,客户端只访问自己所需要的端口

DIP

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象

小结:
抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易变化部分分开
分离(Separation): Keep It Simple, Stupid (KISS)

GRASP设计原则

GRASP是关于如何为“类”和“对象”指派“职责”的一系列原则

5-2面向可维护性的设计模式

关于如何“创建类的新实例”的模式

工厂方法模式

当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指 明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从 而使一个类的实例化延迟到其子类。

常规情况下,client直接创建具体对象 
Product p = new ProductOne();
在工厂方法模式下: 
Product p = new ConcreteCreatorOne().factoryMethod();

工厂方法将new的动作隐藏起来,有新的具体产品类加入时,不影响客户端代码。

相比于通过构造器(new)构建对象:

  1. 静态工厂方法可具有指定的名称
  2. 不必在每次调用的时候都创建新对象
  3. 可以返回原返回类型的任意子类型

满足OCP原则。

抽象工厂

抽象工厂模式:提供接口以创建一组相关/相互依赖的对象, 但不需要指明其具体实现类。
实现方法:通过一个可调用其他多个工厂的工厂

抽象工厂模式使用组合将创建对象的责任委托给另一个类,而工厂方法模式使用继承并依赖派生类或子类来创建对象。

结构模式

Proxy(代理模式)

远程代理:为一个在不同的地址空间提供局部代表(缓存机制)
虚代理:根据需要创建开销很大的对象
保护代理:提供访问保护

代理与适配器的辨析:
Adaptor目的:消 除不兼容,目的是B以客户端期望的统一的方式与A建立起联系
Proxy目的:隔离对复杂 对象的访问,降低难度/代价,定位在“访问/使用行为”

行为模式

Observer(观察者模式)

一种“发布-订阅”形式,发布方的变化,会通知订阅方
订阅方在发布方注册
通过接口分离两者
观察者模式的分类:

发生变化时推送通知 
发生变化时推送通知和数据 
拉取数据

主体(被观察者)和观察者之间松耦合,主体不需要关注观察者
支持动态的增加和删除观察者
观察者的行为不受主体的控制
主体需要存储观察者列表
可通过在update()中增加参数来观察多个主体

Visitor(访问者模式)

对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码 可以在不改变ADT本身的情况下在需要时通过delegation接入ADT

访问者模式与迭代器的辨析:
迭代器:以遍历的方式访问集合数据而无需暴露其内部表 示,将“遍历”这项功能delegate到外部的iterator对象。
访问者模式:在特定ADT上执行某种特定操作,但该操作不 在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活 扩展/改变visitor的操作算法,而不影响ADT

Strategy与Visitor的辨析:
Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系 不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client 通过它设定visitor操作并在外部调用。
Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。 这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类 而已。
区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操 作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度, 灵活变化对其内部功能的不同配置。

设计模式的对比











5-3面向可维护性的构造技术

基于状态的构造技术

使用有限状态机来定义 程序的行为、使用状态来控制程序的执行
根据当前状态,决定下一 步要执行什么操作、执行操作之后要转移到什么新的状态

如果在ADT内部自行管理状态的转换,需要大量的if-else

基于自动机的编程

核心思想:将程序看作是一个有限状态 自动机,侧重于对“状态”及“状态转换” 的抽象和编程

程序的执行被分解为一组自动执 行的步骤
各步骤之间的通讯 通过“状态变量”进行
使用枚举类型enum或更加复 杂的数据类型定义状态
常使用二维数组定 义状态转换表

状态模式

允许在运行时修改对象的行为或状态,每个行为(一组方法构成)用一个状 态类表达
状态改变时,修改状态对象
具体执行的方法,委托给状态对象

备忘录模式

记住对象的历史状态,以便于“回滚”

需要“备份”的ADT,rep中只记录当前状态
每次“备份”都 生成一个外部的Memento对象
还有一个类负责掌控全部的状态备份,客户端通过它来操纵ADT的状态备份与恢复

语法驱动的构造

有一类应用,从外部读取文本数据, 在应用中做进一步处理。

语法基本操作符:
Concatenation 连接 x ::= y z
Repetition 重复 x ::= y | z
Union选择 x ::= y | z

  • ? +优先级最高,连接次之,| 最低
    Optional (0或1次) x ::= y?
    Optional (1次及以上) x ::= y+
    区间范围内的任意一个字符 x ::= [a-c]
    非区间范围内的任意一个字符 x ::= [^a-c]

正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点
正则表达式去除引号和空格,从而表达更简洁(更难懂)
. 表示任意一个字符
\d 任意数字
\s 任意空白符
\w 任意字母或数字
转义符 包括 ., (, ), *, +

例:‘http://’ ([a-z]+ ‘.’)+ [a-z]+ (’:’ [0-9]+)? ‘/’
简化后:http://([a-z]+.)+[a-z]+(:[0-9]+)?/

Parser generator是一个工具,根据语法规则生成一个parser程序

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值