目录
1 实验目标概述 1
2 实验环境配置 1
3 实验过程 2
3.1 待开发的三个应用场景 2
3.2 面向可复用性和可维护性的设计:PlanningEntry 3
3.2.1 PlanningEntry的共性操作 3
3.2.2 局部共性特征的设计方案 4
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案) 5
3.3 面向复用的设计:R 10
3.4 面向复用的设计:Location 14
3.5 面向复用的设计:Timeslot 16
3.6 面向复用的设计:EntryState及State设计模式 18
3.7 面向应用的设计:Board 24
3.8 Board的可视化:外部API的复用 28
3.9 PlanningEntryCollection的设计 30
3.10 可复用API设计及Façade设计模式 37
3.10.1 检测一组计划项之间是否存在位置独占冲突 37
3.10.2 检测一组计划项之间是否存在资源独占冲突 38
3.10.3 提取面向特定资源的前序计划项 38
3.11 设计模式应用 463.11.1 Factory Method 46
3.11.2 Iterator 473.11.3 Strategy 47
3.12 应用设计与开发 49
3.12.1 航班应用 50
3.12.2 高铁应用 63
3.12.3 课表应用 67
3.13 基于语法的数据读入 69
3.14 应对面临的新变化 76
3.14.1 变化1 76
3.14.2 变化2 76
3.14.3 变化3 77
3.15 Git仓库结构 78
4 实验进度记录 78
5 实验过程中遇到的困难与解决途径 79
6 实验过程中收获的经验、教训、感想 80
6.1 实验过程中收获的经验和教训 80
6.2 针对以下方面的感受 80
1 实验目标概述
本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
(1)子类型、泛型、多态、重写、重载
(2)继承、代理、组合
(3)常见的OO设计模式
(4)语法驱动的编程、正则表达式
(5)基于状态的编程
(6)API 设计、API 复用
本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性) 和更容易面向各种变化(可维护性)。
2 实验环境配置
参考了实验手册
(1) 阅读 http://web.mit.edu/6.031/www/fa18/getting-started/,在本地机器安装了相应的开发环境(JDK、Eclipse、Git)
(2) 阅读https://github.com/junit-team/junit4/wiki/Download-and-Install,并在自 己的 Eclipse IDE 中安装配置 JUnit4。
(3) 阅读 https://github.com/junit-team/junit4/wiki/Getting-started,了解如何使 用 JUnit4 为 Java 程序编写测试代码并执行测试在这里给出你的GitHub Lab3仓库的URL地址(Lab3-1180301002)。https://github.com/ComputerScienceHIT/Lab3-1180301002.git
3 实验过程
3.1 待开发的三个应用场景
我选择的是航班应用、高铁管理应用、课表管理应用
异同点如下:
对于上图我的理解是:
(1)地点:航班:2个(地点不可更改)高铁:3个或更多(地点不可更改)课程:1个(状态为WAITING或ALLOCATED时,可以更改地点)
(2)资源:航班:飞机(可区分,单个)高铁:车厢(可区分,多个,有次序)课程:教师(可区分,单个)
(3)时间对(创建时设定):航班:1对时间对高铁:若有n个地点,则有n-1对时间对课程:1个时间对
3.2 面向可复用性和可维护性的设计:PlanningEntry
我认为所给的六个方案都不太适合本实验,对于前四个方案,实验手册中已经给出了缺点。对于第五个方案和第六个方案,是从特征方面(某个计划项有某个特征,某个计划项没有某个特征)对计划项进行划分,而本实验是从类型方面(航班计划项、高铁计划项、课程计划项)对计划项进行划分。因此,我采用了其他方法:PlanningEntry是一个接口,用于后续静态工厂模式,包括了计划项的局部共性方法的名字、Spec;CommonPlanningEntry是一个抽象类,实现了PlanningEntry接口中的所有方法。FlightPlanningEntry、TrainPlanningEntry、CoursePlanningEntry是三个具体类,继承了抽象的CommonPlanningEntry,每个具体类中都有自己个性化的方法。
3.2.1 PlanningEntry的共性操作
①PlanningEntry是一个接口,用于后续静态工厂模式,包括了计划项的局部共性方法的名字、Spec,对应的实例类是CommonPlanningEntry
②无rep
③方法(详细见实验文件夹\javadoc\PlanningEntry\PlanningEntry):
3.2.2 局部共性特征的设计方案
①CommonPlanningEntry是可变的抽象类,是PlanningEntry的实例类,实现了PlanningEntry接口中的所有方法,即计划项的局部共性方法,具体的子类型是FlightPlanningEntry、TrainPlanningEntry、CoursePlanningEntry
②AF(locations, timeslot, name, plane, state) = 一个具体的计划项有地点列表、时间对列表、名字、资源、状态
③RI: true(在其具体的子类型中检查)
④Safety from rep exposure:
属性私有且不可变,或者是保护的
多数方法仅返回真假,其他方法防御式拷贝
⑤rep:protected List location是地点的集合protected List timeslots是时间对的集合protected String name是该计划项的名字protected List resources是资源的列表protected EntryState state是该计划项的状态
⑥方法(详细见实验文件夹\javadoc\PlanningEntry\CommonPlanningEntry):
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
(1)FlightPlanningEntry类
①FlightPlanningEntry类是可变的,继承CommonPlanningEntry抽象类,包括了航班计划项的个性方法
②AF() = 一个具体的计划项有地点列表(父)、时间对列表(父)、名字(父)、特定资源(父)
③RI:
locations(父)元素个数必须为二(出发地点、停止地点)
timeslots(父)元素个数必须为一(起始时间,终止时间)
name(父)不能为空、空字符串
④Safety from rep exposure:
方法返回仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntry\FlightPlanningEntry):
(2)TrainPlanningEntry类
①TrainPlanningEntry类是可变的,继承CommonPlanningEntry抽象类,包括了高铁计划项的个性方法
②AF() = 一个具体的计划项有地点列表(父)、时间对列表(父)、名字(父)、特定资源(父)
③RI:
locations(父)元素个数必须大于等于三(出发地点、停止地点、停止地点)
timeslots(父)元素个数必须大于等于二(起始时间,中间停车时间)和(中间停车时间,终止时间)
name(父)不能为空、空字符串
④Safety from rep exposure:
方法返回仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntry\TrainPlanningEntry):
(3)CoursePlanningEntry类
①CoursePlanningEntry类是可变的,继承CommonPlanningEntry抽象类,包括了课程计划项的个性方法
②AF() = 一个具体的计划项有地点列表(父)、时间对列表(父)、名字(父)、特定资源(父)
③RI:
locations(父)元素个数必须大于等于三(出发地点、停止地点、停止地点)
timeslots(父)元素个数必须大于等于二(起始时间,中间停车时间)和(中间停车时间,终止时间)
name(父)不能为空、空字符串
④Safety from rep exposure:
方法返回仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntry\CoursePlanningEntry):
3.2.4 PlanningEntry的测试用例
先构造基本数据:
Location B46 = Location.createLocation(“126.63°E”, “045.75°N”, “B46教室”, false);
Location B42 = Location.createLocation(“126.63°E”, “045.75°N”, “B42教室”, false);
Location Shanghai = Location.createLocation(“121.47°E”, “031.23°N”, “上海”, true);
Location Quanzhou = Location.createLocation(“118.37°E”, “024.54°N”, “泉州”, true);
Location Beijing = Location.createLocation(“116.46°E”, “039.92°N”, “北京”, true);
Timeslot timeslot1 = Timeslot.createTimeslot(“2020-03-01 12:00”, “2020-03-01 14:00”);
Timeslot timeslot2 = Timeslot.createTimeslot(“2020-03-01 15:00”, “2020-03-01 16:00”);
List locations = new LinkedList<>();
List timeslots = new LinkedList<>();
Plane plane = Resource.createPlane(“1”, “A350”, 100, 5.5);
Carriage carriage1 = Resource.createCarriage(“1”, “商务”, 10, 2020);
Carriage carriage2 = Resource.createCarriage(“2”, “二等”, 100, 1997);
List train = new LinkedList<>();Teacher teacher =
Resource.createTeacher(“42900520010119xxxx”, “李x”, “男”, “助教”);
@Test public void testCreateFlightPlanningEntry()//测试静态生成FlightPlanningEntry实例对象,及其个性方法先构造一个新的航班计划,测试其不变的属性,再通过allocatePlane方法,改变属性,测试改变前后状态
@Test public void testCreateTrainPlanningEntry()//测试静态生成TrainPlanningEntry实例对象,及其个性方法 先构造一个新的高铁计划,测试其不变的属性,再通过allocatePlane方法,改变属性,测试改变前后的状态,最后通过block方法,改变状态,测试改变前后的状态
@Test public void testCreateCoursePlanningEntry()//测试静态生成CoursePlanningEntry实例对象,及其个性方法 先构造一个新的课程计划,测试其不变的属性,再通过allocateTeacher方法和setLocation方法,改变属性,测试改变前后状态
@Test public void testRun()//测试Run方法 先构造一个新的高铁计划(可以阻塞,测试更加全面)对于六种状态,均调用Run方法,测试:状态转变成功(此时是ALLCOTED、BLOCKED)状态转换失败(此时是WAITING、RUNNNING、ENDED、CANCELLED)注意如果改不回需要测试的状态,需要重新新建一个新的高铁计划
@Test public void testCancel()//测试Cancel方法 先构造一个新的高铁计划(可以阻塞,测试更加全面)对于六种状态,均调用Cancel方法,测试:状态转变成功(此时是WAITING、BLOCKED)状态转换失败(此时是ALLOCATED、RUNNNING、ENDED、CANCELLED)注意如果改不回需要测试的状态,需要重新新建一个新的高铁计划
@Test public void testEnd()//测试End方法 先构造一个新的高铁计划(可以阻塞,测试更加全面)对于六种状态,均调用End方法,测试:状态转变成功(此时是RUNNING)状态转换失败(此时是WAITING、BLOCKED、ALLOCATED、ENDED、CANCELLED)注意如果改不回需要测试的状态,需要重新新建一个新的高铁计划
@Test public void testAllocate()//测试Allocate方法 先构造一个新的高铁计划(可以阻塞,测试更加全面)对于六种状态,均调用Allocate方法,测试:状态转变成功(此时是WAITING)状态转换失败(此时是RUNNING、BLOCKED、ALLOCATED、ALLOCATED、CANCELLED)注意如果改不回需要测试的状态,需要重新新建一个新的高铁计划
@Test public void testGetName()//测试获取计划名字的方法 先构造一个新的高铁计划(任意),测试getName方法返回值与预期
@Test public void testGetState()//测试获取计划状态的方法 先构造一个新的高铁计划(可以阻塞,测试更全面)对于六种状态,均测试:状态转变成功时,测试操作前后状态与预期状态转换失败时,测试操作前后状态与预期由于状态转变不能转变为WAITING状态,每次状态转变只有五种可能情况注意如果改不回正在测试的状态,需要重新新建一个新的高铁计划
测试用例通过如下:
3.3 面向复用的设计:R
R是设计的任何表达资源的类,包为Resource,包含接口Resource、实例类Plane、实例类Carriage和实例类Teacher
3.3.1 设计Resource接口及其实例
(1)Resource接口
(利用接口,设计了采用静态工厂方法,来创建三种资源对象)
①Resource是接口,对应的实例类是Plane、Carriage和Teacher,即航班应用的资源(飞机)、高铁管理应用的资源(车厢)、课表管理应用的资源(教师)
②无rep
③方法(详细见实验文件夹\javadoc\Resource\Resource):
(2)Plane类
①Plane类是不可变的,是Resource的实例类
②AF(number, type, seatNumber, age) = 一架飞机有编号,机型号,座位数,机龄
③RI:
number不能为空、空字符串
type不能为空、空字符串
seatNumber必须是正数(不可以为0)age不能是负数(可以为0)
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性私有且用final修饰
防御式拷贝
⑤rep:
final private String number是飞机编号
final private String type是机型号
final private int seatNumber是座位数
final private double age是机龄
⑥方法(详细见实验文件夹\javadoc\Resource\Plane):
(3)Carriage类
①Carriage类是不可变的,是Resource的实例类
②AF(number, type, peopleNumber, manufactureYear) = 一节车厢有编号、类型、定员数、出厂年份
③RI:
number不能为空、空字符串type不能为空、空字符串,且类型必须是商务、一等、二等、软卧、硬卧、硬座、行李车、餐车的一种
peopleNumber不能是负数(可以为0)
manufactureYear必须为正数(不可以为0)
④Safety from rep exposure:数据类型不可变(没有修改值的方法)属性私有且用final修饰防御式拷贝
⑤rep:
final private String number是车厢编号
final private String type是车厢类型
final private int peopleNumber是定员数
final private int manufactureYear是出厂年份
⑥方法(详细见实验文件夹\javadoc\Resource\Carriage):
(4)Teacher类
①Teacher类是不可变的,是Resource的实例类
②AF(number, name, sex, title) = 一位教师有身份证号,姓名,性别,职称
③RI:
number不能为空、空字符串
name不能为空、空字符串
sex不能为空、空字符串,且类型必须是男、女、其他的一种
title不能为空、空字符串
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性私有且用final修饰
防御式拷贝
⑤rep:
final private String number是身份证号
final private String name是姓名
final private String sex是性别
final private String title是职称
⑥方法(详细见实验文件夹\javadoc\Resource\Teacher):
3.3.2 Resource的测试用例
@Test public void testCreatePlane()//测试静态生成Plane实例对象、其方法构造一架飞机,利用get方法比较其属性与期望值
@Test public void testCreateCarriage()//测试静态生成Carriage实例对象、其方法构造一节车厢,利用get方法比较其属性与期望值
@Test public void testCreateTeacher()//测试静态生成Teacher实例对象、其方法构造一位教师,利用get方法比较其属性与期望值
测试用例通过如下:
3.4 面向复用的设计:Location
3.4.1 设计Location接口及其实例
(1)Location接口
(为了使用户尽量对抽象的ADT进行操作,这里采用接口形式)①Location是接口,对应的实例类是ConcreteLocation
②无rep
③方法(详细见:实验文件夹\javadoc\Location\Location):
(2)ConcreteLocation类
①ConcreteLocation类是不可变的,是Location的实例类
②AF(longitude, latitude, name, share) = 一个地点有经度、纬度、名称以及是否可共享使用
③RI:
longitude不能为空、空字符串,必须符合 xxx.yy°E或xxx.yy°W的语法规则(x和y是数字)
latitude不能为空、空字符串,必须符合 xxx.yy°N或xxx.yy°S的语法规则(x和y是数字)
name不能为空、空字符串
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性私有且用final修饰
防御式拷贝
⑤rep:
final private String longitude 是经度
final private String latitude 是纬度
final private String name 是名称
final private boolean share是判断是否可以共享使用的标记,若为真,说明可以共享(航班管理、铁路管理的地点);若为假,说明不可以共享(课表管理的教室)
⑥方法(详细见:实验文件夹\javadoc\Location\ConcreteLocation):
3.4.2 Location的测试用例
@Test public void testCreateLocation()//测试静态生成Location实例对象、其方法构造一个位置,利用get方法比较其属性与期望值
测试用例通过如下:
3.5 面向复用的设计:Timeslot
3.5.1 设计Timeslot接口及其实例
(1)Timeslot接口
(为了使用户尽量对抽象的ADT进行操作,这里采用接口形式)
①Timeslot是接口,对应的实例类是ConcreteTimeslot
②无rep
③方法(详细见实验文件夹\javadoc\Timeslot\Timeslot):
(2)ConcreteTimeslot类
①ConcreteTimeslot类是不可变的,是Timeslot的实例类
②AF(startTime, endTime) = 一个时间对有起始时间和终止时间
③RI:
startTime不能为空、空字符串,必须符合 yyyy-MM-dd HH:mm 的语法规则
endTime不能为空、空字符串,必须符合 yyyy-MM-dd HH:mm 的语法规则
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性私有且用final修饰
防御式拷贝
⑤rep:
final private String startTime 是起始时间
final private String endTime 是终止时间
⑥方法(详细见实验文件夹\javadoc\Timeslot\ConcreteTimeslot):
3.5.2 Timeslot的测试用例
@Test public void testCreateTimeslot()//测试静态生成Timeslot实例类、其方法构造一个时间对,利用get方法比较其属性与期望值测试
用例通过如下:
3.6 面向复用的设计:EntryState及State设计模式
3.6.1 利用State设计模式设计EntryState接口及其实例
(1)EntryState接口
(仿照课件5.3状态设计模式,这里采用接口形式,完全隐藏了子类型)
①EntryState是接口,对应的实例类有ALLOCATED、BLOCKED、CANCCELLED、ENDED、RUNNING、WAITING
②无rep
③方法(详细见实验文件夹\javadoc\EntryState\EntryState):
(2)ALLOCATED、BLOCKED、CANCCELLED、ENDED、RUNNING、WAITING类(由于这六个状态大同小异,这里整合成一个来说明)
①六个类是不可变的,是EntryState的实例类
②AF(name) = 一个状态有自己的名字
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性私有且用final修饰
防御式拷贝
⑤rep:
final private String name 是状态的名字
⑥方法(详细见实验文件夹\javadoc\EntryState中对应的html文件):
ALLOCATED类
BLOCKED类
CANCELLED类
ENDED类
RUNNING类
WAITING类
状态转换图如下(图中指令即move(String order)方法的参数):
3.6.2 EntryState的测试用例
先构造六个状态:
EntryState waiting = EntryState.createWAITING();
EntryState allocated = EntryState.createALLOCATED();
EntryState running = EntryState.createRUNNING();
EntryState blocked = EntryState.createBLOCKED();
EntryState ended = EntryState.createENDED();
EntryState cancelled = EntryState.createCANCELLED();
@Test public void testCreateWAITING() //测试静态生成WAITING实例类测试新建的WAITING状态,是否是WAITING状态
@Test public void testCreateALLOCATED() //测试静态生成ALLOCATED实例类测试新建的ALLOCATED状态,是否是ALLOCATED状态
@Test public void testCreateRUNNING() //测试静态生成RUNNING实例类测试新建的RUNNING状态,是否是RUNNING状态
@Test public void testCreateBLOCKED()//测试静态生成BLOCKED实例类测试新建的BLOCKED状态,是否是BLOCKED状态
@Test public void testCreateENDED()//测试静态生成ENDED实例类测试新建的ENDED状态,是否是ENDED状态
@Test public void testCreateCANCELLED() //测试静态生成CANCELLED实例类测试新建的CANCELLED状态,是否是CANCELLED状态
@Test public void testGetName() 利用get方法测试六个状态的名字是否符合预期
@Test public void testMove() 测试状态转换:输入特定指令,转换成功(变成其他状态)输入其他指令(乱输、中文、首字母未大写、该状态不能执行该指令、该状态是最终状态),转换失败(维持原有状态不变)
@Test public void testAccept() 利用Accept方法测试六个状态是否是最终状态,是否符合预期
测试用例通过如下:
3.7 面向应用的设计:Board
由于手册中明确提出不需要抽象设计,不过我还是设计了一个Board抽象类,里面有三个子类型共用的方法
3.7.1 设计Board抽象类及其具体的继承类
(1)Board类
①Board类是不可变的抽象类,具体的子类型是FlightBoard、TrainBoard、CourseBoard
②AF(planningEntryCollection, location, now) = 一个显示板有计划项整合,有所在的位置,可以显示当前时间
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
属性用final和protect修饰
防御式拷贝
⑤rep:
final protected String now是当前时间
final protected PlanningEntryCollection planningEntryCollection是计划项整合
final protected Location location是该显示板所在的地点
⑥方法(详细见实验文件夹\javadoc\Board\Board):
(2)FlightBoard类
①FlightBoard类是不可变的,是Board的继承类
②AF() = 一个航班显示板有计划项整合(父),有所在的位置(父)
③RI:true
④Safety from rep exposure:
仅有一个构造函数,一个main方法(用于测试),以及一个无返回值的方法
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\Board\FlightBoard):
(3)TrainBoard类
①TrainBoard类是不可变的,是Board的继承类
②AF() = 一个高铁显示板有计划项整合(父),有所在的位置(父)
③RI:true
④Safety from rep exposure:仅有一个构造函数,一个main方法(用于测试),以及一个无返回值的方法
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\Board\TrainBoard):
(4)CourseBoard类
①CourseBoard类是不可变的,是Board的继承类
②AF() = 一个课程显示板有计划项整合(父),有所在的位置(父)
③RI:true
④Safety from rep exposure:
仅有一个构造函数,一个main方法(用于测试),以及一个无返回值的方法
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\Board\CourseBoard):
3.7.2 Board的测试
由于是展现信息板,采用JUnit来测试不太方便。因此,对于每一个Board的子类都有一个main方法,模拟用户输入,进行测试测试步骤如下:
①构造时间对
②构造地点
③构造资源
④构造计划
⑤改变状态(所有状态都要体现)
⑥构造计划整合,并收集上述计划
⑦计划排序(先加入时间晚的计划项,排序后,观察其是否在后面)
⑧构造显示屏
⑨展现显示屏由于测试众多(航班、课程计划构造了12个计划项;高铁计划构造了6个计划项),仅仅在3.8部分罗列了部分测试结果
3.8 Board的可视化:外部API的复用
采用自带的JTable、JFrame,无外部库为了建立一个窗口,具体操作如下:
①新建一个一维容器,保存信息板第一行的内容Vector title; = new Vector();
②新建一个二维容器,保存所有将要显示在信息板上的内容Vector<Vector> datas;
③调用父类(Board类)中的iterator()方法构造一个计划项迭代器,用于遍历所有的计划项。对于其中某一个计划,如果地点吻合且时间在当前时间的前后一个小时内,则将该计划项的数据存入二维容器中
④新建一个JTable,参数是一维容器和二维容器JTable table = new JTable(datas, title);
⑤新建一个窗口,设置标题JFrame window = new JFrame(“标题”);
⑥把数据传入窗口中window.add(new JScrollPane(table));
⑦设置窗口位置window.setBounds(10, 10, 1000, 500);
⑧设置窗口可见性window.setVisible(true);
可视化效果如下(以高铁中的某个计划项“高铁6”为例):
地点:上海->北京->哈尔滨
时间对1:“2020-03-01 20:00”, “2020-03-01 22:00”
时间对2:“2020-03-01 22:00”, “2020-03-01 24:00”
注意:下图的当前时间是在代码内修改的(用户不可修改),不是真实的
3.9 PlanningEntryCollection的设计
该ADT是PlanningEntry的集合类,由于我设计的PlanningEntryCollection并不是一个Set(集合),而是一个ADT,为了区分,本实验报告称PlanningEntryCollection为计划项整合。设计了一个抽象的PlanningEntryCollection,用于保存子类型的共性方法,其子类型是具体的FlightPlanningEntryCollection,TrainPlanningEntryCollection和CoursePlanningEntryCollection,在每个子类型中又设计了个性方法。
3.9.1 设计PlanningEntryCollection抽象类及其具体的继承类
(1)PlanningEntryCollection类
①PlanningEntryCollection类是可变的抽象类,具体的子类型是FlightPlanningEntryCollection、TrainPlanningEntryCollection和CoursePlanningEntryCollection
②AF(PlanningEntrys) = 一个计划的整合有所有计划的列表
③RI:true
④Safety from rep exposure:
属性唯一,且是protect
防御式拷贝
⑤rep:
protected List<PlanningEntry> PlanningEntrys是所有计划的列表
⑥方法(详细见实验文件夹\javadoc\PlanningEntryCollection\PlanningEntryCollection):
(2)FlightPlanningEntryCollection类
①FlightPlanningEntryCollection类是可变的,是PlanningEntryCollection的继承类
②AF() = 一个航班计划的整合可以收集航班计划
③RI:true
④Safety from rep exposure:
方法返回值仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryCollection\FlightPlanningEntryCollection):
(3)TrainPlanningEntryCollection类
①TrainPlanningEntryCollection类是可变的,是PlanningEntryCollection的继承类
②AF() = 一个高铁计划的整合可以收集航班计划
③RI:true
④Safety from rep exposure:
方法返回值仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryCollection\TrainPlanningEntryCollection):
(4)CoursePlanningEntryCollection类
①CoursePlanningEntryCollection类是可变的,是PlanningEntryCollection的继承类
②AF() = 一个课程计划的整合可以收集航班计划
③RI:true
④Safety from rep exposure:方法返回值仅为真假
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryCollection\CoursePlanningEntryCollection):
(5)TimeComparator类
①TimeComparator类是不可变的,实现了 Comparator<PlanningEntry>接口,用于比较两个计划项开始时间的先后,PlanningEntryCollection中的sortPlanningEntrys()方法(按开始时间排序计划项整合中的所有计划)使用Collections.sort(PlanningEntrys, new TimeComparator());的方式调用本类
②AF() = 一个时间比较器可以设置比较的方法
③RI:true
④Safety from rep exposure:
方法返回值为不可变的基础类型
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryCollection\TimeComparator):
3.9.2 PlanningEntryCollection的测试用例
先构造基本数据
Location B46 = Location.createLocation(“126.63°E”, “045.75°N”, “B46教室”, false);
Location B42 = Location.createLocation(“126.63°E”, “045.75°N”, “B42教室”, false);
Location Shanghai = Location.createLocation(“121.47°E”, “031.23°N”, “上海”, true);
Location Quanzhou = Location.createLocation(“118.37°E”, “024.54°N”, “泉州”, true);
Location Beijing = Location.createLocation(“116.46°E”, “039.92°N”, “北京”, true);
Timeslot timeslot1 = Timeslot.createTimeslot(“2020-03-01 12:00”, “2020-03-01 14:00”);
Timeslot timeslot2 = Timeslot.createTimeslot(“2020-03-01 15:00”, “2020-03-01 16:00”);
Timeslot timeslot3 = Timeslot.createTimeslot(“2020-02-28 18:00”, “2020-02-28 20:00”);
Timeslot timeslot4 = Timeslot.createTimeslot(“2020-02-28 21:00”, “2020-02-28 22:00”);
List locations = new LinkedList<>();
List timeslots1 = new LinkedList<>();
List timeslots2 = new LinkedList<>();
FlightPlanningEntryCollection flightEntryCollection = new FlightPlanningEntryCollection();
TrainPlanningEntryCollection trainEntryCollection = new TrainPlanningEntryCollection();
CoursePlanningEntryCollection courseEntryCollection = new CoursePlanningEntryCollection();
@Test public void testCollect()//测试收集计划项的方法 先构造一个新的计划(航班、高铁、课程),再构造一个计划整合,调用addPlanningEntry方法,测试前后状态:加入计划前,整合里面没有这个计划加入计划后,整合里面有这个计划测试添加:一个、同一个、多个(两个)不一样的计划
@Test public void testGetPlanningEntrys()//测试获取所有计划的方法 先构造一个新的计划(航班、高铁、课程),再构造一个计划整合已经一个临时列表,添加一个、同一个、多个(两个)不一样的计划,对临时列表采取同样操作,每次添加计划后都调用getPlanningEntrys方法,测试返回的所有计划与临时列表
@Test public void testGetPlanningEntry()//测试获取某一个计划的方法 先构造一个新的计划(航班、高铁、课程),再构造一个计划整合,测试前后状态:添加计划前,获取不到,返回为null添加计划后,可以获取到同一个计划
@Test public void testSortPlanningEntrys()//测试排序计划的方法 先构造两个个新的计划(航班、高铁、课程),他们的开始时间有早晚之分,再构造一个计划整合。先添加开始时间晚的计划,后添加开始时间早的计划,排序后,开始时间晚的计划应该在开始时间早的计划之后
测试用例通过如下:
3.10 可复用API设计及Façade设计模式
本节要求使用Facade设计模式,并包括以下3.10.1、3.10.2、3.10.3的三种方法。此外,后续章节还要求使用Strategy模式便于用户灵活切换。因此,我设计了一个抽象类PlanningEntryAPIs其中包括了APP操作的所有方法以及每种方法对用户的提示信息:调用成功、调用失败(失败原因),其中还有一个抽象方法findPreEntryPerResource。其具体的子类型PlanningEntryAPIs1和PlanningEntryAPIs2对应的是上述抽象方法的两种不同实现:算法1,算法2(这两种算法在3.11.3中详细说明)
3.10.1 检测一组计划项之间是否存在位置独占冲突
Spec
检测一组计划项之间是否存在位置独占冲突:如果两个计划项在同一时
间点上占用了不可共享的位置,那么就存在了位置冲突。例如:某次《软件构造》
课的时间是本日 8:00-10:00,而某次《计算机系统》课是在本日 9:00-11:00,
二者均在 D01 教室,而“教室”这种位置是不可共享的,那么在 9:00-10:00 这个时间段里就存在着位置冲突。但是对机场、车站等可共享位置来说,则不会存在位置冲突(假设不考虑位置的容量问题)
@param entries 是一组计划的列表
@return 若为真,存在地点冲突;若为假,不存在地点冲突
public boolean checkLocationConflict(List<PlanningEntry> entries)
首先,航班、高铁计划项不会存在位置冲突,因此直接返回false并提示信息即可。对于课程计划,首先两重循环遍历参数entries中的所有计划,直至发现第i个计划和第j个计划共用了同一个地点且两者的时间对冲突时,就提示找到了,输出两个计划项的基本信息(计划名称、开始时间)以及冲突的地点,并返回true;如果没有找到地点冲突的两个计划项,提示信息,返回false
3.10.2 检测一组计划项之间是否存在资源独占冲突
Spec
检测一组计划项之间是否存在资源独占冲突:如果两个计划项在同一时间点上占用了同样的资源,那么就存在了资源冲突,例如同一个教师、同一个车厢、同一架飞机。(对 ROM、学习资料等不可区分个体的资源,无需考虑资源独占冲突。)
@param entries 是一组计划的列表
@return 若为真,存在资源冲突;若为假,不存在资源冲突
public boolean checkResourceExclusiveConflict(List<PlanningEntry> entries)首先两重循环遍历参数entries中的所有计划,对于第i个计划和第j个计划,分别依次遍历两者的资源(可能有多个),并进行比较,如果出现了相同的资源,说明第i个计划和第j个计划存在相同的资源,此时分别依次遍历两者的时间对(可能有多个),如果时间对冲突,则两者资源冲突,提示两个计划项的基本信息(计划名称、开始时间)以及冲突的资源的唯一编号(对于航班计划是:飞机编号;对于高铁计划是:车厢编号;对于课程计划是:教师身份证),并返回true;如果时间不冲突,则继续遍历计划项,继续寻找,若最终未找到,提示信息,返回false
3.10.3 提取面向特定资源的前序计划项
Spec
采用strategy模式,委派给子类型来完成,有两种不同的算法
算法1:对应子类型PlanningEntryAPIs1(适用于计划个数多,先对计划项按开始时间进行排序,再找出资源相同的计划项加入列表中,在列表中指定计划的前一个即是最近的前序计划)
算法2:对应子类型PlanningEntryAPIs2(适用于计划个数少,先找出资源相同的计划项加入列表中,遍历列表中的计划,每找到一个更近的前序计划,就替换返回的结果,如此反复,直到找到开始时间最近的前序计划)
提取面向特定资源的前序计划项:针对某个资源 r 和使用 r 的某个计划项 e,从一组计划项中找出 e 的前序 f,f 也使用资源 r,f 的执行时间在 e 之前,且在 e 和 f 之间不存在使用资源 r 的其他计划项。若不存在这样的计划项f,则返回 null。如果存在多个这样的 f,返回其中任意一个即可。(对 ROM、学习资料等不可区分个体的资源,该 API 不适用。)
@param r 是某个资源
@param e 是使用该资源的类
@param entries 是所有计划的列表
@return 前序计划项
abstract public PlanningEntry findPreEntryPerResource(Resource r, PlanningEntry e, List<PlanningEntry> entries);
由于本方法使用了strategy模式,委派给两个子类型来完成,将在3.11.3中详细说明
3.10.4 使用Facade设计模式设计PlanningEntryAPIs
PlanningEntryAPIs使用了facade模式进行封装,对其他类进行隐藏,APP只会调用该类。此外,除了必要的检查以及3.10.1、3.10.2、3.10.3的三个方法外,方法的功能全部委派给其他类来实现。PlanningEntryAPIs的方法主要有
①用户提供必要信息,管理(增加、删除)可用的资源;(由于提供信息不同,有三个方法对应分别三种不同的资源:飞机、车厢、教师)
②用户提供必要信息,管理(增加、删除)可用的位置;(两种不同的地点:机场/火车站(可以共享)、教室(不可以共享))
③用户提供必要信息,增加一条新的计划项;
④用户提供必要信息,取消某个计划项;
⑤用户提供必要信息,为某个计划项分配资源;(三种不同的资源:飞机、车厢、教师)
⑥用户提供必要信息,启动某个计划项;
⑦用户提供必要信息,以变更某个已存在的计划项的位置。(课程计划专用)
⑧用户提供必要信息,以阻塞/挂起某个计划项;(高铁计划专用)
⑨用户提供必要信息,以重启动某个已挂起的计划项;(高铁计划专用)
⑩用户提供必要信息,结束某个计划项;
⑪用户选定一个计划项,查看它的当前状态;⑫检测当前的计划项集合中可能存在的位置独占冲突;
⑬检测当前的计划项集合中可能存在的资源独占冲突;
⑭针对用户选定的某个资源,列出使用该资源的所有计划项 (包含尚未开始执行的、执行中的、已经结束的)。
⑮用户选中其中某个计划项之后,可以找出它的前序计划项;(是抽象方法,采用strategy模式)
⑯选定特定位置,可视化展示当前时刻该位置的信息板
由于PlanningEntryAPIs还采用了策略模式,因此其子类型PlanningEntryAPIs1和PlanningEntryAPIs2将留到3.11.3中详细说明,本处只说明PlanningEntryAPIs的设计
①PlanningEntryAPIs类是可变的抽象类,具体的子类型是PlanningEntryAPIs1和PlanningEntryAPIs2
②AF(type, resources, locations, PlanningEntryCollection) = 一个计划项APIs可以根据用途给定的必要信息,做出对应的操作
③RI:true
④Safety from rep exposure:
大部分方法只返回真假,或不返回
属性私有或protected
防御式拷贝
⑤rep:
final private String type是APIs的类型
protected List resources是已经添加的资源的列表
private List locations是已经添加的地点列表
protected PlanningEntryCollection PlanningEntryCollection是已经添加的计划的整合
⑥方法(详细见实验文件夹\javadocPlanningEntryAPIs\PlanningEntryAPIs):
3.10.5 PlanningEntryAPIs的测试用例
先构造几个APIsPlanningEntryAPIs
flightPlanningEntryAPIs1 = new PlanningEntryAPIs1(“航班”);
PlanningEntryAPIs trainPlanningEntryAPIs1 = new PlanningEntryAPIs1(“高铁”);
PlanningEntryAPIs coursePlanningEntryAPIs1 = new PlanningEntryAPIs1(“课程”);
PlanningEntryAPIs trainPlanningEntryAPIs2 = new PlanningEntryAPIs2(“高铁”);
PlanningEntryAPIs coursePlanningEntryAPIs2 = new PlanningEntryAPIs2(“课程”);
@Test public void testAddPlane()//测试添加飞机资源的方法
测试:添加一个资源、同一个资源、多个(两个)资源,测试方法返回值与预期
@Test public void testAddCarriage()//测试添加车厢资源的方法
测试:添加一个资源、同一个资源、多个(两个)资源,测试方法返回值与预期
@Test public void testAddTeacher()//测试添加教师资源的方法
测试:添加一个资源、同一个资源、多个(两个)资源,测试方法返回值与预期
@Test public void testDeleteResource()//测试删除资源的方法
测试:删除存在的资源,删除不存在的资源,删除被占用的资源,测试方法返回值与预期测试:不同的计划项APIs
@Test public void testAddStation()//测试添加可共享站点的方法
测试:添加一个站点、同一个站点、多个(两个)站点,测试方法返回值与预期
测试:不同的计划项APIs
@Test public void testAddClassRoom()//测试添加不可共享教室的方法
测试:添加一个教室、同一个教室、多个(两个)教室,测试方法返回值与预期
@Test public void testDeleteLocation()//测试删除地点的方法
测试:删除存在的地点,删除不存在的地点,删除被占用的地点,测试方法返回值与预期测试:不同的计划项APIs
@Test public void testAddPlanningEntry()//测试添加计划项的方法
测试:添加计划项成功(可以添加重名的计划项);添加计划项失败:地点不存在而导致的,地点个数不符合要求导致的,时间对个数不符合要求导致的
测试:不同的计划项APIs
@Test public void testCancelPlanningEntry()//测试取消计划项的方法
测试:取消计划项成功;取消计划项失败:当前状态不能取消导致的,选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的
测试:不同的计划项APIs
@Test public void testAllocatePlanningEntry()//测试分配资源给计划项的方法
测试:分配资源成功;分配资源失败:选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,资源分配达到上限导致的,分配资源重复导致的,当前状态不能分配资源导致的,资源未添加导致的
测试:不同的计划项APIs
@Test public void testRunPlanningEntry()//测试启动计划项的方法
测试:启动计划成功;启动计划失败:选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,当前状态不能启动计划导致的
测试:不同的计划项APIs
@Test public void testChangeLocation()//测试改变计划项位置的方法
测试:变更位置成功;变更位置失败:航班计划和高铁计划不能变更位置,选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,当前状态不能变更位置导致的
测试:不同的计划项APIs
@Test public void testBlockPlanningEntry()//测试阻塞计划项的方法
测试:阻塞成功;阻塞失败:航班计划和课程计划不能阻塞,选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,当前状态不能阻塞导致的
测试:不同的计划项APIs
@Test public void testRestartPlanningEntry()//测试重新启动计划项的方法
测试:重新启动成功;重新启动失败:航班计划和课程计划不能重新启动,选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,当前状态不能重新启动导致的
测试:不同的计划项APIs
@Test public void testEndPlanningEntry()//测试结束计划项的方法
测试:结束计划成功;结束计划失败:选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的,当前状态不能结束计划导致的
测试:不同的计划项APIs
@Test public void testGetPlanningEntryState()//测试获取计划项状态的方法
测试:查询六种不同的状态测试:获取计划状态成功;获取计划状态失败:选定的计划项名称不存在导致的,给定的计划名称与给定的计划开始日期不匹配导致的
测试:不同的计划项APIs
@Test public void testCheckLocationConflictAPP()//测试检查地点冲突的方法
测试:地点冲突;地点不冲突:时间不重合导致的、地点不同导致的测试:不同的计划项[email protected] public void testCheckResourceExclusiveConflictAPP()//测试检查资源冲突的方法
测试:资源冲突;资源不冲突:时间不重合导致的、资源不同导致的
测试:不同的计划项APIs
@Test public void testPlanningEntrysOfUsingTheSameResourceAPP()//测试输出共用同一种资源的计划项列表的方法
测试:能成功输出;不能成功输出:资源未添加,没有找到符合的计划(只有ALLOCATED、RUNNNING、ENDED状态的计划才能被输出 )
测试:不同的计划项APIs
@Test public void testFindPreEntryPerResourceAPP()//测试查找前序计划项的方法
测试:能成功输出;不能成功输出:选定的资源未添加、选定的计划项未添加、选定的计划没有使用选定的资源、选定的计划项是使用相同资源计划项中最早的
测试:不同的计划项APIs测试:不同类型的APIs(两种算法)
测试用例通过如下
3.11 设计模式应用
3.11.1 Factory Method
在PlanningEntry接口中采用了Factory Method模式,不通过new来生成子类型,并且可以隐藏子类型的名字
Spec
静态工厂方法,构造一个新的航班计划项
@param 是资源(一个飞机)
@param locations 是地点的集合,元素个数必须为二(出发地点、停止地点)
@param timeslots 是时间对的集合,元素个数必须为一(起始时间,终止时间) @param name 是该计划项的名字,不能为空、空字符串
@return 一个新的航班计划项
public static FlightPlanningEntry createFlightPlanningEntry(List locations, List timeslots, String name)
Spec
静态工厂方法,构造一个新的高铁计划项
@param 是资源(多个车厢)
@param locations 是地点的集合,元素个数必须大于等于三(出发地点、停止地点、停止地点)
@param timeslots 是时间对的集合,元素个数必须大于等于二(起始时间,中间停车时间)和(中间停车时间,终止时间)
@param name 是该计划项的名字,不能为空、空字符串
@return 一个新的高铁计划项
public static TrainPlanningEntry createTrainPlanningEntry(List locations, List timeslots, String name)
Spec
静态工厂方法,构造一个新的课表计划项
@param 是资源(一个教师)
@param locations 是地点的集合,元素个数必须为一(上课地点)
@param timeslots 是时间对的集合,元素个数必须为一(起始时间,终止时间)
@param name 是该计划项的名字,不能为空、空字符串
@return 一个新的课表计划项
public static CoursePlanningEntry createCoursePlanningEntry(List locations, List timeslots, String name)
3.11.2 Iterator
在Board抽象类中采用了Iterator模式,可以遍历所有的计划项,为计划项按照开始时间进行排序打好基础
Spec
迭代器,可以遍历计划整合中的计划
public Iterator<PlanningEntry> iterator()
3.11.3 Strategy
在PlanningEntryAPIs抽象类中的抽象方法findPreEntryPerResource采用了Strategy模式,APP可以灵活切换两者不同的算法
(1)PlanningEntryAPIs1类
①PlanningEntryAPIs1类是可变的,是PlanningEntryAPIs的继承类,对应算法1:先对计划项按开始时间进行排序,再找出资源相同的计划项加入临时列表中,在临时列表中指定计划的前一个即是最近的前序计划,适用于计划个数多
②AF() = 本类findPreEntryPerResource方法的算法适用于计划个数多
③RI:true
④Safety from rep exposure:
仅有一个构造函数,一个重写方法
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryAPIs\PlanningEntryAPIs1):
(2)PlanningEntryAPIs2类
①PlanningEntryAPIs2类是可变的,是PlanningEntryAPIs的继承类,对应算法2:先找出资源相同的计划项加入列表中,遍历列表中的计划,每找到一个更近的前序计划,就替换返回的结果,如此反复,直到找到开始时间最近的前序计划,适用于计划个数少
②AF() = 本类findPreEntryPerResource方法的算法适用于计划个数多
③RI:true
④Safety from rep exposure:
仅有一个构造函数,一个重写方法
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\PlanningEntryAPIs\PlanningEntryAPIs2):
3.12 应用设计与开发应用
可以实现的功能如下
①用户提供必要信息,管理(增加、删除)可用的资源;(飞机、车厢、教师)
②用户提供必要信息,管理(增加、删除)可用的位置;(站点(可以共享)、教室(不可以共享))
③用户提供必要信息,增加一条新的计划项;
④用户提供必要信息,取消某个计划项;
⑤用户提供必要信息,为某个计划项分配资源;(飞机、车厢、教师)
⑥用户提供必要信息,启动某个计划项;
⑦用户提供必要信息,以变更某个已存在的计划项的位置。(课程计划专用)
⑧用户提供必要信息,以阻塞/挂起某个计划项;(高铁计划专用)
⑨用户提供必要信息,以重启动某个已挂起的计划项;(高铁计划专用)
⑩用户提供必要信息,结束某个计划项;
⑪用户选定一个计划项,查看它的当前状态;
⑫检测当前的计划项集合中可能存在的位置独占冲突;
⑬检测当前的计划项集合中可能存在的资源独占冲突;
⑭针对用户选定的某个资源,列出使用该资源的所有计划项 (包含尚未开始执行的、执行中的、已经结束的)。
⑮用户选中其中某个计划项之后,可以找出它的前序计划项;(是抽象方法,采用strategy模式)
⑯选定特定位置,可视化展示当前时刻该位置的信息板
此外,上述①②③中的添加功能,必须要符合语法标准,若不符合(非法输入)将会退回到主菜单,并再次打印主菜单
3.12.1 航班应用
FlightAPP类
①FlightAPP类是不可变的,只调用了PlanningEntryAPIs及其子类
②AF() = AF() = 启动航班APP
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
只调用了PlanningEntryAPIs及其子类,对其他的类做了隐藏处理(facade模式)
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\APP\FlightAPP):
(1)主菜单
将会阻止非法输入,提示用户重新输入:
(2)提示用户是否需要自动读取文件(如何自动读取文件的内容在3.13中)
在APP运行的最开始回答Yes,可以自动读取文件数据,遇到格式错误将提示并结束读取(之前读取的内容仍然会保留),返回主菜单
回答No,则不读取文件,通过后续用户手动输入数据,返回主菜单:
将会阻止非法输入,提示用户重新输入:
(3)管理资源
①在主菜单输入1可以管理资源,再输入1可以添加资源,必须符合语法标准,输入正确后(添加已有的资源,也会提示错误),提示信息,返回主菜单:
若添加资源时非法输入,则提示错误信息,并返回主菜单
②在主菜单输入1后,再输入2可以删除资源
若删除成功,则返回主菜单
若删除被占用的资源(状态为ALLOCATED、RUNNING、BLOCKED),则会提示错误信息,并返回主菜单
若删除不存在的资源,则会提示错误信息,并返回主菜单
③在主菜单输入1后,再输入其他的字符,将提示非法输入,返回主菜单
(4)管理地点
①在主菜单输入2可以管理地点,再输入1可以添加地点,必须符合语法标准,输入正确后(添加已有的地点也会提示错误),提示信息,返回主菜单:
若添加地点时非法输入,则提示错误信息,并返回主菜单
②在主菜单输入2后,再输入2可以删除地点若删除成功,则返回主菜单若删除被占用的资源删除被占用的地点(状态为WAITING、ALLOCATED、RUNNING、BLOCKED),则会提示错误信息,并返回主菜单
若删除不存在的地点,则会提示错误信息,并返回主菜单
③在主菜单输入2后,再输入其他的字符,将提示非法输入,返回主菜单
(5)添加新的计划项
①在主菜单输入3可以添加新的计划项,必须符合语法标准,输入正确后(地点或资源未添加,也会提示错误),提示信息,返回主菜单:
②在主菜单输入3后,再非法输入,将提示错误信息,返回主菜单:
(6)取消、启动、结束计划项(图示以取消为例)
在主菜单输入4可以取消计划项,输入6可以取消计划项,输入7可以取消计划项,输入正确后(未找到计划项,计划项与开始日期不匹配,计划项状态不能取消,也会提示错误),提示信息,返回主菜单:
(7)为某个计划项分配资源
在主菜单输入5为某个计划项分配资源,输入正确后(未找到计划项,计划项与开始日期不匹配,资源未添加,计划项状态不能分配资源,也会提示错误),提示信息,返回主菜单:
(8)选定一个计划项,查看他的状态
在主菜单输入8可以选定一个计划项,查看他的状态,输入正确后(未找到计划项,计划项与开始日期不匹配,也会提示错误),提示信息,返回主菜单:
(9)检查目前已有的计划项的位置冲突
在主菜单输入9可以检查目前已有的计划项的位置冲突,提示信息,返回主菜单:
(10)检查目前已有的计划项的资源冲突
在主菜单输入10可以检查目前已有的计划项的资源冲突(以FlightSchedule_5.txt为例,在3.13中将具体分析文件,此处仅仅给出冲突信息),提示信息,返回主菜单:
(11)选定的某个资源,列出使用该资源的所有计划项 (包含尚未开始执行的、执行中的、已经结束的)
在主菜单输入11可以选定的某个资源,列出使用该资源的所有计划项 (包含尚未开始执行的、执行中的、已经结束的),以FlightSchedule_5.txt中的飞机编号为B8639的飞机资源为例,提示信息,返回主菜单:
(12)选定其中某个计划项之后,找出它的前序计划项;(默认算法1:适用于计划个数多)
①在主菜单输入12,再输入1,可以使用算法1(适用于计划个数多),以FlightSchedule_5.txt中开始于2020-01-02的,航班号为NX605的,飞机编号为B8639的计划项为例,提示信息,返回主菜单:
②在主菜单输入12,再输入2,可以使用算法2(适用于计划个数少),以FlightSchedule_5.txt中开始于2020-01-02的,航班号为NX605的,飞机编号为B8639的计划项为例,提示信息,返回主菜单:
③在主菜单输入12后,再非法输入,将提示错误信息,返回主菜单:
从上述①②中可以看出,两种算法的结果是一样的
(13)选定特定位置,可视化展示当前时刻该位置的信息板
在主菜单输入13,以FlightSchedule_5.txt为例,提示信息,展示信息板,返回主菜单:
注意:下图的当前时间是在代码内修改的(用户不可修改),不是真实的:
(14)退出APP
在主菜单输入close,可以退出APP,提示信息,不再返回主菜单:
3.12.2 高铁应用
TrainAPP类
①TrainAPP类是不可变的,只调用了PlanningEntryAPIs及其子类
②AF() = AF() = 启动高铁APP
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)
只调用了PlanningEntryAPIs及其子类,对其他的类做了隐藏处理(facade模式)
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\APP\TrainAPP):
(1)主菜单
将会阻止非法输入,提示用户重新输入:
(2)阻塞/挂起某个计划项(由于大部分功能与航班应用相同,3.12.1中已经说明的功能,这里不再叙述)
在主菜单输入7,可以阻塞/挂起某个计划项,输入正确后(未找到计划项,计划项与开始日期不匹配,计划项状态不能阻塞,也会提示错误),提示信息,返回主菜单:
①先新建一个计划项(其中地点已经提前添加):
(主菜单未截完整)
②为上述计划项分配车厢资源(车厢资源已经提前添加)
③启动该计划项
④阻塞该计划项
(3)重启动某个已挂起的计划项
在主菜单输入8,可以重启动某个已挂起的计划项,输入正确后(未找到计划项,计划项与开始日期不匹配,计划项状态不能重新启动,也会提示错误),提示信息,返回主菜单:
重新启动3.12.2(2)中的计划项:
3.12.3 课表应用
CourseAPP类
①CourseAPP类是不可变的,只调用了PlanningEntryAPIs及其子类
②AF() = 启动课程APP
③RI:true
④Safety from rep exposure:数据类型不可变(没有修改值的方法)只调用了PlanningEntryAPIs及其子类,对其他的类做了隐藏处理(facade模式)
⑤rep:无
⑥方法(详细见实验文件夹\javadoc\APP\CourseAPP):
(1)主菜单
将会阻止非法输入,提示用户重新输入:
(2)变更某个已存在的计划项的位置(由于大部分功能与航班应用相同,3.12.1中已经说明的功能,这里不再叙述)
在主菜单输入7,可以变更某个已存在的计划项的位置,输入正确后(未找到计划项,计划项与开始日期不匹配,计划项状态不能变更位置,也会提示错误),提示信息,返回主菜单
①先新建一个计划项(其中地点已经提前添加):
②重新设置位置(其中地点已经提前添加):
3.13 基于语法的数据读入
Spec
读取文件中的数据,从中抽取航班信息,构造一些航班计划项实体集合
文件必须存在,且格式必须是合法的
@param fileName 是数据文件名
@param PlanningEntryAPIs 是对读取的数据进行操作的APIs
@return 若为真,读取文件成功;否则,读取文件失败
public static boolean readData(String fileName, PlanningEntryAPIs PlanningEntryAPIs)
在航班APP中的一开始可以选择(调用本方法)自动读取文件里面的数据。如果文件不存在,将会捕获并提示异常
3.13.1 读取文件数据(需要先删除所有的空行)
以FlightSchedule_1.txt的第一个航班为例:
Flight:2020-01-16,AA018第一行是航班号,起飞日期
{第二行是左大括号
DepartureAirport:Hongkong第三行是出发机场
ArrivalAirport:Shenyang第四行是抵达机场
DepatureTime:2020-01-16 22:40第五行是起飞时间
ArrivalTime:2020-01-17 03:51第六行是抵达时间
Plane:B6967第七行是飞机编号
{第八行是左大括号
Type:A340第九行是机型
Seats:332第十行是座位数
Age:23.7第十一行是机龄
}第十二行是右大括号
}第十三行是右大括号
由于每个航班的行数是固定的13行,在读取过程中,采用一个航班为一个循环,进行读取,未发现格式错误时,调用APIs中的方法添加该航班,并分配对应的飞机资源。发现格式错误时,提示格式错误信息(行数、错误原因),结束读取
3.13.2 正则表达式
第一行:“Flight:(\d{4}-\d{2}-\d{2}),([A-Z][A-Z]\d{2}\d?\d?)”;//逗号前为航班日期,遵循 yyyy-MM-dd 格式,逗号后为航班号,由两位大写字母和 2-4 位数字构成
第二行:"\{";
第三行:“DepartureAirport:([A-Za-z]+)”;//出发机场,word
第四行:“ArrivalAirport:([A-Za-z]+)”;//抵达机场,word
第五行:“DepatureTime:(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})”;//起飞时间
第六行:“ArrivalTime:(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})”;//抵达时间
第七行:“Plane:([BN]\d{4})”;//飞机编号,第一位为 N 或 B,后面是四位数字
第八行:"\{";
第九行:“Type:([A-Za-z0-9]+)”;//机型,大小写字母或数字构成,不含有空格和其他符号
第十行:“Seats:([1-9]?\d{2})”;//座位数,正整数,范围为[50,600]
第十一行:“Age:([1-9]?\d(.\d)?)”;//机龄,数字,范围是[0,30],可最多 1 位小数或无小数,诸如 0、0.0、2、2.0、20.0 这样的表述都是对的,但 02.0、2.12 这样的表述则不符合规则
第十二行、第十三行:"\}";
其中第十行、第十一行中的范围判定是在匹配正则表达式后进行的
3.13.3 五个文件的结果
(1)FlightSchedule_1.txt
查看文件后,确实如此:
(2)FlightSchedule_2.txt
(3)FlightSchedule_3.txt
查看文件后,确实如此:
(4)FlightSchedule_4.txt
查看文件后,确实如此:
(5)FlightSchedule_5.txt
3.13.4 补充3.12.1(10)中的资源冲突
(1)FlightSchedule_1.txt
(2)FlightSchedule_2.txt
查看文件后,确实如此:
(3)FlightSchedule_3.txt
查看文件后,确实如此:
(4)FlightSchedule_4.txt
(5)FlightSchedule_5.txt
查看文件后,确实如此:
3.14 应对面临的新变化
3.14.1 变化1(航班支持经停,即包含最多 1 个中间经停机场)
(1)修改了FlightPlanning中的checkRep
(2)修改了FlightAPP中对航班计划地点的限制(2—>2或3)并增加提示信息:请输入要添加的计划项中的地点数目(2或3)
(3)在PlanningEntryTest的@Test public void testCreateFlightPlanningEntry()方法中新增加了对添加3个地点的测试
(4)修改了部分Spec
耗时7分钟,可以应对变化,代价很小
测试用例在3.14.3中给出
3.14.2 变化2(如果高铁车次已经分配了车厢资源,则不能被取消)
(1)修改了PlanningEntryAPIs中的cancelPlanningEntry方法:如果该高铁计划的状态是ALLOCATED,则不能取消,同时新增了提示信息:取消失败(该高铁计划项已经分配资源,已经不能取消)
(2)修改了部分Spec
耗时4分钟,可以应对变化,代价很小
测试用例在3.14.3中给出
3.14.3 变化3(课程可以有多个教师一起上课,且需要区分次序)
(1)Teacher类中增加了新的属性priority,意为优先度,优先度越大,在上课时的优先级就越高。修改了Teacher类的构造函数,新增加了priority参数。同时增加了对该参数的获取方法。生成了新的hashCode和equals方法
(2)修改了所有对Teacher类型变量的构造:如Resource中的createTeacher静态工厂方法;PlanningEntryTest;ResourceTest;CourseBoard;PlanningEntryAPIsTest
(3)修改了PlanningEntryAPIs 中的addTeacher的方法的参数,并去掉课程计划的资源分配上限的提示的错误信息
(4)在CoursePlanningEntry的allocateTeacher方法中,去掉课程计划的资源分配上
(5)在CourseAPP中添加提示信息"请输入优先度(正整数,数值越大,该教师上课时的优先度越高)"
(6)修改PlanningEntryTest中的@Test public void testCreateCoursePlanningEntry()之前不可以分配两位教师,现在可以,修改测试(assertFalse—>assertTrue)
(7)修改了部分Spec
耗时25分钟,由于涉及到构造函数的改变,虽说耗时很长,但是由于IDE的静态检查,可以很容易的修改代码来应对变化,代价相较于3.14.1和3.14.2略大,但是代价依旧很小
3.14.4 测试用例
修改后,测试用例通过:
3.15 Git仓库结构
4 实验进度记录
略
5 实验过程中遇到的困难与解决途径
略
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
6.2 针对以下方面的感受
(1) 重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在五个不同的应用场景下使用,你是否体会到复用的好处?
我认为面向ADT的编程像是在做一块一块的小零件,零件应该设计成什么样子,各个零件之间如何拼接,以及如何保护零件不被恶意破坏,最后将这些零件组合起来,形成供用户使用的产品,后续扩展是,只需更换零部件即可而面向应用场景编程,更像工厂中的模具,表面上看压的很漂亮,然而它的内部却是实心的,要想改变其样子,只能重新再压一个。虽说也能完成任务,但是很不利于对后续的扩展我确实体会到了复用的好处,尤其是在开发APP时,由于大部分功能都是类似或者完全一样,因此代码层面的复用用的很多
(2) 重新思考Lab2中的问题:为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后的编程中坚持这么做?
意义就是提升可维护性,可读性,安全性。因为不能确保ADT一写完以后就不会添加新的功能,也不能保证每一个客户都是善意的,并且实验三的任务很多,时间跨度很大,有时候需要使用一周前甚至是两周前写的ADT。因此这些specification, invariants, RI, AF,以及防止rep exposure是一个ADT中必不可少的内容。我愿意在以后编程中坚持这么做
(3) 之前你将别人提供的API用于自己的程序开发中,本次实验你尝试着开发给别人使用的API,是否能够体会到其中的难处和乐趣?
难处主要体现在:需要设计许多的方法,虽然这些方法多是委派给其他的类,但由于我在设计APIs方法时,会给出提示信息,方便了用户,自然就,麻烦了自己
乐趣主要体现在:由于采用了facade模式,在设计APP时不必在东找西找,极大的提高了开发APP的速度
(4) 在编程中使用设计模式,增加了很多类,但在复用和可维护性方面带来了收益。你如何看待设计模式?
我认为设计模式就是过来人的总结,对我们这些后来人有很大的作用。就拿facade模式来说,如果没有采用facade模式设计APIs,设计APP时的方法调用就会杂乱无章,拖慢了开发速度
(5) 你之前在使用其他软件时,应该体会过输入各种命令向系统发出指令。本次实验你开发了一个解析器,使用语法和正则表达式去解析输入文件并据此构造对象。你对语法驱动编程有何感受?
我认为这很方便。以前,我处理(截取、比较)字符串时,要一个字符一个字符去判断,通常会写很多的if。然而,采用语法驱动编程后,直接调用.matches方法就能很容易的比较,调用.group()方法就能很容易的截取出想要的字符串。大大提高了变成速度
(6) Lab1和Lab2的大部分工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验是你完全从0开始进行ADT的设计并用OOP实现,经过五周之后,你感觉“设计ADT”的难度主要体现在哪些地方?你是如何克服的?
难点是:在设计一个ADT时不能单单考虑这一个ADT,还要考虑它与其他ADT的联系。需要纵观整个实验,才能把握好每一个ADT的设计
克服方法:实验的前几周先学习课件、熟读手册、在piazza上面看看大家的问题,然后再开始写我的实验,虽说进度落后,但能少走弯路,节约时间
(7) “抽象”是计算机科学的核心概念之一,也是ADT和OOP的精髓所在。本实验的五个应用既不能完全抽象为同一个ADT,也不是完全个性化,如何利用“接口、抽象类、类”三层体系以及接口的组合、类的继承、设计模式等技术完成最大程度的抽象和复用,你有什么经验教训?
①尽量不要让用户直接对一个具体类进行操作,多用接口隔开
②设计静态方法可以隐藏子类名字,不必使用new方法来构造子类
③一个ADT中最好只出现相关的方法,其他不相关的方法多用委派
(8) 关于本实验的工作量、难度、deadline。
由于是从0开始,工作量很大,难度自然也是很大的,有很多天我都写到凌晨两三点。不过5周的时间加上late days,还是能完成实验的
(9) 到目前为止你对《软件构造》课程的评价。
在学完本课程的重点难点后,体会到该课程的好处以及对我今后的深远影响