1 实验目标概述
本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护
性的软件,主要使用以下软件构造技术:
-
子类型、泛型、多态、重写、重载
-
继承、代理、组合
-
常见的 OO 设计模式
-
语法驱动的编程、正则表达式
-
基于状态的编程
-
API 设计、API 复用
2 实验环境配置
简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。
3 实验过程
3.1 待开发的三个应用场景
应用场景:航班管理、高铁车次管理、学习日程管理
共性:
都有创建一个新计划项的功能;
都可以为对应的计划项申请资源。
都可以启动、取消、完成指定的计划项;
每个计划项都有状态,应用可以获得这个状态;
获得每个计划项的名字。
差异:
航班计划项的位置数量只有两个,包括起止点,高铁计划项的有一个起点、一组中间位置和一个终点,而学习活动只有一个位置;
这三个计划项的位置都可以提前设定,但航班计划项和高铁计划项的位置设定后不可更改,而学习活动的位置在设定后可以更改;
航班计划项的飞机资源是单个可区分的,高铁计划项的资源是多个有序排列的一组可区分的车厢资源,学习活动的资源是多个不带次序的不可区分的材料资源;
航班计划项和学习活动计划项的时间表是一个起止时间对,并且是不可阻塞的,而高铁计划项的时间表是一组起止时间对,并且可以阻塞,这三个计划项的时间在创建的时候都可以设定。
3.2 面向可复用性和可维护性的设计:PlanningEntry<R>
3.2.1 PlanningEntry<R>的共性操作
- 实现PlanningEntry<R>
方法 | 方法作用 |
---|---|
public static <R> PlanningEntry<R> flightEntry (String name, Location departure, Location arrival, String start, String end) | 静态工厂方法,创建一个航班计划项 |
public static<R> PlanningEntry<R> trainEntry (String name, List<Location> locationList, List<String> times) | 静态工厂方法,创建一个高铁计划项 |
public static <R> PlanningEntry<R> activityEntry (String name, String start, String end) | 静态工厂方法,创建一个活动计划项 |
public boolean allocate() | 将计划项状态设置为已分配状态 |
public boolean running() | 将计划项状态设置为运行状态 |
public boolean cancel() | 将计划项状态设置为已取消状态 |
Public boolean End() | 将计划项状态设置为完成状态 |
public String getName() | 获取计划项的名字 |
public String getState() | 获得计划项状态 |
- 实现CommonPlanningEntry<R>
(1)数据域:保存计划项状态的state和表示计划项名字的name。
protected State state;
private final String name;
(2)方法:
① 构造方法:把计划项的状态设为未分配(WAITING),计划项命名为name。
/**
* 创建一个通用计划项
*
* @param name 计划项的名字,非空
*/
public CommonPlanningEntry(String name) {
state = new Waiting();
this.name = name;
}
② allocate()、running()、cancel()、end()方法的实现都是调用state.nextState()方法,然后判断返回的状态是否是目的状态,如果是,返回true,否则说明不能转移到该状态,返回false。实现样例如下:
@Override
public boolean allocate() {
state = state.nextState("ALLOCATED");
if (state.state().equals("ALLOCATED"))
return true;
else
return false;
}
③ getState()方法调用state.state()方法,获得状态。
- 实现计划项集合类PlanningEntryCollection<R>
private final List<PlanningEntry<R>> entries = new ArrayList<>();
(1)数据域:计划项列表
(2)方法:
方法 | 实现 |
---|---|
public boolean addEntries(PlanningEntry<R> entry) | 判断计划项是否为空,若为空返回false,不为空则加入到列表返回true |
public List<PlanningEntry<R>> getEntries() | 用Collections.unmodifiableList()转换成不可修改的列表 |
public Iterator<PlanningEntry<R>> iterator() | 返回entries.iterator() |
public void sort() | 调用entries.sort()对计划项排序 |
(3)AF、RI、Safety from rep exposure:
Abstraction function:
AF(entries)=现实中的计划项清单
Representation invariant:
entries!=null
Safety from rep exposure:
所有的数据域都是私有的用final限定
获得entries时使用Collections.unmodifiableList()转化为不可更改的list返回
- 测试:
(1)测试静态方法:
Test strategy
测试flightEntry()
通过getName()、getState()、getDeparture()、getArrival()、getTimeslot()观察
测试trainEntry()
通过getName()、getState()、getLocations()、getTimeslot()观察
测试activityEntry()
通过getName()、getState()、getLocation()、getTimeslot()观察
(2)测试实例方法:
Test strategy
测试allocate()
此时的状态为WATIING或ALLOCATED,此时的状态不为WAITING和ALLOCATED
测试running()
此时状态为ALLOCATED或BLOCKED或RUNNING,此时状态不为ALLOCATED和BLOCKED和RUNNING
测试cancel()
此时状态为WATIING或ALLOCATED或BLOCKED或CANCELLED,此时状态不为WATIING和ALLOCATED和BLOCKED或CANCELLED
测试end()
此时状态为RUNNING或ENDED,此时状态不为RUNNING和ENDED
测试getName()
测试是否返回计划项的名称
测试getState()
测试是否正确返回计划项的状态
(3)测试PlanningEntryCollection
Test strategy
测试AddEntries()
添加的计划项未null,计划项不为null
测试getEntries()
当没有计划项时,当有计划项时
测试sort()
测试能够按照时间的先后顺序给计划项排序
3.2.2 局部共性特征的设计方案
3.2.2.1 实现可阻塞功能
定义具有阻塞功能的接口BlockableEntry,当计划项是可以被阻塞的时候实现此接口,接口声明方法如下:
/**
* 把计划项的状态设置为阻塞
*
* @return false 设置失败 true 设置成功
*/
public boolean block();
3.2.2.2 实现两个位置
- 定义表示出发和抵达两个地点的接口TwoLocationEntry,对与选择的航班计划项可以实现此接口,方法声明如下:
/**
* 获得出发地点
*
* @return 出发地点
*/
public Location getDeparture();
/**
* 获得目的地点
*
* @return 目的地点
*/
public Location getArrival();
- 定义一个immutable的实现该接口的类TwoLocation。
(1)数据域:表示开始位置和结束位置。
private final Location departure, arrival;
(2)方法:
方法 | 作用 |
---|---|
public TwoLocation() | 构造方法 |
public Location getDeparture() | 获得出发地点 |
public Location getArrival() | 获得到达地点 |
public String toString() | 把存储的位置按照“departure->arrival”的形式输出 |
public boolean equals() | 判断两个对象是否相同 |
(3)AF、RI、Safety from rep exposure:
Abstraction function:
AF(departure,arrival)=现实中从departure位置到arrival位置
Representation invariant:
departure!=null
arrival!=null
Safety from rep exposure:
所有的数据域都是私有的且使用final限定
- 测试:
(1)接口方法:
Test strategy
测试getDeparture
测试返回的位置是否和期望相同
测试getArrival
测试返回的位置是否和期望相同
(2)子类型方法:
Test strategy
测试toString()
测试返回的字符串是否与期望的相同
测试equals()
相同的两个位置,两个位置不相同
3.2.2.3 实现多个有序的位置
- 定义一个表示多个不可更改的位置的接口MultipleLocationEntry,对于选择的高铁计划项可以实现此接口,方法声明如下:
/**
* 获得途径的所有地点
*
* @return 途经地点的list
*/
public List<Location> getLocations();
- 定义一个immutable的实现该接口的类MultipleLocation。
(1)数据域:表示一组有序的位置
private final List<Location> locations;
(2)方法:只有一个构造方法和一个实现的接口定义的方法。
public MultipleLocation(List<Location> locations) {
this.locations = locations;
}
@Override
public List<Location> getLocations() {
return Collections.unmodifiableList(locations);
}
(3)AF、RI、Safety from rep exposure:
Abstraction function:
AF(locations)=以locations中的对象为位置且按顺序排列的一组位置
Representation invariant:
locations!=null
Safety from rep exposure:
所有的数据域都是私有的且使用final限定
返回位置列表时使用Collections.unmodifiableList()返回一个不可修改的列表
- 测试:
Test strategy
测试getLocations()
测试返回的位置列表与期望是否相等
3.2.2.4 实现可修改的单个位置
- 定义一个表示可变更的单个位置的接口ModifiableSingleLocationEntry,对于选择的高铁计划项可实现此接口,方法声明如下:
/**
* 设置位置
*
* @param location 待设置的位置
* @return false 设置失败,位置为空 true 设置成功
*/
public boolean setLocation(Location location);
/**
* @return 获得位置
*/
public Location getLocation();
- 定义一个mutable的实现该接口的类ModifiableSingleLocation。
(1)数据域:表示一个位置。
private Location location;
(2)方法:
实现接口中的方法,public boolean setLocation(Location location):当location是null是返回false,否则把当前的位置改为location返回true。
(3)AF、RI、Safety from rep exposure:
Abstraction function:
AF(location)=一个现实中的位置
Representation invariant:
location!=null
Safety from rep exposure:
所有的数据域都是私有的
- 测试:
Test strategy
测试setLocation()
位置为空,位置不为空
测试getLocation()
测试返回的位置是否和预期相同
3.2.2.5 实现一组有序资源
- 定义表示一个可复用的有序的资源组的接口MultipleSortedResourceEntry<R>,对于选择的高铁计划项可实现此接口。方法声明如下:
/**
* 获得资源组
*
* @return 一个资源组的列表
*/
public List<R> getResources();
- 定义一个immutable的实现该接口的类MultipleSortedResource<R>。
(1)数据域:表示一组有序的资源。
private final List<R> train;
(2)方法:
/**
* 创建一个资源组
*
* @param train 指定的资源组,非空,元素个数大于0
*/
public MultipleSortedResource(List<R> train) {
this.train = train;
}
@Override
public List<R> getResources() {
return Collections.unmodifiableList(train);
}
(3)AF、RI、Safety from rep exposure:
Abstraction function:
以train中的顺序排好序的,有train.size()个个体的一组资源
Representation invariant:
train!=null
train.size()>0
Safety from rep exposure:
所有的数据域都是私有的且使用final限定
获得资源时用Collections.unmodifiableList()转化为不可变的List输出
- 测试:
Test strategy
测试getResources()
测试返回的资源是否与期望的相等
3.2.2.6 实现一个起止时间对
- 定义一个代表可被预设的起止时刻的时刻表的接口PresetSingleTimeslotEntry,对于选择的航班计划项和学习活动计划项可实现此接口,方法声明如下:
/**
* 获得起止时刻
*
* @return 一个有起止时刻的时刻表
*/
public Timeslot getTimeslot();
- 定义一个immutable的实现该接口的类PresetSingleTimeslot。
(1)数据域:表示一个起止时间对。
private final Timeslot timeslot;
(2)方法:
/**
* 创建起止时刻表
*
* @param start 开始时间
* @param end 结束时间
*/
public PresetSingleTimeslot(String start, String end) {
this.timeslot = new Timeslot(start, end);
}
@Override
public Timeslot getTimeslot() {
return timeslot;
}
(3)AF、RI、Safety from rep exposure:
Abstraction function:
AF(timeslot)=计划项运行过程中的起止时刻
Representation invariant:
timeslot!=null
Safety from rep exposure:
所有的数据域都是私有的且使用final限定
- 测试:
Test strategy
测试getTImeslot()
测试返回的时间表是否和预期的相等
3.2.2.7 实现一组起止时间对
- 定义一个表示被预设好的一组有序的时间表的接口PresetMultipleTimeslotEntry,对于高铁计划项可实现此接口,方法声明如下:
/**
* 获得时刻表
*
* @return 一个时刻表
*/
public List<Timeslot> getTimeslot();
- 定义一个immutable的实现该接口的类PresetMultipleTimeslot。
(1)数据域:表示一组有序时间对的列表。
private final List<Timeslot> timeslot = new ArrayList<>();
(2)方法:
/**
* 创建一个时刻表
*
* @param times 被预设的时间表,非空,times.size()>0
*/
public PresetMultipleTimeslot(List<String> times) {
for (int i = 0; i < times.size(); i += 2) {
this.timeslot.add(new Timeslot(times.get(i), times.get(i + 1)));
}
}
@Override
public List<Timeslot> getTimeslot() {
return Collections.unmodifiableList(timeslot);
}
(3)AF、RI、Safety from rep exposure:
Abstraction function:
以timeslot中的顺序排好序的,有timeslot.size()个个体的一组资源
Representation invariant:
timeslot!=null
timeslot.size()>0
Safety from rep exposure:
所有的数据域都是私有的且使用final限定
获得资源时用Collections.unmodifiableList()转化为不可变的List输出
- 测试:
Test strategy
测试getTImeslot()
测试返回的时间表是否和预期的相等
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
3.2.3.1 实现FlightEntry<R>
继承抽象类CommonPlanningEntry,实现接口TwoLocationEntry, PresetSingleTimeslotEntry。
- 数据域:表示起止位置的twoLocations,表示起止时刻表的timeslot,表示所占用的资源的plane。
private final TwoLocationEntry twoLocations;
private final PresetSingleTimeslotEntry timeslot;
private R plane;
- 方法:
(1)构造方法:调用父类CommonPlanningEntry的构造方法,为计划项命名,然后设定twoLocations和timeslot为给定起止位置和时刻表。
(2)public boolean allocatePlane(R plane):调用父类的allocate()方法,判断返回值,如果返回false,说明当前的状态不可为计划项分配资源,直接返回false,否则说明可以分配资源,把参数plane赋给计划项资源this.plane,然后返回true表示分配成功。
(3)public R getResource():获得资源,直接返回plane。
(4)public Location getDeparture():获得出发位置。
public Location getArrival():获得到达位置。
委托给twoLocations,分别调用twoLocations.getDeparture()和twoLocations.getArrival()获得起止位置。
(5)public Timeslot getTimeslot():委托给timeslot,调用timeslot.getTimeslot()获得起止时间对。
(6)public int compareTo(PlanningEntry<R> entry):比较两个计划项的开始时间,当this在entry之前开始,返回-1,当this在entry之后开始,返回1,两者同时开始,返回0。
- AF、RI、Safety from rep exposure:
Abstraction function:
AF(twoLocations, timeslot, plane)=一个timeslot.getStartTime()从
twoLocations.getDeparture()出发timeslot.getEndTime()到达twoLocations.getArrival()的航班
Representation invariant:
twoLocations!=null
timeslot!=null
Safety from rep exposure:
twoLocations,timeslot数据域都是私有的用final限定
plane是私有的
- 测试:
Test strategy
测试allocatePlane()
状态可分配资源,