Blog of Samperson

软件工程与计算II重点整理(第16-19章)

2017-12-21

第16章 设计模式

1.如何实现可修改性、可扩展性、灵活性

教材263页
需要进行接口和实现的分离:通过接口和实现该接口的类;通过子类继承父类
注意:继承关系(A+B)可能使得灵活性下降,因为父类接口的变化会影响子类,这时可以通过组合关系来解决。

利用抽象类机制实现可修改性和可扩展性:只要方法的接口保持不变,方法的实现代码是比较容易修改的,不会产生连锁反应。通过简单修改创建新类的代码,就可以相当容易地做到扩展新的需求(不用修改大量与类方法调用相关的代码。
利用委托机制实现灵活性:继承的缺陷:一旦一个对象被创建完成,它的类型就无法改变,这使得单纯利用继承机制无法实现灵活性(类型的动态改变)。利用组合(委托)机制可以解决这个问题

2.策略模式

设计分析:
1)首先,可以把上下文和策略分割为不同的类实现不同的职责。上下文Context类负责通过执行策略实现自己职责;而策略类Strategy只负责复杂策略的实现。
2)其次,上下文类和策略类之间的关系是用组合代替继承。
3)最后,各种策略则在具体策略类(ConcreteStrategy)中提供,而向上下文类提供统一的策略接口。(客户通常会创建一个ConcreteStrategy对象,然后传递给Context来灵活配置Strategy接口的具体实现)
应用场景:
需要定义同一个行为的多种变体(可以是一个类的行为,也可是多个类的行为)
一个类定义了很多行为,而且通过switch语句进行选择。
相关原则:SRP,OCP+LSP+DIP,使用组合
缺点:代码难以理解(违反显式原则);需要考虑concreteStrategy由谁来创建的问题

可修改性、可扩展性
符合DIP原则的抽象类(继承)机制:通过定义抽象类与继承(实现)它的子类强制性地做到:接口与实现的分离,进而实现上述质量;强制性地使用抽象类起到接口的作用,强制性地使用子类起到实现的作用
灵活性:组合(委托)机制,动态调整所委托的类,实现灵活性

3.抽象工厂模式

学会区分简单工厂,工厂方法,和抽象工厂
简单工厂:使用一个工厂对象,通过if-else等方式实例化需要的对象
工厂方法:一个抽象方法creator(可以在原来的类中),使用子类继承creator所在的类通过实现creator方法来实例化需要的对象(实例化推迟到子类)。
抽象工厂:为应对灵活性要求,提供2套接口:一是表现出稳定的工厂行为的工厂接口,二是表现出稳定产品行为的产品接口。从而,实现了工厂多态和产品多态。
需要的是产品组合。有一个抽象工厂,该抽象工厂有所有种类产品的create(),
不同的产品组合拥有不同的具体工厂(继承抽象工厂,实现所有的create()).
(1)相关原则:不要重复,封装,OCP+LSP+DIP。可能违法了显式原则。
(2)应用场景:抽象工厂模式可以帮助系统独立于如何对产品的创建、构成、表现。
抽象工厂模式可以让系统灵活配置拥有某多个产品族中的某一个。
一个产品族的产品应该被一起使用,抽象工厂模式可以强调这个限制。
如果你想提供一个产品的库,抽象工厂模式可以帮助暴露该库的接口,而不是实现。
(3)应用注意点:隔离了客户和具体实现。客户可见的都是抽象的接口。
使得对产品的配置变得更加灵活。
可以使得产品之间有一定一致性。同一类产品可以很容易一起使用。
但是限制是对于新的产品的类型的支持是比较困难。抽象工厂的接口一旦定义好,就不容易变更了。
而这个场景的“代价”,或者是“限制”,是一个工厂中具体产品的种类是稳定的。

4.单件模式

为了实现只创建一个对象,首先要让类的构造方法变为私有的;然后只能通过getInstance方法获得Singleton类型的对象的引用。
相关原则:封装,而且使用了全局变量

5.迭代器模式

通过Aggregate创建Iterator
对于遍历这件事情,主要有2个行为:1)是否有下一个元素;2)得到下一个元素。所以,我们设计迭代器接口hasNext()和next(),分别对应前面2个行为。有了这两个接口,就可以完成遍历操作。迭代器提供的方法只提供了对集合的访问的方法,却屏蔽了对集合修改的方法,这样就对我们把集合作为参数可以做到对集合的“值传递”的效果。
体现的原则:针对接口编程;SRP; ISP(接口分离);OCP+LSP+DIP
应用:在同一个集合上进行多种方式的遍历

【题型】给出场景,应用设计模式写出代码
【题型】给出代码,要求用设计模式改写

信息隐藏——策略模式、单件模式
对象创建——抽象工厂、单件模式
接口编程——迭代器模式

策略模式——减少耦合、依赖倒置
抽象工厂——职责抽象、接口重用
单件模式——职责抽象
迭代器模式——减少耦合、依赖倒置

