2020年春季学期
计算机学院《软件构造》课程
Lab 3实验报告
3.2 面向可复用性和可维护性的设计:PlanningEntry<R>· 1
3.2.1 PlanningEntry<R>的共性操作··· 1
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)··· 2
3.6 面向复用的设计:EntryState及State设计模式··· 2
3.9 PlanningEntryCollection的设计··· 2
3.10.1 检测一组计划项之间是否存在位置独占冲突··· 2
3.10.2 检测一组计划项之间是否存在资源独占冲突··· 2
根据实验手册简要撰写。
本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护
性的软件,主要使用以下软件构造技术:
- 子类型、泛型、多态、重写、重载
-
- 继承、代理、组合
- 常见的 OO 设计模式
-
- 语法驱动的编程、正则表达式
- 基于状态的编程
- API 设计、API 复用
- 本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管
- 理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程
- 实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充
- 分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)
- 和更容易面向各种变化(可维护性)。
实验环境设置请参见 Lab-0 实验指南。
本次实验在 GitHub Classroom 中的 URL 地址为:
https://classroom.github.com/a/aMg3ti15
请访问该 URL,按照提示建立自己的 Lab3 仓库并关联至自己的学号。
本地开发时,本次实验只需建立一个项目,统一向 GitHub 仓库提交。实验
包含的多个任务分别在不同的包内开发,具体目录组织方式参见各任务最后一部
分的说明。请务必遵循目录结构,以便于教师/TA 进行测试。
https://github.com/ComputerScienceHIT/Lab3-1180300604.git
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
1.航班管理
2.高铁车次管理
4.大学课表管理
分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
Flight 和 Course 十分类似 可以从他们的board程序里面看出来,表格是根据地点来绘制的,但是这两个都只有1个location,不需要经停,开始之后都不能阻塞
Train有一个最大的不同,它含有多个location,且每个location都有经停时间,同时表格上也有差异,但是board的显示上面有一些区别
-
- 面向可复用性和可维护性的设计:PlanningEntry<R>
该节是本实验的核心部分。
程序的结构主要是
- 首先有一个接口
- 接着是一堆抽象类来对应上面的接口
- 然后是common的各个基本类,继承上述抽象类,这些类都是个体类针对个体,单个计划,单个资源,单个地点,单个时间
- 在之后是common_s类,采用装饰模式,没有继承,直接设置了包含上面单个common类的list,大多数类是通过list来集成的
- APP
-
-
- PlanningEntry<R>的共性操作
-
都是针对单个计划项的各种操作。
主要是对于里面的list的操作,增加或者移除计划
-
-
- 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
-
在装饰过的第二子类中方法比较完善,因此例如像上述的第三子类不太需要太多的特定方法,有一些特定方法放在了app类里
此处的泛型R,L,都是代表类似Flight_Resource, Flight_Location的类,需要在创建CommonPlanningEntry的时候传入进去,因此可以改成Train_Location等等
-
- 面向复用的设计:Location
首先是一个接口
Location的抽象类没有补充太多属性和方法
再用对应于单个location 的commonlocation来继承添加部分属性重写一个方法
采用装置模式,产生了一个包含多个单个location的common_s类
包含了众多处理list的方法,其中包含几个函数,传入参数是String,方便在app即客户端口,通过读入的string 直接对list做处理。
后面 再针对不同的需求添加方法
-
- 面向复用的设计:Timeslot
首先是一个接口,这里主要采用了localdatetime这个类来表示时间,在app里面用户每操作一次,将计划时间和现有时间做一次对比,将所有计划项的时间进行更新。
抽象类没有补充太多
后面的继承类就添加了单个时间的年月日小时分钟秒,还有个总扩的time(localdatetime)
update_settime就是讲属性里的int同步到time里面,还有个比较重要的方法是time_deal
在客户端输入时,客户往往输入的是字符串,比如日期 “2020:1:1“ 时间 “5:00”
Time_deal函数可以直接被调用,判断读入的是日期字符串还是时间字符串,将读入数据更新到属性的year month……等。
由于采用装饰模式,不想要类之间有太多的耦合,所以之间创建了存储多个时间的list
对于航班计划来说,就两个时间,高铁有多个,但都可以用list这种动态数组来存储,有很好的复用性。
后面针对 航班高铁课程 再继续继承可扩展的子类。
-
- 面向复用的设计:EntryState及State设计模式
State类的作用包含在了planningentry类里面,这里不赘述。
-
- 面向应用的设计:Board
针对3个APP,4个方法。
显示的gui主要用的是
Jtable表格方法
效果如图
这里是英文界面,设置的地点是“2”可以用“哈尔滨”等代替
-
- Board的可视化:外部API的复用
主要采用java JTable 的api,效果还可以。
命名为Common_PlanningEntries,内置list来集合计划
-
- 可复用API设计及Façade设计模式
通过判断资源state标志位,在common类里面置了state标志位,在app中也有检查
通过判断资源state标志位,在common类里面置了state标志位,在app中也有检查
Resource 和location里面都有标志位和相应方法来判断占用。
请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。
可以使用匿名类的方式来实现 但是也可以在子类中创建 创建方法如:
也可以在别的类里面创建类似的方法构成工厂模式。
Iterator比较与list有些复杂,list作为可变数组非常好用,唯一不足是在迭代过程中无法remove.
针对特定List<R> 的add操作就有两种算法,一种传入参数是R一种是String,在app中都有采用。String的更加方便。
这是两种截然不同 的add方法,传入参数明显string较为方便
利用上述设计和实现的ADT,实现手册里要求的各项功能。
只需保留你选定的三个应用即可。
首先创建了上述的多个航班类的对象,用来管理资源和计划,
之后的方法对应用户可以进行的操作,在main函数中是首先打印出可以进行的操作,然后和用户交互,根据用户输入对应调用上述函数
例如add_flightentry函数,内部十分复杂,因为要检测资源,地点是否存在是否可用等等。
public static void add_flightentry() {
System.out.println("adding.............");
System.out.println("please input the start and end time start and end place, type of the plane");
System.out.println("for example: Chengdu Beijing c919 2020:6:1 9:00 12:00");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String input_entry = null;
try {
input_entry = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
String divide[] = input_entry.split(" ");
//input check
boolean check_flag1,check_flag2,check_flag3;
check_flag1=flight_location.check(divide[0]);
check_flag2=flight_location.check(divide[1]);
check_flag3=flight_resource.check(divide[2]);
if ((check_flag1 == false) || (check_flag2 == false) || (check_flag3 == false)) {
System.out.println("Plane modle or locations are not added.");
System.out.println("Add failed.");
return;
}
//location resource deal
CommonLocation need_add0 = new CommonLocation();
CommonLocation need_add1 = new CommonLocation();
Flight_Resource need_add2 = new Flight_Resource();
Flight_Location need_add3 = new Flight_Location();
for (CommonLocation i : flight_location.common_locations) {
if (divide[0].equals(i.get_location())) {
need_add0=i;
}
if (divide[1].equals(i.get_location())) {
need_add1=i;
}
}
need_add3.add(need_add0);
need_add3.add(need_add1);
for(CommonResource i : flight_resource.common_resources) {
if (divide[2].equals(i.get_resource())) {
need_add2.common_resources.add(i);
}
}
//time deal
Common_Times need_addTimes = new Common_Times();
CommonTime need_addTime1 = new CommonTime();
CommonTime need_addTime2 = new CommonTime();
need_addTime1.time_deal(divide[3]);
need_addTime1.time_deal(divide[4]);
need_addTime1.date_copy(need_addTime2);
need_addTime2.time_deal(divide[5]);
need_addTime1.update_settime();
need_addTime2.update_settime();
need_addTimes.add_times(need_addTime1);
need_addTimes.add_times(need_addTime2);
flight_PlanningEntries.add_entry(need_addTimes,need_add2,need_add3);
System.out.println("All entries:");
show_fightenties();
}
System.out.println("input middle location and time(like Chengdu 9:00 9:05 Meishan 10:00 10:07):");
BufferedReader reader1 = new BufferedReader(new InputStreamReader(System.in));
String input_entry1 = null;
try {
input_entry1 = reader1.readLine();
} catch (IOException e) {
e.printStackTrace();
}
String divide1[] = input_entry1.split(" ");
CommonLocation need_addx = new CommonLocation();
CommonTime need_addy = new CommonTime();
CommonTime need_addz = new CommonTime();
for(int i=0;i<divide1.length;i=i+3) {
for (CommonLocation o : flight_location.common_locations) {
if (divide1[i].equals(o.get_location())) {
need_addx=o;
}
}
need_addy.time_deal(divide1[i+1]);
need_addTime1.date_copy(need_addy);
need_addz.time_deal(divide1[i+2]);
need_addTime1.date_copy(need_addz);
need_add3.add(need_addx);//l
need_addTimes.add_times(need_addy);//t
need_addTimes.add_times(need_addz);//t
主要区别是中间有多个站点,同时每个站点包含到达时间,出发时间,所以用户需要再次输入中间站点。
// flight_resource.add_resource("a", "plane");
// flight_resource.add_resource("b", "plane");
// flight_resource.add_resource("c", "plane");
// flight_resource.add_resource("d", "plane");
// flight_location.add_location("1");
// flight_location.add_location("2");
// flight_location.add_location("3");
// flight_location.add_location("4");
System.out.println("start time: "+now_time);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("please choose the operation(example: if you choose the first operation please input 1): ");
System.out.println("1. add a plane");
System.out.println("2. delete a plane");
System.out.println("3. add an flight entry");
System.out.println("4. cancle an flight entry");
System.out.println("5. set resource to a flight entry");
System.out.println("6. change location of a flight entry");
System.out.println("7. see an entry's state");
System.out.println("8. location board");
System.out.println("111. exit");
int input_number=0;
while(input_number!=111) {
input_number=0;
try {
input_number=Integer.valueOf(reader.readLine());
} catch (IOException e) {
// TODO 自动生成的 catch 块
System.out.println("get input failed");
e.printStackTrace();
}
switch(input_number) {
case 1:
add_plane();
update();
break;
case 2:
delete_plane();
update();
break;
case 3:
add_flightentry();
update();
break;
case 4:
delet_flightentry();
update();
break;
case 5:
set_resource_to();
update();
break;
case 6:
change_location();
update();
break;
case 7:
see_state();
update();
break;
case 8:
show_board();
update();
break;
case 9:
break;
case 111:
System.exit(0);
break;
}
}
}
和飞机计划相比差别不是很大,主要注意要调用board类里面不同的方法,对应课表的显示方式。
修改“航班”应用以扩展该功能,读入text 按照指定格式,添加一个文本读写功能
使用bufferedreader,filereader类操作文件读写。
传入要更改的航班计划类,资源类,地点类,在函数中对这些类做出更改。
核心处理部分主要是发现13行一组的规律,通过对字符串的处理,还有split函数应用,类似于if语句,对输入文件进行读取。
其实飞机增加经停点和火车的差异不是很大,但还是需要做一些小改动
分支创建了一个新的app叫F314change.java
-
- Git仓库结构
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2020.5.1 | 8:00-23:00 | 读透实验手册 | 没有怎么读懂,后面和同学讨论,确定了大致采用装饰模式 |
2020.5.8 | 7:00-24:00 | 完成common,commons类 | 基本完成,复用性不好 |
2020.5.15 | 6:30-23:00 | 完成app类 | 还不错 |
20205.16 | 7:00-23:00 | 完成扩展内容 | Test没写完,分支还没完善 |
遇到的难点 | 解决途径 |
Gui不会
| 上网学习,决定使用JTable |
List无法remove
| 在迭代中无法remove,flight_PlanningEntries.remove_entry(j);选出来之后再remove |
Test没有头绪
| 使用app做测试,但其实还是需要习惯测试优先。 |
文件读入不熟练 | 上网学习 |
难度很大,自己还有继续好好学习java,感觉能写出复用性好的api确实是了不起的
- 重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在五个不同的应用场景下使用,你是否体会到复用的好处?
答:后面几个app可以轻松复用,不怎么需要重写
- 重新思考Lab2中的问题:为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后的编程中坚持这么做?
答:方便后续阅读代码,别人阅读代码,比较愿意
- 之前你将别人提供的API用于自己的程序开发中,本次实验你尝试着开发给别人使用的API,是否能够体会到其中的难处和乐趣?
答:难处是接口不清晰,不熟练,乐趣是简单快捷。
- 在编程中使用设计模式,增加了很多类,但在复用和可维护性方面带来了收益。你如何看待设计模式?
答:非常好用非常有必要
- 你之前在使用其他软件时,应该体会过输入各种命令向系统发出指令。本次实验你开发了一个解析器,使用语法和正则表达式去解析输入文件并据此构造对象。你对语法驱动编程有何感受?
答:方便快捷,应该非常实用,可以写系统。
- Lab1和Lab2的大部分工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验是你完全从0开始进行ADT的设计并用OOP实现,经过五周之后,你感觉“设计ADT”的难度主要体现在哪些地方?你是如何克服的?
答:主要是架构要提前打好,磨刀不误砍柴工,慢慢来,考虑架构花了很长时间。
- “抽象”是计算机科学的核心概念之一,也是ADT和OOP的精髓所在。本实验的五个应用既不能完全抽象为同一个ADT,也不是完全个性化,如何利用“接口、抽象类、类”三层体系以及接口的组合、类的继承、设计模式等技术完成最大程度的抽象和复用,你有什么经验教训?
答:一定要找好同与不同,不然后面继承过于混乱。
- 关于本实验的工作量、难度、deadline。
答:工作量太大,不是很难,ddl延长一周更好
- 到目前为止你对《软件构造》课程的评价。
答:感觉学到了太多东西,发现自已以前的程序非常混乱。。。学到的ADT方法,设计模式,让我编程的逻辑更加清晰,能搞大软件了。