Lab3 Report
1 实验目标概述
本次实验覆盖课程第3、4、5章的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 常见的OO设计模式
⚫ 语法驱动的编程、正则表达式
⚫ 基于状态的编程
⚫ API设计、API复用
本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程实现,而是通过ADT和泛型等抽象技术,开发一套可复用的ADT及其实现,充分考虑这些应用之间的相似性和差异性,使ADT有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。
2 实验环境配置
在eclipse中直接新建项目。
3 实验过程
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 待开发的三个应用场景
我选择了:
- 航班管理
- 高铁车次管理
- 课程管理
异同点: - 位置有1个、2个、多个
- 课程的位置可更改
- 资源有1个或多个
- 高铁可阻塞
- 均可设定时间
3.2 面向可复用性和可维护性的设计:PlanningEntry
该节是本实验的核心部分。
3.2.1 PlanningEntry的共性操作
操作和获取计划项的状态,比如开始、取消等等,获取所用的资源、位置、时间。获取计划项名字。
/**
* 开始计划项
*/
public void start();
/**
* 取消计划项
*/
public void cancel();
还有一些getter。
3.2.2 局部共性特征的设计方案
用一个抽象类CommonPlanningEntry实现了PlanningEntry中大部分的方法,由于一些关于时间和地点的操作是个性化的,所以此处采用抽象类和抽象方法实现。
abstract public List<Timeslot> getTimes();
abstract public List<Location> getLocationss();
abstract public List<R> getResource();
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
采用了CRP接口组合的方式,根据个性化的特征分做了一些接口并实现,例如SingleResourceEntry、MultipleSortedResourceEntry等等,再将这些个性化的接口一一实现。再使用一个新的接口将其组合成需要的接口。
public interface FlightEntryPlanning extends TwoLocationEntry, SingleResourceEntry<Plane> {}
此时,便可以根据组合的接口FlightEntryPlanning和之前的抽象类CommonPlanningEntry写一个面向应用的FlightEntry了。其他的Entry也如此。在Entry中采用委托的机制将需要实现的功能交给构造的被委托者实现。
public FlightEntry(String name, Timeslot time, TwoLocationEntryImpl tle, SingleResourceEntryImpl<Plane> sre) {
super(name, time);
this.tle = tle;
this.sre = sre;
checkRep();
}
@Override
public void setDeparture(Location departure) {
tle.setDeparture(departure);
}
3.3 面向复用的设计:R
我选择的计划项的资源分别是飞机、车厢、老师。这些资源几乎没有什么共同点,直接把他们当作不相关的类写出来即可。对于这些资源,他们有一个构造器和几个getter,并且我重写了他们的toString、equals、hashCode方法,方便后面使用。
3.4 面向复用的设计:Location
对于Location这个设计,我想所有的实际的位置都可以用经度、纬度、名字、是否可共享几个属性来实现。于是他有这样的属性。
private final String longitude;
private final String latitude;
private final String name;
private final boolean sharable;
当然,他也有一些getter,并且它的toString、equals、hashCode方法也被重写了。
3.5 面向复用的设计:Timeslot
用Timeslot来表示一个起止时间对,时间采用LocalDateTime来实现,避免采用Date又被更改的情况发生。这个Timeslot拥有setter和getter方法,因为我们无可避免的有时候需要去修改某个计划项的时间。LocalDateTime当中保存了秒数,但是在使用的时候加上一个DateTimeFormatter来规范输入和输出的形式即可。
3.6 面向复用的设计:EntryState及State设计模式
采用State设计模式,把EntryState设计成一个接口,再用几个类来实现了WAITING、ALLOCATED、RUNNING、BLOCKED、ENDED、CANCELLED几个状态。状态的实现采用静态属性和私有化构造器的办法来节约内存。
static Allocated instance = new Allocated();
private Allocated() {}
对于合法的状态转换,直接return下一个状态,让调用状态转换的方法去修改状态。
public EntryState start() {
return Running.instance;
}
而对于不合法的状态转换,我采用抛出一个IllegalStateException()加一些提示语句的办法。
public EntryState block() {
throw new IllegalStateException("未启动");
}
3.7 面向应用的设计:Board
实验指导书中说到,Board是直接面向应用设计的,于是我直接实现了FlightBoard、TrainBoard、CourseBoard三个类。每个Board都有time和location属性来记录这个信息板要展示的时间和地点,还有一个在构造时传入的包含所有计划项的一个List,从中找出符合Board需要的计划项比如位置相同并且时间相差不超过一小时,把这些计划项放进一个新的List中保存,再把这个List中的信息用visualize()可视化的展现出来,这样就实现了Board的展示指定的计划项的功能。
private LocalDateTime time;
private final Location location;
private List<FlightEntry> arrivals = new ArrayList<>();
private List<FlightEntry> departures = new ArrayList<>();
private List<FlightEntry> entries;
public FlightBoard(Location location, List<FlightEntry> entries, LocalDateTime time)
3.8 Board的可视化:外部API的复用
可视化的功能交给visualize()方法实现,使用的是JFrame和JTable的框架。实现JTable使用的是实现抽象模型的方法。
public class FlightTableModel extends AbstractTableModel
3.9 可复用API设计及Façade设计模式
3.9.1 检测一组计划项之间是否存在位置独占冲突
设计思路是对于传入的一组计划项,如果他们当中有位置相同&&位置不可共享&&起止时间有交集的话则是存在独占冲突的,否则没有。实现上用一个双层的循环遍历,很容易就能够找出是否有冲突。
3.9.2 检测一组计划项之间是否存在资源独占冲突
与检测位置冲突其实是一样的,而且还更加简单,因为不用设置共享这个条件,与3.9.1属于代码级别的复用,将其稍加修改即可完成。
3.9.3 提取面向特定资源的前序计划项
初始化一个 PlanningEntry pre = null,再在传入的一组计划项中用一个循环遍历地查找与传入资源相同且时间最近的计划项,如果找到则赋给pre,最后直接返回pre。
3.10 设计模式应用
请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。
3.10.1 Factory Method
在一个Factory类中用static实现了三种计划项的实例化。
static public FlightEntry getFlightEntry(String name, Timeslot time) {
TwoLocationEntryImpl tle = new TwoLocationEntryImpl();
SingleResourceEntryImpl<Plane> sre = new SingleResourceEntryImpl<Plane>();
return new FlightEntry(name, time, tle, sre);
}
static public TrainEntry getTrainEntry(String name, Timeslot time, List<Timeslot> times) {
MultipleLocationEntryImpl mle = new MultipleLocationEntryImpl();
BlockableEntryImpl be = new BlockableEntryImpl();
MultipleSortedResourceEntryImpl<Carriage> msre = new MultipleSortedResourceEntryImpl<>();
return new TrainEntry(name, time, mle, be, msre, times);
}
static public CourseEntry getCourseEntry(String name, Timeslot time) {
SingleLocationEntryImpl sle = new SingleLocationEntryImpl();
SingleResourceEntryImpl<Teacher> sre = new SingleResourceEntryImpl<Teacher>();
return new CourseEntry(name, time, sle, sre);
}
注意到此时的实例化并没有直接给资源、时间表、地点等参数赋值,这些参数需要在之后采用set的方法去给出。即是说用工厂创建的这个PlanningEntry的某部分属性是需要后面set时才有的。
3.10.2 Iterator
由于实验课上提到comparator的要求存在一定问题,可以不用管,所以在Board中直接返回一个List entries的Iterator即可。
public Iterator<FlightEntry> iterator() {
return entries.iterator();
}
3.10.3 Strategy
对于APIs当中的检测位置独占冲突我采用了Strategy的模式。在与APIs的同一个package下建立了一个APIsStrategy接口,用以实现Strategy模式的检测。
public interface APIsStrategy {
@SuppressWarnings("rawtypes")
public boolean checkLocationConflict(List<? extends PlanningEntry> entries);
}
再分别用了两个类来实现这个接口,分别是SearchStrategy,MapStrategy。在使用Strategy模式的情况下,PlanningEntryAPIs中的该方法变成了如下。
public boolean checkLocationConflict(APIsStrategy s, List<? extends PlanningEntry> entries) {
return s.checkLocationConflict(entries);
}
APIsStrategy s,这个参数就可以实现选取不同的算法。
3.11 应用设计与开发
采用GUI的实现,实现时借用了WindowBuilder插件。
3.12 基于语法的数据读入
此任务最为关键的是匹配的Pattern。
Pattern pattern = Pattern.compile("Flight:\\d{4}-\\d{2}-\\d{2},([A-Z]{2}\\d{2,4})\n\\{\nDepartureAirport:([A-Za-z]+)\nArrivalAirport:([A-Za-z]+)\nDepatureTime:(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2})\nArrivalTime:(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2})\nPlane:([NB]\\d{4})\n\\{\nType:([A-Za-z0-9]+)\nSeats:(\\d{2,3})\nAge:([1-9][0-9]*([.][0-9]?))\n\\}\n\\}\n");
从文件中按字符读入每当读入一个字符就进行匹配,若匹配上了就根据分组匹配得到的信息新建飞机、机场、时间、计划项,将其加入保存计划项的List当中,然后清空读入的信息继续读入。
3.13 应对面临的新变化
只考虑你选定的三个应用的变化即可。
3.13.1 变化1
对于航班的这个变化,相对而言是三个变化当中我改动最大的,原因是我保存时间和地点分别使用了一个Timeslot和两个Location。导致我想要改动时将其改成了MultipleLocationEntry这个接口,由于是基础组合接口的改动,后面的改动就显得有些多了。
写报告时突然想到可以采用List和List去保存,这样的话我就不用去改接口了,只需要在list当中add即可。
3.13.2 变化2
针对高铁这个改动,我的改动是很简单的,只需要在App中改状态时加入判断是否是从ALLOCATED到CANCELLED的状态改变,如果是就抛出异常。当然也可以从根本上的EntryState方面下手,不过这样的话稍显麻烦了。
3.13.3 变化3
针对课程的这个改变,其实是与变化1类似的,也是组合接口方面的改变,从SingleResourceEntry变成了MultipleSortedResourcenEntry。不过不同的是,我最开始就采用了List来保存资源,所以此处的改动相对于变化1还是很小的,也很容易修改。
3.14 Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚314change分支和master分支所指向的位置。
上一篇: 查看MySQL连接数的实际操作流程
下一篇: 正则表达式系统教程_PHP