第17、18章 软件构造和代码设计

1.构造包含的活动

详细设计;编程;测试;调试;代码评审;集成与构建;构造管理。

2.名词解释

(1)重构:修改软件系统的严谨方法,它在不改变代码外部表现的情况下改进其内部结构
(2)测试驱动开发:测试驱动开发要求程序员在编写一段代码之前,优先完成该段代码的测试代码。
测试代码之后,程序员再编写程序代码,并在编程中重复执行测试代码,以验证程序代码的正确性。
(3)结对编程:两个程序员挨着坐在一起,共同协作进行软件构造活动

3.给定代码段示例,对其进行改进或发现其中的问题

复杂决策:使用有意义的名称封装复杂决策,例如对于决策“ If( (id>0) && (id<=MAX_ID))”,可以封装为“If ( isIdValid(id) )”,方法isIdValid(id)的内容为 “return ((id>0) && (id<=MAX_ID) )”
数据使用
(1)不要将变量应用于与命名不相符的目的。例如使用变量total表示销售的总价,而不是临时客串for循环的计数器。
(2)不要将单个变量用于多个目的。在代码的前半部分使用total表示销售总价,在代码后半部分不再需要“销售总价”信息时再用total客串for循环的计数器也是不允许的。
(3)限制全局变量的使用,如果不得不使用全局变量,就明确注释全局变量的声明和使用处。
(4)不要使用突兀的数字与字符,例如15(天)、“MALE”等,要将它们定义为常量或变量后使用。
明确依赖关系:类之间模糊的依赖关系会影响到代码的理解与修改,非常容易导致修改时产生未预期的连锁反应。对于这些模糊的依赖关系,需要进行明确的注释

4.契约式设计

契约式设计又称断言式设计,它的基本思想是:如果一个函数或方法,在前置条件满足的情况下开始执行,完成后能够满足后置条件,那么这个函数就是正确、可靠的。见书上示例P308
(1)异常
(2)断言:assert Expression1( : Expression2)
Expression1 是一个布尔表达式,在契约式设计中可以将其设置为前置条件或者后置条件;Expression2 是一个值,各种常见类型都可以;
如果Expression1 为true,断言不影响程序执行;如果Expression1 为false,断言抛出AssertionError 异常,如果存在Expression2 就使用它作为参数构造AssertionError。

5.防御式编程

优点:显著提高可靠性
防御式编程的基本思想是:在一个方法与其他方法、操作系统、硬件等外界环境交互时,不能确保外界都是正确的,所以要在外界发生错误时,保护方法内部不受损害。与契约式设计有共同点,又有很大的差异。共同点是他们都要检查输入参数的有效性。差异性是防御式编程将所有与外界的交互都纳入防御范围,如用户输入的有效性、待读写文件的有效性、调用的其他方法返回值的有效值……
编程方式:异常与断言。

输入参数是否合法?
用户输入是否有效?
外部文件是否存在?
对其他对象的引用是否为NULL?
其他对象是否已初始化?
其他对象的某个方法是否已执行?
其他对象的返回值是否正确?
数据库系统连接是否正常?
网络连接是否正常?
网络接收的信息是否有效?

6.表驱动

对于特别复杂的决策,可以将其包装为决策表,然后用使用表驱动编程的方式加以解决。示例见书上P307

7.单元测试用例的设计

MockObject创建桩程序

第19章 软件测试

1.白盒测试和黑盒测试的常见方法,并比较优缺点【必考】

(1)黑盒测试:基于规格的技术,是把测试对象看做一个黑盒子,完全基于输入和输出数据来判定测试对象的正确性,测试使用测试对象的规格说明来设计输入和输出数据。
常见方法:等价类划分(把输入按照等价类划分,包括有效和无效);边界值分析;决策表;状态转换。
(2)白盒测试:基于代码的技术,将测试对象看成透明的,不关心测试对象的规格,而是按照测试对象内部的程序结构来设计测试用例进行测试工作。

2.能解释并区别白盒测试三种不同的方法【必考】

语句覆盖、分支覆盖和路径覆盖。教材329页
语句覆盖:确保被测试对象的每一行程序代码都至少执行一次
条件覆盖:确保程序中每个判断的每个结果都至少满足一次
路径覆盖:确保程序中每条独立的执行路径都至少执行一次

【题型】给出一个场景,判断应该使用哪种测试方法,如何去写(JUnit基本使用方法)
给出功能需求——写功能测试用例
给出设计图——写集成测试用例,Stub和Driver
给出方法描述——写单元测试用例,MockObject

链接

👉软件工程与计算II重点整理(第1-5章)
👉软件工程与计算II重点整理(第6-7章)
👉软件工程与计算II重点整理(第8-11章)
👉软件工程与计算II重点整理(第12-15章)
👉软件工程与计算II重点整理(第20-23章)