面向复用的软件构造

本文探讨了软件复用的概念,包括其目的、衡量标准和不同级别,如代码、模块和API层面的复用。重点讲解了白盒和黑盒复用策略,并介绍了面向复用编程的挑战和益处。此外,文章还讨论了设计原则,如LSP(里氏替换原则)、协变和委托,以及如何设计可复用的类和框架。强调了在设计系统级复用时,API库和框架的角色,以及如何在实际开发中有效地利用和管理这些资源。
摘要由CSDN通过智能技术生成

目录

软件复用

主要目的

如何衡量复用度

复用的级别

代码层面

白盒复用

黑盒复用

源代码层面

模块层面

复用类

API层面

框架层面

设计框架

设计可复用的类

LSP

协变

泛型中的LSP

委托和组合

Interface Comparator

委派

设计系统级复用:API库和框架


软件复用

使用已有的软件模块实现或者更新软件系统。

两个方面:面向复用编程(开发出可复用的软件);基于复用编程:利用已有的可复用软件搭建应用系统。

例如,在实验2中对接口Graph分别用边和顶点进行实现就是面向复用编程,而之后的写诗和朋友圈则是基于复用编程。

主要目的

降低成本和开发时间;经过充分测试,可靠、稳定;标准化,在不同应用中保持一致。

但仍然需要更多思考,因为相比于只为了单独使用设计程序,面向复用编程需要考虑更多东西,要考虑是否易于扩展,是否易于使用,这样的成本还是高的。另外,基于复用编程,为了适配写好的程序,也需要做一些改动。但是随着复用的数量增加,代价增长比独立进行开发的代价增长缓慢得多,如下图所示。

可复用组件:被使用的产品规模越大,数量越多,复用的成本越低。

开发可复用的软件:开发成本高于一般软件的成本:要有足够高的适应性;性能差些:针对更普适场景,缺少足够的针对性。

使用已有软件进行开发:要对可复用软件库进行有效的管理;往往无法拿来就用,需要进行适配(添加额外的功能,删除不需要的功能,修改已有的功能)

如何衡量复用度

复用的机会有多频繁?复用的场合有多少?

复用的代价有多大?搜索、获取;适配、扩展;实例化;与软件其他部分互联的难度

高复用性:小、简单;与标准兼容;灵活可变;可拓展

复用的级别

代码层面------最主要的复用

但是软件构造过程中的任何实体都可能被复用。(需求,设计/规约,数据,测试用例,文档)

代码层面

源代码:方法,叙述等等

模块:类和接口

库:API

结构:框架

白盒复用

源代码可见,可修改可扩展,可定制化程度高。增加了软件复杂度, 需要对内部充分的了解。

黑盒复用

源代码不可见,不能修改,只能通过API接口来使用,无法修改代码,简单清晰;适用性差些(规约可能不符合要求)。

源代码层面

最低级别的复用。维护性问题,高风险性。

研究:如何从互联网上快速找到需要的代码段

反向研究:如何从源代码中检测出克隆代码

网站:github, searchcode

模块层面

复用类

使用jar包进行封装,使用时那这个jar加入到classpath中,静态链接。

继承,缺点:只能增加方法属性,不能减少。

委托:当前类调用另外一个类中的方法

API层面

一种是直接使用类库中的方法(实现逻辑在用户),另一种是框架(依照这个框架实现相应方法)。

类库:开发者构造可运行软件实体,其中涉及到对可复用库的调用。

框架:框架作为主程序加以执行,执行过程中调用开发者所写的程序。

框架层面

框架:一组具体类、抽象类、及其之间的连接关系。只有“骨架”,没有“血肉”。

开发者根据框架的规约,填充自己的代码进去,形成完整系统。

设计框架

白盒框架:通过代码层面的继承进行框架扩展

黑盒框架:通过实现特定接口/delegation进行框架扩展

设计可复用的类

LSP

子类型多态:客户端可用统一的方式处理不同类型的对象。

规则:如果对于类型T的对象x,q(x)成立,那么对于类型T的子类型S的对象y,q(y)也成立。

子类型可以增加方法,但不可删 

子类型需要实现抽象类型 (接口、抽象类)中所有未实现的方法

子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant的参数

子类型中重写的方法必须使用同样类型的参数或者符合contra-variant的参数(此种情况Java目前按照重载overload处理)

子类型中重写的方法不能抛出额外的异常

更强的不变量

更弱的前置条件

更强的后置条件

限制:前置条件不能强化,后置条件不能弱化,不变量要保持,子类型方法参数:逆变,子类型方法的返回值:协变,异常类型:协变

协变

沿着同一个方向变化,逆变与之相反

在Java中数组是协变的:对于T[]数组,可以保存T及其子类型的数据。

泛型中的LSP

泛型是类型不变的。

ArrayList<String>是List<String>的子类型

List<String>不是List<Object>的子类型

类型擦除:类型参数在编译后被丢弃,在运行时不可用。

没有限定,T直接替换为Object

 但是在运行时类型查询只适用于原始类型

可以采用通配符实现两个泛型类的协变。

上限通配符:? extends Object1,Object1的子类型及Object1。(extends也可代表implements)

无界通配符:?(情况1:方法的实现不依赖于类型参数(不调用其中的方法),如List中的方法;情况2:或者只依赖于Object类中的功能)

下限通配符:? super Object2,Object2的父类型及Object2


      
      
  1. List<? extends Integer> intList = new ArrayList<>();
  2. List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>

一个类型变量如果有多个限定(类或接口),则它是所有限定类型的子类型;如果多个限定中有类(至多只允许有一个类),则要写到声明的最前面。

委托和组合

Interface Comparator<T>

排序委托comparator进行,重写了compare方法。

还有另外一种是用这种类本身实现Comparator,也就不需要构建新的类,这样就不是委派。

委派

定义:一个对象请求另一个对象的功能。

委派是复用的一种常见形式。

显式委派:直接调用该方法

隐式委派:通过语言查找规则

很多设计模式将继承和委托结合使用

在实验2中对Graph的复用就是一种委托机制。

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

组合优先于继承。组合时委派的一种形式。

委托发生在Object层面,继承发生在Class层面

 接口之间进行聚合,一个接口实现这个聚合,然后从接口中派生具体类,这个具体类中再委派两个对象去实现聚合中的接口的行为,可通过外部进行指定。获取了聚合的接口的具体的类,接下来对组合中的方法进行重写,这里是进行了委托。

委派的种类(都支持一对多的委派)

依赖:A调用B(临时性的)

关联:A中有B作为成员变量(永久性的)

组合和聚合都是联系的具体形态

组合:更强的关联,但难以变化。也就是在A进行创建时,B在A的内部就生成了对象,不依靠外部进行传参,那么当A消亡时,B也不复存在。

聚合:更弱的关联,可动态变化,这里B就是从外部获得的,A消除之后,B依然存在。

设计系统级复用:API库和框架

对于类库,主动权在用户这里,由用户进行调用第三方库。

API是程序员最重要的资产和“荣耀”,吸引外部用户,提高声誉

白盒框架:可以通过继承来直接复用,可以重写一些方法(对给出的框架进行扩展)是一种继承机制。

黑盒框架:通过接口进行实现,框架内部实现了接口之间的逻辑,需要用户实现接口。并不是像白盒框架那样框架本身进行实现,而是委托了一个接口,那么用户要做的就是实现这个接口。是一种委派机制,通过组合的形式实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值