哈工大软件构造期末复习3

4.1 Metrics, Morphology and External Observations of Reusability
1. 面向复用编程
面向复用编程(programming for reuse):开发可以复用的软件

基于复用编程(programming with reuse):复用已有的软件开发

为了降低成本和开发时间,提出了面向复用的编程,所有面向复用的代码都应该经过充分的测试,以保证它的可靠性和稳定性(不能在未来使用的时候发现一堆bug,那就白干了),而因为它是面向复用的,所以在不同的应用里可以保持一致的表现,也就是说对此功能做了标准化。

可复用性的评估

评估的方面:复用的频繁性、复用的代价 (适配)

一个有高可复用性的代码应该有如下特点:小、简单;与标准兼容;灵活可变;可扩展;泛型、参数化;模块化;变化的局部性;稳定;丰富的文档和帮助。

2. 复用的层面
最主要的复用是在代码层面,这也是我们所关注的,但软件构造过程中的任何实体都可能被复用(需求、spec、数据、测试用例、文档等等)

源代码层面:方法、语句…

模块层面:ADT (类和接口)

库层面:API,如.jar文件

架构层面:框架

复用分为白盒复用和黑盒复用,白盒复用意味着源码是可见的,对我们来说意义不是很大,更多的是源码不可见的黑盒复用,只有这样才能隔离客户端和ADT的内部实现。

源代码层面的复用
可以在网络上寻找自己需要的代码,但要注意开发商用的软件不能直接复制开源的代码,避免引起法律纠纷。

模块层面的复用
通过继承 (Inheritance) 的方式复用父类的代码,同时也可override父类中已存在的方法。
另一个复用的方法是 委托(delegation),详见下一小节(4.2)。
库层面的复用
通过导入库来调用库中的API完成复用。

除了导入本地库,也可以通过导入部署在网络上的库来完成复用,如 Web Services / Restful APIs

架构层面的复用
框架:一组具体类、抽象类、及其之间的连接关系。开发者可以根据spec填充自己的代码从而形成完整的系统。开发者根据Framework预留的接口所写的程序,而Framework作为主程序加以执行,执行过程中调用开发者所写的程序。关于框架详见下一小节4.2.3。

黑盒框架:通过实现特定接口/delegation进行框架扩展
白盒框架:通过代码层面的继承进行框架扩展
4.2 Construction for Reuse
1. Liskov替换原则(LSP)
子类型多态
子类型多态:客户端可用统一的方式处理不同类型的对象。

LSP
Liskov Substitution Principle中子类重写父类的方法应该满足的条件:

编译器在静态类型检查时强制满足的条件

子类型可以增加方法,但不可删除
子类型需要实现抽象类型中的所有未实现方法
子类型中重写的方法返回值必须与父类相同或符合co-variance(协变)
子类型中重写的方法必须使用同样类型的参数或者符合contra-variance(逆变)的参数
子类型中重写的方法不能抛出额外的异常
还应该满足的条件

更强的不变量 (RI)
更弱的前置条件
更强的后置条件
协变
关于返回值的类型,应该保持不变或者变得更具体,也就是与派生的方向一致。

所抛出的异常的类型也是如此。

逆变

关于参数的类型,应该保持不变或者变得更抽象,也就是与派生的方向相反

类型擦除(泛型中的LSP)
泛型类型是不支持协变的,如ArrayList<String> 是List<String>的子类型,但List<String>不是List<Object>的子类型。这是因为发生了类型擦除,运行时就不存在泛型了,所有的泛型都被替换为具体的类型。

但是在实际使用的过程中是存在能够处理不同的类型的泛型的需求的,如定义一个方法参数是List<E>类型的,但是要适应不同的类型的E,于是可使用通配符?来解决这个需求。

2. 组合与委托

委派/委托:一个对象请求另一个对象的功能。

实现比较功能还有另一种方式,让ADT实现Comparable<T>接口然后override该接口的comparaTo()方法,但是这种方法就不再是delegation了。

选择继承还是委派?

如果子类只需要复用父类中的一小部分方法,可以不需要使用继承,而是通过委派机制来实现,也就是说一个类不需要继承另一个类的全部方法,通过委托机制调用部分方法,从而避免大量无用的方法。

四种委派方式

  • Dependency:依赖关系,临时性的delegation。把被delegation的对象以参数方式传入。只有在需要的时候才建立与被委派类的联系,而当方法结束的时候这种关系也就随之断开了。

  • Association:关联关系,永久性的delegation。被delegation的对象保存在rep中,该对象的类型被永久的与此ADT绑定在了一起。

  • Composition: 更强的association,但难以变化。也就是Association中的法二。

  • Aggregation: 更弱的association,可动态变化。也就是Association中的法一。

