软件构造学习笔记(5)

哈工大软件构造课程学习笔记(5)


前言

本文具体讲解了面向可复用性和可维护性的设计模式和面向正确性与健壮性的软件构造


一、面向可复用性和可维护性的设计模式

这里将讲解的设计模式分为三大类Creational patterns、Structural patterns和Behavioral patterns。

1.Creational patterns

工厂方法模式

要解决的问题:当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指
明要具体创建的实例时,用工厂方法。 

解决方案:定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从 而使一个类的实例化延迟到其子类。

常规情况下,client直接创建具体对象,但是是一种表示泄漏。

在工厂模式下,先定义一个公共的接口,之后对于每一种具体类型,用一个实现公共接口的具体的类的静态方法来产生具体类型,这样可以很好的方式表示泄漏。

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

1. 静态工厂方法可具有指定的更有意义的名称 

2. 不必在每次调用的时候都创建新的工厂对象 

3. 可以返回原返回类型的任意子类型

2.Structural patterns

(1)适配器模式

要解决的问题:解决类之间接口不兼容的问题

解决方案:通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。

本质上来讲,就是把适配器原本不适配子类的代替,适配器可以继承或者委托原本不适配的类,注意,适配器也是一个类。

(2)装饰器模式

要解决的问题:为对象增加不同侧面的特性 

解决方案:对每一个特性构造子类,通过委派机制增加到对象上  

Decorator抽象类是所有装饰类的基类,里面包含的成员变量,在抽象类中委托给被修饰的类来完成所有基础功能,具体的修饰类继承Decorator抽象类,只需按需要增加修饰。

如果客户端需要一 个具有多种特 性的object,通过一层一层的装饰来实现,就像一层一 层的穿衣服。

 3.Behavioral patterns

(1) 策略模式

 要解决的问题:有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里

解决方案:为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法
类实例

总结:先定义一个策略接口,是对所有具体策略的抽象,包含具体策略的共同的方法。之后具体的策略类只需实现接口就好。在使用策略的类中,将希望具体策略完成的算法委托给策略接口的某一个实现即可,不同实现可以用参数来区分等。

(2) 模板模式

要解决的问题:做事情的步骤一样,但具体方法不同 

解决方案:共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

总结:简单来讲就是用一个抽象类来将做事情的步骤一样,但具体方法不同 每个具体类的做事步骤抽象出来,具体类继承抽象类,差异在于每个方法的实现不同,一般会增加一个方法来按顺序调用事情的步骤的方法。

(3) Iterator

要解决的问题:客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型

解决方案:使用迭代器

总结:第一步,另要被遍历的集合实现Iterable接口,第二步实现自己的独特Iterator迭代器(hasNext, next, remove),允许客户端委托这个迭代器进行显式或隐式的迭代遍历。

 (4) Visitor

 对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。

本质上:将数据和作用于数据上的某种/些特定操作分离开来。

总结:特点就是实现一个额外的ADT(一般是接口+类),只不过这个ADT优点特殊,在正在实现的类中,使用如accept()方法来将一些特定的操作分离委托给ADT实现,而在ADT的实现过程中,还要对当前类委托,利用当前类的一些属性和数据之类的,也就是形成了双向委托。

  

二、面向正确性与健壮性的软件构造

健壮性:系统在不 正常输入或不正常外部环境下仍能够表现正常的程度

面向健壮性的编程希望:处理未期望的行为和错误终止 ;即使终止执行,也要准确/无歧义的向用户展示全面的错误信息 

正确性:程序按照spec加以执行的能力,是最重要 的质量指标!

正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)

可靠性=健壮性+正确性

可以通过平均故障间隔时间(MTBF)(平均无故障运行时间),是指相邻两次故障之间的平均工作时间,来衡量健壮性。

MTBF用于描述可修复系统的平均无故障运行时间,MTTF(故障前平均时间)描述不可修复系统的故障前平均时间

1.Error and Exception

​​​​​​​ 

这里有两种很重要的分类,分类一:

