JAVA可复用性

一.可复用性的度量、形态与外部表现
1.可复用性简介
  虽然说复用有不同层级,但是总的来说可复用性就是我们重复使用我们已经编写好的模块。因此过程分为两部分:在某些模块编写出可复用度高的代码,然后在某些模块尽量调用这些代码。
  越抽象的东西,可复用性一般越强。因此总的来说,java中接口的可复用性大于抽象类,抽象类大于一般类。
  可复用性高显然会带来很多优点:首先编写软件的过程会很愉快(如果能很简单复用的话),可以让代码更标准,实现的更可靠等等。
  但是有时为了提高可复用性,我们也会付出很高的代价,主要是很多时候想实现好的可复用性是很难的,会有很高的开发代价,维护代价以及复用时要进行适配带来的代价。比如Lab2中,Graph这个ADT想实现很好的可复用性就很简单,但对于棋类游戏,想让这个ADT对很多种特定的棋类都有很好的可复用性,就是非常让人头疼的事情。

2.可复用性的衡量
  总的来说分为两点,一是复用的次数是否很频繁以及是否可以广泛的应用在很多情况之中(这两个有很强的正相关性,比如List可以广泛的被应用,所以理所当然的用的次数很频繁);二是复用的代价如何。可见我们并不能定量的去判断某个软件可复用性效果如何,我们只能定性的做一些观察。

3.复用的层面
  两种复用的方式:白盒复用和黑盒复用。黑盒复用就类似于调用API接口,或者方法等等;白盒复用是直接得到想要复用的源代码(或其他内容)等等,然后自己根据需要进行修改。
  根据复用内容的结构从小到大,分为如下四种复用。
  (1).源代码级别的复用:这个是最底层的复用,就是简单的复制粘贴修改。
  (2).模块级复用:以class作为最基本的单元,复用的方式有继承和委托,其中委托的耦合度明显低于继承。
  (3).类库级重用:相当于把一系列的class进行了打包,类似于第三方库的调用。
  (4).框架级复用:在框架的基础上,填充自己的代码,形成完整系统。(其实就类似于一些实验中,给出了很多代码,要求我们填入一些自己的代码)

二.面向复用的软件构造技术
1.LSP与泛型中的运用
  在我们3.4的继承,多态,泛型等等的基础上,这里我们主要介绍LSP,即Liskov替换原则。
  Liskov替换原则用数学性的语言来说,则如下图所示。

  从实际设计的角度来说,这个原则其实就是对子类型的约束:要求子类型对象完全可以当作父类型的对象来使用,其中就包括了规约的前置条件不能强化,后置条件不能弱化,要保持不变量等等。
  下面介绍协变与异变的概念。
  协变:子类对父类方法的覆写中,异常、返回值的类型是父类方法中返回值的类型的子类型,就称之为协变。
  逆变:顾名思义,与协变恰恰相反。
  因此为了复合Liskov替换原则,我们要求如果有变化的话,那异常,返回值必须是协变,参数必须是异变(当然,在java语言中参数的异变或协变导致方法不再是覆写,而是重载)。
  java中的泛型是类型不变的,如下图所示,是十分经典的例子。如果想进行协变或逆变,则需要使用通配符,例如List<? extends Number>是List < Number>的子类型,但是不论如何都要确保一个List中的元素都是一种类型的(尽管多种元素都可以放入)。

  Class为类型类,详细的用法可以参考“java中的反射”,可以得到很多信息,来验证我们上面的说法。这里就不详细说明了。

2.委托与组合
  委托顾名思义,类似于现实中我们委托别人去做一些事情,抽象到代码中,那就是在某个类的方法中,传入其他类的变量,把实现逻辑写在这个其他类的方法中,并在这个类的方法中调用。
  例如我们想通过List的复用性实现stack,如果是继承关系的话,则stack中不可避免的有很多与我们期待的数据结构不匹配的方法。因此我们应该采取委托的方式,将pop等函数里某些操作委托给list即可。
  由于继承过于严格,因此如果不是有很强的继承关系,使用委托会更合适。
  总的来说,委托有四种形式。
  (1).依赖(Dependency):逻辑即为A use B,在A的方法中通过把B当作参数来或在方法的局部中,调用B的方法,是一种临时性的委托,只有调用这个方法的时候,它们才有临时关系。
  (2).关联(Association):逻辑即为A has B,对象A中的字段中有对象B,之后的操作和前面类似;只是A与B之间就有一个相对长的关系了。
  (3).组合(Composition):逻辑即为B is part of B,A中有字段B,且在A被构造时,B就被初始化好了(A中的定义导致的,比如A中有private T B = new T(…)语句);因此它们声明周期一致。
  (4).聚合(Aggregation):逻辑即为A owns B,与(3)很相似,只是B构造的方式是通过A的构造函数的参数。因此对于B的构造,比第三种要灵活。