组合 Composite Reuse Principle(CRP)

利用delegation的机制,将功能的具体实现与调用分离,在实现中又通过接口的继承树实现功能的不同实现方法,而在调用类中只需要创建具体的子类型然后调用即可。组合就是多个不同方面的delegation的结合。

抽象层是不会轻易发生变化的,会发生变化的只有底层的具体的子类型,而具体功能的变化(实现不同的功能)也是在最底层,所以抽象层是稳定的。而在具体层,两个子类之间的委派关系就有可能是稳定的也有可能是动态的,这取决于需求和设计者的设计决策。

上图中所存在的子类与父类的替换只有在满足LSP的前提下才能存在,不满足LSP就没有这种delegation机制了。

3. 框架framework

黑盒框架:通过实现特定接口进行框架扩展,采用的是delegation机制达到这种目的,通常采用的设计模式是策略模式(Strategy)和观察者模式(Observer)
白盒框架:通过继承和重写实现功能的扩展,通常的设计模式是模板模式(Template Method)。
白盒框架所执行的是框架所写好的代码,只有通过override其方法来实现新的功能,客户端启动的的是第三方开发者派生的子类型。黑盒框架并不是这样,黑盒所预留的是一个接口,在框架中只调用接口中的方法,而接口中方法的实现就依据派生出的子类型的不同而不同,它的客户端启动的就是框架本身。

4.3 Design Patterns for Reuse

六种设计模式:Adapter、Decorator、Facade、Strategy、Template method、Iterator

1. Adapter适配器模式

目的是将某个类/接口转换为client期望的其他形式。通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。

因为Adaptee是不匹配客户端所需求的,可能是参数上的,所以此时就需要一个中间件来做客户端和Adaptee之间的适配工作,这就是Adapter,它接受客户端的功能请求但是不会做具体的功能实现,而是把客户端所提供的参数转换成Adaptee所接受的形式,然后将任务委派给Adaptee完成。

2. Decorator装饰器模式

每个子类实现不同的特性,因为这些特性都是多个维度上的个性化的特征,没办法做到在一个顶层的接口中完成所有特征的抽象,而且需要做到在各个维度上的特性的任意组合,此时光靠继承是没办法实现特性的组合的,如果要强行使用继承实现,那么面对的一个不可避免地问题是组合爆炸,因为每次继承只能扩展一个特性,多个特性就要多次继承实现,并且也不便于维护与扩展,而且会有大量的重复代码。

因此,提出了Decorator设计模式,为对象增加不同侧面的不同特性。

装饰器模式的原理是从接口派生出子类,然后在子类中定义一个父类接口然后将其作为delegation的对象,也就是说:自己到自己的委派。这里第一个自己指的是子类型本身,第二个自己是指该接口的其他子类,由于他们两个是同一个接口的子类,所以可以称为自己到自己的委派。

下图中,Component是一个接口,接口中是公共的特性,ConcreteComponent是这个接口的基本实现,没有任何个性。Decorator是接口的一个抽象实现,它解决了委派关系的建立问题,从它派生出的诸多子类可以实现各个单独的特性而不必考虑所需要的其他特性如何在本类中实现,这些问题都通过delegation机制交给了其他子类完成。

3. Facade外观模式

客户端在调用的API时候会以固定的方式调用一系列的方法,而为了简化客户端的使用,便于客户端的学习使用、解耦,所以需要提供一个统一的接口来取代一系列小接口调用,对复杂系统做了一个封装。

经过封装得到的一个方法通常是静态方法,因为客户端可以直接调用这个方法而没必要new一个对象。

4. Strategy策略模式

有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。因此可以为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。

ConcreatestrategyA和ConcreateStrategyB是Strategy接口的两种不同的实现,客户端在运行时可选择任意一种来完成功能。在方法中只需要留出一个Strategy接口类型的参数,客户端选择具体类型后传入即可。

5. Template Method模板模式

做事情的步骤一样,但具体方法不同,因此共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。使用继承和重写实现模板模式。

在模板中,实现了一个固定执行一系列操作的方法,这个方法使用final关键字做了限定,不能再被子类重写,因此,子类只能通过重写该方法调用的那些尚未实现的方法。

6. Iterator迭代器模式

客户端希望遍历被放入容器/集合类的一组ADT对象,而无需关心容器的具体类型,也就是说,不管对象被放进哪里,都应该提供同样的遍历方式

实现方式是在ADT类中实现Iterable接口,该接口内部只有一个返回一个迭代器的方法,然后创建一个迭代器类实现Iterator接口,实现hasnext()、next()、remove()这三个方法。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值