软件工程(三)

第八章 设计优化

一 小即是美

(那我可能丑到爆炸了)

1 设计的“味道”(由内味了)

僵化性,脆弱性,顽固性,粘滞性
不必要的复杂性,不必要的重复性,晦涩性

2 设计的优化

1.运行时的多态:多态性在结构上形成类的继承层次
2.重写的要求:
重写的方法本质上与父类方法具有相似的行为,但在细节上进行了有针对性的调整
重写的方法与原方法在相同的条件作用下工作,子类的方法不应具有比其父类更严格的条件限制
重写的方法最高不能超出父类方法的状态
3.耦合的消息链:交互集中的设计与交互分散的设计
4.狎昵关系:两个类间表现出过分亲密的关系,即高耦合
5.被拒绝的遗赠:
一是借助继承的方式。但当子类只需要重用父类的某些功能,对父类的行为并不想保持一致,这就是拒绝的遗赠现象,预示着继承层次的误用,应考虑第二种方式进行扩展
二是通过所谓的委托(Delegation) 进行,其基本形式是新类与原有类构成关联关系,即具有一个它的实例变量
6.循环依赖:如果两个类分别处于不同的包中,并且具有双向导航的关联关系,这样的循环依赖好像是不可避免的

二 设计原则

1.接口隔离原则(ISP)

(鄙人理解就是对操作进行封装,相当于电脑,只需要关心如何使用,不用关心内部具体实现,而且更换各种配置也相对简单【比如把有线鼠标换成无线】)
1)含义:
a。应尽量使用 “ 接口继承” ” ,而非 “ 实现继承 ” 。接口关注对象的概貌,将对象中 “不变 ” 的信息抽象出来,不涉及细节,因此是 “ 稳定 ” 的
b。通过 接口只将需要的操作 “ 暴露 ” 给客户类,而将不需要的操作隐藏起来。接口在这里充当类的视图
c。好处就是当业务需求变化时,更容易发生改变的是具体类,而这些变更可以通过稳定的抽象类进行隔离,使得Client 不受变化的影响,从而提高了系统的可维护性
2)面向接口的作用:
a。面向接口的设计能够使Client 只需关注如何进行业务活动(如:驾驶),而不必关心其使用对象的具体实现
b。一个对象可以很容易地被(实现了相同接口的)另一个对象所替换,这样对象间的连接不必硬绑定(hard wire )到一个具体类的对象上,因此增加了灵活性
c。这是一种松散的耦合,同时增加了重用的可能性

2. 依赖倒置原则(DIP)

1)宗旨:应依赖于抽象,而不要依赖具体

  1. 扩展的基础越具体,扩展的难度也越大,具体类的变化无常势必造成扩展类的 不稳定
  2. 依赖倒置原则使细节和具体实现都依赖于抽象,抽象的稳定性决定了系统的 稳定性
  3. 一个基础稳定的系统要比一个基础不稳定的系统在整体上要更 “ 稳定 ”(感觉像在放屁)
    (应该就是留点发挥空间,增加灵活性)

3. 开放封闭原则(OCP)

(比如三个类:interface good,milk implements good,现在要对milk进行打折,保持原来代码不变的基础上,新增一个类,discountmilk,在里面实现对于milk打折的操作)
1)开放封闭原则(The Open-Closed Principle, OCP):一 个模块对扩展应是开放的,而对修改应封闭的
1.这条原则是面向对象思想的最高 境界,即设计者 应给出对于需求变化进行扩展的模块,而永远不需要改写已经实现的内部代码或逻辑 。
2.特点:
a。模块 的行为可以被扩展,以需要满足新的需求
b。模块 的源代码是不允许进行改动的
3.OCP 是相对的,没有绝对符合OCP 的设计,而且一个软件系统的所有模块不可能都满足OCP ,要 做的 是尽量 最小化 不满足OCP 的模块数量

4. Liskov替换原则(LSP)