3.框架设计
  有两种形式:一是白盒框架,二是黑盒框架。
  白盒框架:主要采用继承的方式,通过构建子类的方式进行扩展,子类中有main函数。代码是可见的,但是带来的问题就是要对父类的代码有了解,更贴近于开发者使用。
  黑盒框架:主要采用用插件实现接口和委托的方式进行扩展,子类中没有main函数。更贴近于用户使用。
  但无论是那种框架,控制权都是在框架手上的,这其实是框架和很多类构成的一个体系的区别。
  至于如何设计好一个框架,总的来说与多个类之间的设计思想一致,只是需要更多的去了解这个框架的用途,然后更有针对性地去开发。

三.面向复用的设计模式
  总的来说,针对开发的阶段不同,设计模式分为以下三种:创建型模式、结构型模式、行为类模式。本章主要是讲解其中与可复用性密切相关的一些模式。

1.结构型模式
(1).适配器模式
  主要是用于解决类之间的接口不兼容的问题。一般用于新版本的时候重新定义了接口或者新创建了一些类,要和旧版本写适配的时候。方式还是一样有两种,分为继承和委托,不过一般委托更方便些。

(2).装饰者模式
  总的来说,就是把一个个特殊功能看成是装饰品,然后调用时通过决定加入哪些装饰品从而得到我们想要的结果,而不是通过继承的方式来得到具体的类。
  具体实现方式就以ppt上冰淇淋这个例子来举例了,这也是最简单的一个装饰着模式的例子了。
  我们对于一个冰激凌,想在它上面添加一些食料。由于不同种的食料可以组合,采用继承会组合数爆炸。因此我们可以采用如下的策略:首先定义接口,其中有我们想要有不同实现的方法:AddTopping()方法;其次,我们正常的实现没有任何装饰的类,用于委托;之后,我们定义一个装饰器基类,把没装饰的类的一个对象作为自己的一个字段(其实就是委托),然后并不实现要装饰的方法,准备留到子类;注意这个类和没有装饰的类是类似于“兄弟关系”,都是实现共同的接口。

  之后,我们再把对应的三种装饰方式实现为装饰器基类的子类,其中构造函数还是调用父类的构造函数,在待装饰的地方,就是先调用父类的操作,再加上自己独特的操作而成,这就是所谓的“装饰”。

  之后客户端的调用就顺理成章了。我们想要哪些装饰,就套入哪个装饰的构造方法即可,就类似于递归调用,因为我们在编写类的时候方法的实现就是递归的。

  不过这个模式在情况复杂的时候,写起来难度很大,逻辑上哪些应该先装饰,哪些应该后装饰还有冲突等如何处理也是个问题。

(3).外观模式
  这个模式比较简单,其实就是客户端有的时候功能组合比较多,可能用起来比较复杂。因此我们就采用外观模式:用一个相对简化,统一的接口来实现对一系列接口的组合使用。

2.行为类模式
(1).策略模式
  其实这个模式也比较直观,就是实现的代码中,会根据用户输入的不同,采取不同策略的实现方式,就比如C++中的排序sort()函数,系统其实会根据输入的不同采取不同的算法。
  当然,内部的实现可以是最简单的先评判输入,然后再生成对应的参数,调用其他接口(委托);更好的更隐蔽的方式是使用工厂模式,这个在后面的章节中会介绍。

(2).模板模式
  模板模式很适合白盒框架,其实就是在父类中定义一系列抽象方法(即为一个个流程),以及一个方法来定义这些流程的实现步骤,然后再用子类去实现这一个个流程的具体步骤。可见,这个模式和策略模式有相似的地方,就是具体也是采用不同的方法,只是保证步骤一样。

(3).迭代器模式
  不关心容器具体类型,用一种统一的方式进行遍历。
  一般来说,实现的方式如下图所示。首先,这个类要实现Iterable接口,从而要实现蓝框中的方法得到迭代器。然后,我们就可以自己去实现对应的迭代器,其中这个迭代器要实现Iterator接口。通过这种方式,客户端在遍历的时候,完全无需看到迭代器是如何工作的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值