简述
承接上一篇博客,这篇是关于lab3的实验思路的,也就是我是如何一步一步思考,最终完成lab3的。话不多说,先上表。
总体任务
通读lab3实验手册之后,我将总体任务划分为如下几块:
- PlanningEntry部分
- Board部分
- API部分
- APP部分
- 314Change
同时,手册建议/要求我们在某些地方使用一些设计模式,包括:
- 在实现个性化特点时使用装饰器模式
- 在实现状态转变时使用状态模式
- 在设计API时使用外观模式
- 使用工厂方法模式来创建具体的计划项对象
- 使用迭代器模式为Board类增加迭代器
- 设计API时使用策略模式,便于Client切换实际算法
依据我lab2实验心得给出的设计过程:
确立UML类图模型--->写好每个类的AF、RI与方法的spec(这一步需要花费很多时间,但是尤为重要)--->写Junit测试用例--->填代码
我将据此逐步展示我当时的思路。
确定UML类图模型
面向接口设计、以便于Client调用。这里的Client是广义的,不仅仅包括直面用户的客户端、也包括想要使用某个类的调用者,也视为Client。
首先根据手册要求大致将需要的类划分一下:
- 实验手册已经帮我们确定了一些类:Resource、Timeslot、Location、State、PlanningEntry、CommonPlanningEntry
- 因为我选择实现的是航班、高铁、活动
所以自然地又抽象出3个类:FlightEntry、TrainEntry、ActivityEntry - 实现Board功能的类
- 实现API功能的类
- 需要APP类将所有功能综合到一起,并提供给Client接口,以方便调用
接下来需要根据实际需求更近一步地细化抽象:
-
首先手册中要求我们对PlanningEntry进行泛型化处理,用R表示泛化资源类型,表现为
PlanningEntry<R>
关于泛型的使用详解,大家可以参考下面的这篇博客:
这是来自csdn的一篇关于泛型用法的博客,总结地很全,可以点击查看 -
Resource类
表示计划项所使用的资源,不同的计划项资源也不同,但都是Immutable的,所以我就将Resource设计成一个Interface,然后再设计三个具体实现类:Plane、Train、Material。这样便可满足要求了,而且可以直接用Resource来具体化PlanningEntry<R>
中的R -
Timeslot类
因为三个计划项都会用到时间段,所以我们直接用一个Timeslot类来表示即可,并设计成Immutable -
Location类
与Timeslot类相似,直接用一个Location类来表示地点的概念即可,也设计成Immutable -
PlanningEntry类
根据面向接口设计原则,我首先将PlanningEntry设计成Interface;
再根据本篇博客开头的那张表可见这三个计划项有部分共同点、也用部分特性,所以我将CommonPlanningEntry设计成具体计划项类的共同父类,并实现PlanningEnrty接口;public class CommonPlanningEntry<R> implements PlanningEntry<R>
最后,设计3个具体计划项类,继承CommonPlanningEntry类:
public class FlightEntry<R> extends CommonPlanningEntry<R>
public class TrainEntry<R> extends CommonPlanningEntry<R>
public class ActivityEntry<R> extends CommonPlanningEntry<R>
-
State类
首先根据手册要求,转变状态要求使用状态设计模式,而我在lab3实验心得(一)这篇博客中已经给出了设计模式的学习途径。
这是菜鸟教程中状态模式的说明,可点击查看
这是状态模式UML图:主要意思就是将State设计成Interface,各种不同的具体状态类Implements这个State接口;
Context是需要添加状态的类,把State添加进它的Field中,并提供Getter与Setter
据此,我设计出State接口与多个具体实现类
举例ALLOCATED和BLOCKED以展示它们之间的关系:
-
Board类
一方面,3种计划项的Board展示的内容相差较大;另一方面,它们又有一些共同的Fields
//所有的计划项
protected final Set<PlanningEntry<Resource>> planningEntries;
所以,我将Board设计为一个抽象类,然后再设计3种具体计划项Board来extends
-
API
实验手册要求使用策略模式
在这里,我只是将判断是否存在Location冲突用2种算法实现,所以我将PlanningEntryAPIs设计为抽象类,检查地点冲突设计abstract methodpublic abstract boolean checkLocationConflict(List<PlanningEntry<Resource>> entries);
然后在2种API中分别用不同算法实现:
目前的API类关系图如下:
-
APP
APP其实就是将所有的功能都综合在一起,并统一提供对外接口。我将APP设计成Interface,再让3个具体APP类implements它。
-
进一步细化抽象类,并确定类之间的关系
上一步中,我们已经大致将各个类抽象出来了,但是并不完整,还有些功能并不完备,所以在这一步中我们需要根据要求进一步补充类。
-
如何实现计划项个性化特点
- 计划项的资源数目、地点数目、时间段数目都不一样怎么处理?
我直接在CommonPlanningEntry中使用list来存储这些信息了,简单粗暴。当然也有其他更好的存储方案,我就不逐一列举了。
protected final List<Location> locations; protected final List<Timeslot> timeslots; protected List<R> resources;
- 高铁计划项可以Block,如何实现?
手册中给出了6种实现个性化特点的方案,我在这里选用的是CPR:
我设计了一个blockable接口,以及它的实现类blockableImpl,再让高铁计划项实现这一接口
- 活动计划项可以更改Location,如何实现?
直接设计一个接口,再让活动计划项实现该接口即可public interface ChangeableLocation
public class ActivityEntry<R> extends CommonPlanningEntry<R> implements ChangeableLocation
-
写好每个类的AF、RI,方法的Spec
-
确定每个类需要包含哪些方法
这个就根据手册的要求来即可,自己灵活设计,能够满足需求即可 -
这些方法需要传入哪些参数
除去构造方法,类的method主要就2类:Observer和Mutator- observer:一般不需要参数
- mutator:将新的Location、State等对象作为参数传入method,用来对相应的Filed进行修改即可
-
写类的AF、RI;方法的Spec
就按照王忠杰老师的ppt中示例代码的格式来写,这一步很花时间(我这一步好像用了2天时间),但是也尤为重要,不可忽视!
写好Junt测试
依据前面写好的AF、RI写黑盒测试(我们的课程只要求了黑盒测试)
然后可以利用EclEmma测试代码的覆盖度,当然黑盒测试的覆盖度可能不会太高。
关于此工具的使用,大家可以参考一位博主在博客园中发表的博客:
不仅能测试覆盖度,还能导出HTML报告呢!点击此处可查看
填代码
到了最后的填代码阶段了,这一阶段其实就是根据方法的Spec去完成各个方法;根据类的AF、RI写好所有类的Fields。这当然需要一定的java知识积累,同样,也建议采用需求驱动的原则去学习相应的知识点。
拿我写Timeslot基本类做例子,这个类表示时间段,需要2个Fileds,分别表示开始时间和结束时间。
我在此之前了解到的时间相关的类仅有Date、Calendar,但是上课时老师说过这2个类不安全,于是我便搜索java中Immutable的时间类,最终找到LocalDateTime类,这是java8新增的时间类,Immutable,使用起来很方便。
同时可以利用Java8在线API文档查询它的各种Method:
这便是Java8在线API文档,中文翻译版,可能有部分语句由于中文翻译的缘故读起来不太通畅
大家也可以去查阅英文原版文档:
这是官网的原版java8 API文档下载网站
总结
lab3任务太多了,根据前面的设计步骤来一步步做,我也做了5周。但是经过lab3的历练,我觉得不仅提升了自己的java编程能力;更重要的,是自主总结、自主根据需求去搜寻相关资料来学习、最终将自学的内容付诸于实践的独立学习能力。另外,lab3的最终完成,也表明前面的设计过程框架是可行的!
确立UML类图模型--->写好每个类的AF、RI与方法的spec(这一步需要花费很多时间,但是尤为重要)--->写Junit测试用例--->填代码