(这个比较难理解,举个栗子:爹会的儿子必须会,儿子还能会爹不会的,作为新时代的弄潮儿,儿子出生可以选择的方向比爹多【输入宽松】,但是步入社会的需要的结果比爹严格的多【输出更加具体】)
1)Liskov 替换原则(Liskov Substitution Principle, LSP):任何 出现父类的地方应能使用子类对其进行无条件的替换,即当使用子类对其父类进行替换时,该组件仍象替换前一样正常工作

  1. LSP 要求对象间的继承关系既与静态属性相关又与动态行为相关
  2. 通常规定 父类型在使用前和使用后都要具备必要的条件—— 前置条件和后置条件
  3. 当 当 子类型替换父类型 后不能 违反父类型中的前置条件和后置条件,即一个子类型不得具有比父类型更多的限制,这是因为可能对于父类型的某些使用是合法的,但是会因为违背子类型的其中一个额外限制,从而违背了LSP
    2)要求:
    1.子类必须实现父类的抽象方法,但不得重写父类的非抽象方法
    2.子类可以有自己的方法
    3.当子类覆盖或实现父类的方法时,方法的输入参数可以比父类方法的输入参数更宽松
    4.当子类覆盖或实现父类的方法时,方法的返回结果可以比父类方法的返回结果范围更严格

5. 单一职责模式(SRP)

(这个简单,就是舔狗,职责单一,舔就完事了,此处@某姓子腾)
1)单一职责原则(Single Responsibility Principle, SRP )中所谓职责,可理解为功能,就是设计的类功能应该只有一个,而不应为两个或多个 。

  1. 职责 是引起 “ 变化 ” 的原因:当一个类中有两个以上的变化方向,会产生过多的变化

6. 合成/聚合复用原则(CARP)

(聚合就是弱包含,合成就是强包含,详情见下面的借鉴)
1)合成/ 聚合复用原则(Composite/Aggregate ReusePrinciple, CARP )中的合成与聚合是两种特殊的关联关系,是以委托方式实现对象间功能的 重用(另外 一种面向对象特有的重用方式是 继承)。

  1. 委托 重用与继承重用是两种本质上不同的重用方式,委托重用追求的是对象间的独立性即低耦合,而继承重用追求的是对象间应能尽可能的高内聚
  2. 合成/ 聚合复用原则指的是应尽量使用合成/ 聚合形式的委托重用,尽量不使用继承重用
    2)借鉴
    聚合指的是弱包含关系:例如(人属于人群的一部分,而人不包含了人群)
    合成指的是强包含关系:例如(人的手臂属于这个人的一部分,而这个人也包含了这个人的手臂)

三 设计模式

重点画的是:工厂,适配器,观察者,状态

1. 抽象工厂模式

(就是相当于吧在超市的自助找货换成,在KFC的点菜,服务员帮你找餐【KFC为抽象工厂】)
1)抽象工厂(Abstract Factory )模式的主要作用是实现了客户类在创建产品类时引入的耦合,如在对具体对象创建时使用的new 操作,需要指定一个具体的产品类的名字,这样就在客户类和具体产品类之间引入了依赖关系,而这种依赖关系按照面向接口编程等原则是应该进行优化处理的
2)将产品的创建过程从客户类中分离,通过使用一个类似系统服务的工厂类来解决这个问题,工厂类提供了一个创建一系列相关或相互依赖对象的接口,而客户类无需指定它们需要的具体产品类
3)消除对象创建的耦合(就是以前写的工厂类,创建对象时,new的是factory,而不是一个具体的类

2.单例模式

(说的好听点,就是专一,一生只爱一个人;说的难听点,就是懒汉模式,只做一件事,其他的懒得做)
1)单例模式(Singleton )保证了一个类仅有一个实例,并提供一个访问它的全局访问点
要求:

  1. 类的所有构造方法都为私有的,防止其被外部创建
  2. 提供一个公有的方法获取该类的实例
  3. 类中的实例变量为私有或受保护的

3. 适配器模式

