1 实验目标概述
本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护
性的软件,主要使用以下软件构造技术:
- 子类型、泛型、多态、重写、重载
- 继承、代理、组合
- 常见的 OO 设计模式
- 语法驱动的编程、正则表达式
- 基于状态的编程
- API 设计、API 复用
本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。
2 实验环境配置
实验环境设置请参见 Lab-0 实验指南。
本次实验在 GitHub Classroom 中的 URL 地址为:
https://classroom.github.com/a/aMg3ti15
请访问该 URL,按照提示建立自己的 Lab3 仓库并关联至自己的学号。
本地开发时,本次实验只需建立一个项目,统一向 GitHub 仓库提交。实验包
含的多个任务分别在不同的包内开发,具体目录组织方式参见各任务最后一部分 的说明。请务必遵循目录结构,以便于教师/TA 进行测试
3 实验过程
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 待开发的三个应用场景
列出你所选定的三个应用。
- 航班管理
- 高铁车次管理
- 大学课表管理
分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
三个应用都可以看作是有一个个计划项构成的
计划项则可以抽象的看作是资源,地点,时间的集合
航班和课程都使用单个可区分的资源,而高铁是多个资源
航班和课程不可阻塞,而高铁可以阻塞
三个应用的时间均可以设定
航班需要有两个位置信息,高铁则是至少两个,课程则仅需要一个
3.2 面向可复用性和可维护性的设计:PlanningEntry
该节是本实验的核心部分。
3.2.1 PlanningEntry的共性操作
在我的设计当中,Planningentry主要的功能是完成计划项状态的转变,还有就是获取计划项的名字
其中,allocated状态由于其操作的特殊性,我将其放到子类当中个性化实现
3.2.2 局部共性特征的设计方案
Commonplanning主要是对于planningentry的实现,保存有计划项的名字属性,同时实现状态转换功能,这里以running为例
同时,这个类还具有获取名字的功能
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
使用实验手册中给出的方案五:
方案 5:CRP,通过接口组合实现局部共性特征的复用
针对方案 4,通过 delegation 机制进行改造。每个维度分别定义自己的接
口,针对每个维度的不同特征取值,分别实现针对该维度接口的不同实现类,
实现其特殊操作逻辑。
进而,通过接口组合,将各种局部共性行为复合在一起,形成满足每个应
用要求的特殊接口(包含了该应用内的全部特殊功能),从而该应用子类可直接
实现该组合接口。
在应用子类内,不是直接实现每个特殊操作,而是通过 delegation 到外部每
个维度上的各具体实现类的相应特殊操作逻辑。
以航班管理中的FlightEntry类为例来说
首先分析航班的特性,有两个位置,一个时间对,不可阻塞,且只有一个资源,因此设置需要委托的类并且设计AF,RI
通过构造器设置委托关系
通过委托TwoLocationEntryimpl等三个类来进行计划项的set方法
值得一提的是,资源在初始化创建计划项的时候,是不用设置的,当allocated被调用的时候才进行设置
Flightentry剩下的就是一些get方法了。
其余的两个类和flightentry这个类骑士大同小异,主要的方法目的都是一样的,只是由于各计划项的特殊性做了一些改变而已,不再赘述。
3.3 面向复用的设计:R
首先,每个资源都有自己的ID,所以在资源接口中定义getnumber方法,便于比较。
具体的来说,针对于三个应用,设计Plane,SingleTrain,Teacher这三个类,代表计划项所需要的资源。
三个类也大同小异,这里根据plane类来进行说明
在构造器中,根据获得的数据,来进行资源类的创建。
对于singleresourseentry
多个资源的mutipleresourceentry与其思想也差不多
3.4 面向复用的设计:Location
与资源类不同,location所需要的属性使得location类比较通用
除此之外,Location类还需要一些列的get方法来获得属性
本来是想着把Location做成一个借口,然后分别用机场,高铁站,教室来进行实现,后来在写代码的过程中,发现我的设计并没有这么做的必要,因此,就直接使用了一个Location类。
三个与位置有关的借口。
3.5 面向复用的设计:Timeslot
与时间有关的类:
3.6 面向复用的设计:EntryState及State设计模式
在我的设计当中,状态和实际操作是分开实现的,因此,可以理解为状态类指示记录计划项当前的状态。
所有的状态:
在每个状态中,都有到其他状态的方法,但是,如果当前状态下不能够转换到另外一个状态,则直接返回false
以waiting状态举例
在waiting状态下,可以进入cancelled状态或者allocated状态,因此这两个能返回true,并设置新状态,其余的方法则是直接打印错误并返回false
状态之间的转移关系:
上面是可阻塞的计划项状态关系,下面是不可阻塞的计划项状态关系
3.7 面向应用的设计:Board
由于board并没有太多共通的地方,因此没有采取复用设计,直接简单粗暴的设计了三块板子,首先是非可视化的方法
以航班信息板的显示即将到达的航班为例
三块信息板的方法都差不多,不多赘述
3.8 Board的可视化:外部API的复用
按照实验手册所给出的信息板,进行可视化操作
航班的可视化:
思想就是首先建立一个二维数组,然后根据手册给出模板
来进行数据的填充,然后使用Jtable做成表格形式再打印
实际效果:
3.9 可复用API设计及Façade设计模式
3.9.1 检测一组计划项之间是否存在位置独占冲突
因为高铁站和飞机场是可共享的,因此这两个应用不会发生位置冲突
课程则需要进行检查
3.9.2 检测一组计划项之间是否存在资源独占冲突
还是以航班为例
思想就是通过循环,遍历同一个资源的两个计划项,然后判断在时间上有无冲突
3.9.3 提取面向特定资源的前序计划项
3.10 设计模式应用
请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。
3.10.1 Factory Method
3.10.2 Iterator
3.10.3 Strategy
3.11 应用设计与开发
利用上述设计和实现的ADT,实现手册里要求的各项功能。
只需保留你选定的三个应用即可。
3.11.1 航班应用
我将操作分成两个菜单,第一个菜单就是进入系统看到的菜单,第二个则是在第一个菜单中选择6后会显示的菜单。
分别保存资源,地点,计划项和用户选择
至于具体的实现,主体框架是通过switch case来实现的,然后在switch中调用线管的操作,叙述起来过于繁琐,因此不赘述
3.11.2 高铁应用
分别代表 一节车厢,车站,高铁计划项
3.11.3 课表应用
这个应用和航班管理很像很像,区别就是课程管理只有一个地点并且涉及到更换地点也就是教室的操作。
分别代表 老师,教室和课程
3.12 基于语法的数据读入
修改“航班”应用以扩展该功能。
首先观察给定的正则表达式
发现每个航班都是由13行构成的(忽略空行)
因此,我们以读13行为一组,调用通过正则表达式创建航班的函数
同时,这个创建函数还要对于报告中对于文件数据的要求进行检测,如果不符合,就抛出异常,如果符合要求,就加入到最后要返回的计划项当中。
3.13 应对面临的新变化
只考虑你选定的三个应用的变化即可。
3.13.1 变化1
评估之前的设计是否可应对变化、代价如何
如何修改设计以应对变化
将PlaneEntry所继承的TwoLocationEntry变为ThreeLocationEntry
之后在每个涉及到位置的地方进行一些操作就行
比如:
创建航班时:
当然我感觉更进一步最好是将经停的起飞降落时间也考虑进去,但是由于时间和代码复用性的局限,没有去考虑这块。
3.13.2 变化2
评估之前的设计是否可应对变化、代价如何
如何修改设计以应对变化
在对车次就行取消之前判断当前状态即可
3.13.3 变化3
评估之前的设计是否可应对变化、代价如何
如何修改设计以应对变化
将原先courseentry继承的singleresourceent变成mutipleresoourceentry
然后对于资源的操作参照高铁的就行。
将courseentry中和资源有关的操作参照高铁的Trainentry然后在app中关于分配资源的代码也参照高铁的修改一下
3.14 Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚314change分支和master分支所指向的位置。