面向软件构造期末考试程序设计题的复习
目录
1 设计spec
1.1 spec的重要性
spec是程序员自己对所写的方法的规约,它规定了方法应该做什么,不应该做什么。spec时是测试优先编程的基础,因为程序员所编写的代码必定是符合spec的,否则就是不合格的,所以符合spec的代码也必然能通过根据spec所设计出的测试。同时,有了spec,客户端在使用所写的代码时就有所依据,客户端可以轻松的知道他需要为这个方法提供什么样的参数,以及会得到什么样的结果,而不必知道内部逻辑是怎么样的,大大节省了客户端使用自己的API时所需要的时间,并且降低了客户端对自己所编写的代码的误解。
1.2 spec的结构
-
方法的功能;
-
前置条件,即方法参数的限制条件,使用@param说明每个参数的前置条件;
-
后置条件,即当前置条件满足时,方法结束时必须满足的条件,使用@return说明正确的返回值,即后置条件;使用@throws说明出现什么样的错误情况会导致什么样的异常。
1.3 spec中不能包含的部分
spec不能暴露实现细节,不应该暴露局部变量,也不应该暴露私有的数据域,这些东西一旦暴露,就有可能给被非法的程序员利用,发现漏洞并实施攻击。
【注】方法不应该改变输入参数的取值,如果改了,则必须在spec中做出说明。所以推荐使用immutable的对象,以及使用immutable类作为返回值类型。
2 ADT的设计
ADT的设计包括spec的设计、rep的设计以及implementation的设计,spec的设计上面已经提到,下面将介绍一些ADT相关的问题。
2.1 RI
Rep
Invariant,即不变量集合,是R空间所有值的子集,它包含了所有合法的表示值,而只有满足RI的值,才是合法值,才会在A空间内有值与其对应。我们通常通过写checkRep()来随时检查RI是否满足。使用assert检查RI,在所有的方法最好都加入调用这个检查方法。checkRep()在检查时有可能耗费大量的时间影响性能,所以只需要在开发阶段保留这部分。
checkRep检查包括参数大小、参数是否为空、参数长度、参数中是否有
2.2 表示泄露
即client可以拿到数据域的本身或别名。一旦表示泄露,client就有可能无意间改动数据,而如果在设计中,要求一个ADT是Immutable的,而如果它出现了表示泄露,就有可能违反Immutable的原则。一般来说,方法中的变量如果是public的,那么很有可能会出现表示泄露。若某些getter函数并未采用防御式拷贝,则变量有可能被外部指针所引用,导致表示泄露。
2.2 AF
Abstraction
Function,即从R空间到A空间存在一个映射,这个映射是一个满射,是将R中的每一个值解释为A中的一个值的映射。这个映射的解释函数就是AF。
3 面向可维护性的设计模式
3.1 Visitor
对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
首先,设计相应扩展的Visitor接口,其中包括visit方法,该方法的参数为要扩展的方法类型,并且可复用。然后,设计一个CacuVisitor,实现Visitor接口中的方法,满足要扩展的功能条件。接着,在被扩展的方法中增加accept方法,即accept(Visitor
visitor) { visitor.visit(this);
},即将自己传给visit。最后,在客户端调用accept(new CacuVisitor())即可。
3.2 Strategy策略模式
有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。因此可以为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。
首先,设计对应的扩展Strategy接口,接口中有相应的被扩展的方法select,该方法的参数与扩展前相同。然后,创建两个或多个不同的CacuStrategy方法,内部使用不同的策略来实现Strategy接口中的方法,分别满足原策略条件和要扩展的策略的功能条件。接着,在被扩展的方法参数中增加一个Strategy接口类型的参数,并将调用原策略方法的代码改为strategy.select(原参数)。最后,在客户端调用扩展方法时(原参数,new
CacuStrategy)即可。
3.3 Iterator迭代器模式
客户端希望遍历被放入容器/集合类的一组ADT对象。
实现方式是在ADT类中实现Iterable接口,该接口内部只有一个返回一个迭代器的方法,然后创建一个迭代器类实现Iterator接口,实现hasnext()、next()、remove()这三个方法。
首先实现Iterator<遍历对象>的接口,然后实现iterator()方法,即public
Iterator<遍历对象> iterator() { return new CacuIterator();}
接着创建内部类来实现该接口:
private class CacuIterator implements
Iterator<Car>,在该类中要实现next(),hasNext(),remove()三个方法。
3.4 Decorator装饰器模式
每个子类实现不同的特性,因为这些特性都是多个维度上的个性化的特征,没办法做到在一个顶层的接口中完成所有特征的抽象,而且需要做到在各个维度上的特性的任意组合,此时光靠继承是没办法实现特性的组合的,如果要强行使用继承实现,那么面对的一个不可避免地问题是组合爆炸,因为每次继承只能扩展一个特性,多个特性就要多次继承实现,并且也不便于维护与扩展,而且会有大量的重复代码。
首先创建一个类来实现接口的方法,然后再创建一个类,来继承刚刚创建的那个类,并在此类中添加修饰。可以在继承后的类中添加rep,即参数。
3.5 Factory Method工厂模式
解决的问题:当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。也可以通过直接定义静态工厂方法来创建子类实例。
3.6 Adapter适配器模式
将某个类/接口转换为client期望的其他形式。通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。因为Adaptee是不匹配客户端所需求的,可能是参数上的,所以此时就需要一个中间件来做客户端和Adaptee之间的适配工作,这就是Adapter,它接受客户端的功能请求但是不会做具体的功能实现,而是把客户端所提供的参数转换成Adaptee所接受的形式,然后将任务委派给Adaptee完成。
后续将继续添加。