(就是字面意思,把俩不兼容的类通过适配器类强行兼容,比如转换口)

  1. 适配器模式(Adapter )把一个类的接口变换成客户类所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起
  2. 适配器 一般有两种工作方式:一种是通过委托的方式,另外一种是通过继承(接口实现)的
  3. 无论 哪种方式,适配器都可以充当被适配对象参与与客户类的交互,并可以对基本的适配功能做进一步的扩展,而这个功能扩展的作用又可以通过另外的 “ 装饰模式 ” 进一步

4. 桥模式

(把抽象部分和实现部分分离,(五花肉大法好))

  1. 桥模式的主要思想是将抽象部分与它的实现部分( 行为)进行分离,使它们都可以独立地变化
  2. 使用桥模式时,首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合
  3. 一般把业务方法和与之关系最密切的维度设计为 “ 抽象类” 层次结构( 抽象部分) ,而将另一个维度设计为 “ 实现类” 层次结构( 实现部分)

5. 装饰模式

(将桥模式中的“抽象”和“实现”合二为一,是桥模式的一种特殊情况)

  1. 一个类可能有些额外的责任( 除主体业务) ,如加密、缓存、压缩等,这些可能只是辅助主体业务的附着,并不严格按照维度变化
  2. 装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性
  3. 动态给一个对象增加功能,并可以再动态的撤消,因此增加由一些基本功能的组合而产生的非常大量的功能
  4. 装饰模式中既有继承又存在组合,实际上是将Bridge 中的抽象和实现合二为一了,是其特殊形式
    在这里插入图片描述
    Component:抽象组件,是一个接口或者抽象类;就是定义的最原始的对象
    ConcreteComponent:具体组件,实现类(需要装饰)
    Decorator:装饰角色,一般是抽象类,实现接口;它的属性必然有个private变量指向Conponent抽象组件
    ConcreteDecorator:具体的装饰对象

聚合关系用一条带空心菱形箭头的直线表示,上图表示Component聚合到Decorator上,或者说Decorator由Component组成
继承关系用一条带空心箭头的直接表示

6. 门面模式

(相当于提供了一个管家,只需要和管家说要什么,而不需要自己动手)

  1. 门面模式(Facade )要求外部与一个子系统的通信必须通过一个统一的门面对象进行
  1. 门面 模式提供了一个高层次的接口,使得子系统更易于使用。另外,每个子系统一般只要求具有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式 。
  2. 这个 时候的门面类作用 相当于前面 介绍的适配器,负责对外部请求的转发,并且可以在此基础上进行功能的扩充,如对传递进来的参数的验证等。整个系统可以有多个门面类。

在这里插入图片描述
1.更为常见的做法是系统并不提供一个门面类,而是提供一个或多个门面接口
2.因为每次提供服务只涉及一类功能,几乎不会联动使用,所以不违反单一原则精神

7. 代理模式

(就是中介)
1)代理模式(Proxy )一般用来对有价值(稀缺)资源的管理,比如数据库的连接等,目的就是为了提高这些资源的利用率或者系统性能

  1. 它给这些资源对象提供一个代理对象,并由代理对象控制对资源对象的使用,起到中介的作用
  2. 代理对象的存在使得客户类分辨不出代理对象与真实的资源对象。
    在这里插入图片描述
  3. 代理模式也可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入

8. 观察者模式

(就是提供一个可视化的试图给用户,比如空气中的湿度等)
1)观察者模式(Observer )定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象
2)当这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己
3)MVC 架构模式在实现上就使用了观察者模式,其中的主题对象就相当于MVC 中的模型,观察者对象相当于MVC 中的视图

9. 策略模式

(把算法和客户端分离,使得算法可以很好的被共享)

  1. 策略 模式(Strategy )针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换
  2. 好处是能够使得算法可以在不影响到客户端的情况下发生变化,而且将算法的行为和环境分开,环境类负责维持和查询行为类,各种算法在具体的策略类中提供

10. 状态模式

(举个例子:考试成绩60分以上为及格,其他为不及格;及格对应一个类,不及格对应一个类,学生内部有一个函数会根据成绩的不同来调整自身状态对应的类)

  1. 状态模式(State )可以看作是策略模式的一种应用,状态模式允许一个对象在其内部状态改变的时候改变行为
  2. 状态 模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类
  3. 状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变 。
  4. 状态 模式需要对每一个系统可能取得的状态创建一个状态类的子类。当系统的状态变化时,系统便改变所选的子类,从而对类在不同状态下的行为进行管理