Error:程序员通常无能为力,一旦发生,想办法让程序优雅的结束,通常是一些外部的不可抗力导致的。

Exception:程序执行中的非正常事件,导致程序无法再按预想的流程执行。是你自己程序导致的问题,可以捕获、可以处理。

其中由于error难以处理,重点关注Exception。首先Exception和Error都是继承自Throwable类的,其中Exception又分为 RuntimeException和其它,单独分出RuntimeException的原因是RuntimeException比较特殊,运行时异常,是程序源代码中引入的故障所造成的,如果在代码中提前进行验证,这些故障就可以避免;而其它异常即使在代码中提前加以验证(如:文件是否存在),也无法完全避免失效发生,是程序员无法完全控制的外在问题所导致的。

这就引出了第二个重要分类:

Checked exceptions:包括Exception中除了RuntimeException的所有异常,这些既是异常,又是无法完全避免失效发生的。可以理解为需要检查的异常。

unchecked exceptions:包括Error所有和RuntimeException,可以理解为没有检查的必要,Error检查了也无能为力,RuntimeException检查的话会降低对程序员的要求,掩耳盗铃。

对与Checked exceptions,必须捕获并指定错误处理器handler,否则编译无法通过;unchecked exceptions则不需要。

处理Checked exceptions,会用到如下关键字:

(throws) 声明“本方法可能会发生XX异常”
(throw)  抛出XX异常
(try, catch, finally) 捕获并处理XX异常

注: Unchecked异常也可以使用throws声明或try/catch进行捕获,但大多数时候是不需要的,也不应该这么做,是掩耳盗铃,对发现的编程错误充耳不闻的行为。

什么时候使用Checked exceptions,什么时候使用Unchecked呢?

如果客户端可以通过其他的方法恢复异常,那么采用checked exception; 

如果客户端对出现的这种异常无能为力,那么采用unchecked exception; 

总结起来就是如下的表:

 注意:在规约中“异常”也是方法和 client端之间spec的一部分,在post-condition中刻画,使用关键字@throws, 程序员必须在方法的spec中明确写清本方法会抛出的所有checked exception,以便于调用该方法的client加以处理。

还有一些要点:

1.也可以定义自己的异常类,抛出异常的方法throw new EOFException(); 

2.异常发生后,如果找不到处理器,就终止执行程序,在控制台打印出stack trace。 可以不在本方法内处理,而是传递给调用方,由client处理(“推卸责任”) 

3.本来catch语句下面是用来做exception handling的,但也可以在catch里抛出异常,目的是更改exception的类型,更方便client端获取错误信息并处理。

4.finally关键字的优先级最高,甚至高过return,当异常发生后,剩余代码通常不会执行,但Finally部分的代码,是否捕获异常都会被执行。 

2.Assertions

首先要知道,断言是为了程序的正确性而生的,可以快速失败,避免扩散,将bug限制在最小的范围内 。

断言是在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。 

断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能(在实际使用时,assertion都会被disabled)。

形式是assert condition;或assert condition : message;

什么时候用断言呢?

断言可以用来核实:1内部不变量;2表示不变量;3控制流不变量;4方法的前置条件;5方法的后置条件。

断言主要用于开发阶段,避免引入和帮助发现bug ,实际运行阶段, 不再使用断言需要注意的是:

1.程序之外的事,不受你控制,不要乱断言,断言只是检查程序的内部状态是否符合规约,外部错误要使用Exception机制去处理。

2. Java缺省关闭断言,要记得打开(-ea) 

3.防御式编程

1.保护程序免受无效输入的影响在:对每个函数的输入参数合法性要做仔细检查,并决定如何处理非法输入 

2.设置路障:类的public方法接收到的外部数据都应被认为是dirty的,需要处理干净再传递到 private方法——隔离舱;“隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用
断言。
 

3.使用如 SpotBugs的工具。 


总结

本文具体讲解了面向可复用性和可维护性的设计模式和面向正确性与健壮性的软件构造,这些都是软件构造中的重要外部属性。本文也是软件构造学习笔记的最后一部分。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值