第九章 实现技术

这章没啥东西,主要就是一个XML

一 XML

  1. XML 是由W3C 委员会定义的一种标准化语言,用来描述数据模型和数据
  2. 处理方式:文档对象模型(DOM )或用于XML 的简单API (SAX)
    DOM 是复杂对象处理的首选,比如当XML 比较复杂的时候,或者当需要随机处理文档中数据的时候
    SAX 则是以流的方式从文档的开始通过每一节点进行移动,以定位一个特定的节点。
  3. XML编写规范:书p154
    a XML 必须包含根元素,它是所有其他元素的父元素
    b XML 声明文件的可选部分,如果存在需要放在文档的第一行
    c XML 标签对大小写敏感。标签 Letter 与标签 letter 是不同的
    d XML 中,所有元素都必须彼此正确地嵌套
    e XML 中,XML 的属性值必须加引号。
错误示范
<Message>这是错误的。</message>
<b><i>This text is bold and italic</b></i>
<note date=08/08/2008>
正确示范
<message>这是正确的。</message>
<b><i>This text is bold and italic</i></b>
<note date="08/08/2008">

第十章 交互模式

这章就是恶心人的 ,画了一堆模式,主要知道什么模式叫什么就行了
当下流行的一般就是现在的office word这样的Ribbon风格,第三级菜单直接用块的方式展现,如下图
在这里插入图片描述
1.对话原则:(没啥好写的)
任务适合性
自我描述性
可控性
与用户期望一致性
易学性
容错性
可定制性

第十一章 软件测试

一 形式化验证

  1. 软件测试的 基本原理是 根据 用户需求的满足情况判断软件的质量情况

二 测试技术

  1. 三种测试方法:
    在这里插入图片描述
    a)白盒测试关注的是被测对象的内部构成细节,比如算法的结构和流程,所以多在类测试阶段采用
    b)灰盒测试一般对应在集成测试阶段中使用,因为这个过程关注的是类、包等程序单元之间的关系,而不是类内部的细节
    c)在系统或验收测试阶段一般使用的是黑盒的测试方法,这里系统内部的细节不再重要,重要的是系统的外部行为。
  2. 非功能性测试:
    在这里插入图片描述

三 软件度量***

方法复杂程度的度量:McCabe 指标
类的内聚性的度量:LCOM* (这个*是自己带的,不是强调的意思)

1. McCabe指标

1)McCabe 环形复杂度 ,以 方法的控制流程图结构为基础进行计算:边数 - 节点数 + 2
2)考虑到复合条件的情况,McCabe的 的 计算实际上反映了方法中下列语句产生的分支结构:if 语句、条件组合&& 和||for 语句和while 语句。
在这里插入图片描述
(根据这张图我猜测McCabe的值可能是分支数【比如最后面的图的McCabe的值正好为最上面节点的出度】)

2. LCOM*指标

1)度量LCOM* (Lack of Cohesion in Methods ),分析每个类中方法与实例变量之间的关系,然后通过归一化公式进行计算
在这里插入图片描述
m 为方法数,a 为所含的实例变量数,u(Aj)为访问每个实例变量的方法数
2) 当LCOM* 为0 时,该类的内聚性最佳,否则内聚性较差,需要考虑对其中的功能进行 分解。 当然如果该类只有一个唯一的实例变量,则不需要考虑它的LCOM*
在这里插入图片描述

四 等价类测试***(黑盒测试)

  1. 等价类概念:等价类是离散数学中的一个概念,其基本思想是将一个集合按照一定的标准划分为若干个子集合,其中每个元素的归属依赖于指定功能 下具体 的行为
    下图为一个划分等价类的例子:
    在这里插入图片描述
  2. 测试:
    a。一种组合方式是对于有效等价类要尽可能采用少的测试用例进行覆盖
    b。对于无效等价类则要慎重一些,其覆盖的规则是每个无效等价类必须与其它有效等价类组合测试,以此保证能够触发该无效值对应的专门处理过程
    在这里插入图片描述
    c。在等价类的基础上还可以继续应用边界值分析的方法
    d。涵盖所有可能的输入情况,这种组合方式称为强等价类方法(因为用例数量太多,不常用 )

五 基于控制流的测试(白盒测试)

1 覆盖指标

  1. 程序覆盖是提供一组测试用例尽可能使得覆盖率指标越大越好, 或者说越接近1
  2. 计算标准:语句覆盖(Statement Coverage) 、分支覆盖(Branch Coverage) 、条件覆盖(Condition Coverage) 、多条件组合覆盖(Multiple Condition Coverage) 及路径覆盖(Path Coverage)等
    1)语句覆盖:语句覆盖表示在程序控制流图中测试经过的节点数与所有节点数的比例
    (语句覆盖是一种很粗略的度量,因为它主要关注的是控制流图中的节点而非执行路径)
    在这里插入图片描述
    2)分支覆盖:分支覆盖的目标是尽可能覆盖控制流图中所有的边
    (它要求对所有程序片段间的各种可能的连接至少执行一次,因此,满足分支覆盖要求一定会满足语句覆盖要求)
    在这里插入图片描述
    3)条件覆盖:要求每个原子谓词的真假两种取值都要取到
    (条件覆盖不是根据程序的运行情况,而是根据出现的布尔条件进行测试用例的设计,条件覆盖与分支覆盖并没有直接的关系)
    注意:分支的完全覆盖不能保证条件的完全覆盖,反之也成立,即完全的条件覆盖也不能保证分支的完全覆盖
    4)多条件组合覆盖:所有的覆盖要求在多条件组合覆盖标准中得到了综合,它要求所有在条件中出现的原子谓词的组合都要覆盖到
    在这里插入图片描述
    5)路径覆盖:从方法的入口开始到出口结束,由控制流图中若干条边构成的一条唯一的执行路线,也可以看成由若干可能的逻辑条件的组合
    (由于循环这个会导致路径无限增长的憨批存在,所以只考虑有限循环次数的情况)
    基本路径测试步骤:
    a。 绘制程序的空值流图
    b。计算McCabe环形复杂度
    c。导出测试用例
    在这里插入图片描述

六 断言

1 概念

  1. 断言提供对 异常进行检查的能力,但只能在开发阶段使用,并且不能替代常规的 异常处理
  2. 断言规定了方法必须要满足的条件 ,即需求 的程序特性。 如对输入不能确定是否 满足 既定要求 ,比如在防御性程序设计 (defensive programming )中要求方法总是可用,则必须要使用异常处理而不能使用

2 测试框架

就讲了一个测试平台

  1. JUnit:JUnit 可以使用反射机制在测试环境中调用传递过来的方法, 所有某类的test 测试方法都放在一个测试类中,这个类需要从JUnit 提供的系统类TestCase 继承而来

七 可测试性:

  1. 测试的构建一般采用的方式是 “ 自底向上(Bottom Up)” 的方式 ,也就是 新的测试总是在已经经过测试的类和方法的基础上进行构建
  2. 测试进行的过程中尤其是在多人并行开发的时候经常会出现彼此依赖的情况。 为避免 等待而影响开发进度, 可以构建一个 模拟程序(mock )或桩(stub)

1 可测试性构建的原则:

  1. 设计简单的方法: 规模较小的方法的类的测试性要好于带有较少方法但每个方法的规模较大的类
  2. 避免私有方法:方法的可测试性是较差的,由于封装性,普通的JUnit 类无法直接对它们进行访问
  3. 优先使用通用方法:静态代码无法应用多态的特性,也就意味着没有代码重用,无论是对被测程序还是测试程序
  4. 组合优于继承: 类之间通过关联进行组合的关系更易于测试
  5. 避免隐藏的依赖关系与全局状态:对全局状态一定要谨慎,因为全局状态使测试的构建变得复杂,如果全局状态没能共享或者遗漏,都会导致一些意外的后果
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值