欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件构造Lab3心得体会

程序员文章站 2024-02-09 17:25:16
...

1实验目标概述
本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护
性的软件,主要使用以下软件构造技术:
⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 常见的 OO 设计模式
⚫ 语法驱动的编程、正则表达式
⚫ 基于状态的编程
⚫ API 设计、API 复用
本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管
理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程
实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充
分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)
和更容易面向各种变化(可维护性)
2实验环境配置
简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。

建立本地仓库并远程连接github仓库即可。其他环境前几次实验已经配置。

3实验过程
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1待开发的三个应用场景
列出你所选定的三个应用。
分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
我所选定的三个应用是航班计划项,高铁计划项和课程计划项。
共性:
1.各自都有其计划项名称并都应有获取计划项名称的方法
2.都需要创建计划项
3.都需要人为启动,取消,结束计划项
4.都应可以获取当前状态
5.都应可以获取其计划启动时间和计划结束时间
6.创建位置所需的信息相同

差异:
1.各自使用的资源不同,航班使用飞机,高铁使用车厢,课程使用教师
2.所需的资源数量和位置数量不同
3.高铁可阻塞,可重启,而航班及课程不可以
4.机场与高铁站可共享,教室不可共享
5.创建计划向所需的信息各不相同
6.创建资源所需的信息各不相同
7.状态的转换不同
8.课程可以更改教室,而航班和高铁无法更改设定好的位置
3.2面向可复用性和可维护性的设计:PlanningEntry
该节是本实验的核心部分。
3.2.1PlanningEntry的共性操作
在接口中声明了以下共性方法:

1.create a new PlanningEntry
public void CreatePlanningEntry() throws ParseException;

2.start the plan
public boolean startPlan();

3.cancel the plan
public boolean cancelPlan();

4.complete the plan
public boolean completePlan();

5.get the name of the PlanningEntry
public String getPlanningEntryName();

6. get the current state
public EntryState getCurrentState();

7.get the start time
public Calendar getStartTime();

8.get the end time
public Calendar getEndTime();

3.2.2局部共性特征的设计方案
在CommonPlanningEntry中,添加了String类型的字段PlanName记录计划项的名称,并实现了getPlanningEntryName()方法,其他方法重写为抽象方法,在其对应子类中实现。

protected String PlanName;
@Override
public abstract void CreatePlanningEntry() throws ParseException;

@Override
public abstract boolean startPlan();

@Override
public abstract boolean cancelPlan();

@Override
public abstract boolean completePlan();

@Override
public String getPlanningEntryName() {
	return PlanName;
}

@Override
public abstract EntryState getCurrentState();

@Override
public abstract Calendar getStartTime();

@Override
public abstract Calendar getEndTime();

3.2.3面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
本次实验我采用了CRP的设计模式,通过将各种特征抽象为接口,并派生子类实现各个接口,通过接口组合来实现局部共性特征的复用。
1.根据资源数量的不同我定义了SingleResourceEntry和MultipleSortedResourceEntry两个接口,并派生子类SingleResourceEntryImpl和MultipleSortedResourceEntryImpl来实现这两个接口,两个接口均实现了addResource()方法,作用是将资源添加到计划项中存储
2.根据位置数量的不同我定义了SingleLocationEntry,DoubleLocationEntry和MultipleLocationEntry三个接口,并分别派生子类SingleLocationEntryImpl,DoubleLocationEntryImpl,MultipleLocationImpl来实现这三个接口,三个接口均实现了setLocations()方法,并定义了自己的字段来存储Location
3.根据是否可被阻塞我定义了BlockableEntry接口,并派生BlockEntryableImpl子类来实现block()和unblock()方法,作用分别是阻塞计划项和重启动计划项,并记录了阻塞和重启动的时间
4.航班计划项的构造:
我创建了一个接口FlightPlanningEntry,由于航班的资源飞机只有一架,而位置有出发机场和抵达机场两个,因此该接口继承了DoubleLocationEntry,SingleResourceEntry两个接口。然后派生FlightEntry类来继承该接口。在FlightEntry的构造器中我传入了SingleResourceEntryImpl和DoubleLocationEntryImpl类型的实例,用于委托。除了CommonPlanningEntry中待实现的方法,我还添加了getStartAirport(),getEndAirport(),getPlane()等方法。FlightEntry中字段及作用如下:PlaneContext是一个没有阻塞状态的状态管理器,负责控制航班状态的变化,sre是一个SingleResourceEntryImpl类型的实例,用于委托, dle是一个DoubleLocationEntryImpl,用于实现委托,in用来从控制台传入信息,timeslot用来记录起飞和抵达时间。
5.高铁计划项的构造:
我创建了一个接口TrainPlanningEntry,由于高铁的资源车厢有多个且有次序,位置也有多个经停火车站,且支持阻塞操作,因此该接口继承了MultipleLocationEntry,MultipleSortedResourceEntry,BlockableEntry三个接口。然后派生TrainEntry类来继承该接口。在TrainEntry的构造器中我传入了MultipleSortedResourceEntryImpl和MultipleLocationEntryImpl,BlockableEntryImpl类型的实例,用于委托。除了CommonPlanningEntry中待实现的方法,我还添加了getExpectedReaching(),getExpectedLeaveTime(),block(),unblock(),getCarriages(),getStations()等多个方法。TrainEntry中字段及作用如下:TrainContext是一个含有阻塞状态的状态管理器,负责控制高铁状态的变化,msre是一个MultipleSortedResourceEntryImpl类型的实例,用于实现委托, mle是一个MultipleLocationEntryImpl,用于实现委托,in用来从控制台传入信息,timeslot用来记录到达和离开各个车站时间,be是一个BlockableEntryImpl类型的实例,用来实现委托。
6.课程计划项的构造:
我创建了一个接口CoursePlanningEntry,由于课程的资源老师只有一位,而位置也只有一间教室,因此该接口继承了SingleLocationEntry,SingleResourceEntry两个接口。然后派生CourseEntry类来继承该接口。在CourseEntry的构造器中我传入了SingleResourceEntryImpl和SingleLocationEntryImpl类型的实例,用于委托。除了CommonPlanningEntry中待实现的方法,我还添加了getClassroom(),getTeacher()等方法。CourseEntry中字段及作用如下:CourseContext是一个不含有阻塞状态的课程状态管理器,用来控制课程状态,sle是一个SingleLocationEntryImpl类型的实例,用于实现委托, sre是一个SingleResourceEntryImpl类型的实例,用于实现委托,timeslot用于记录课程的开始结束时间, in用于从控制台中输入信息。

3.3面向复用的设计:R
1.Plane类(不可变)
字段如下:
number:飞机编号;modelnumber:机型号;seatnumber:座位数;age:机龄
输入飞机编号,机型号,座位数和机龄即可创建一架飞机。方法有四个属性的get方法,无set方法,Plane是不可变的,重写了equals()方法便于比较两架飞机的异同。
2.Carriagelei(不可变)
字段如下:
number:车厢编号;type:车厢类型;staffNumber:定员数;manufactureYear:出厂年份
提供车厢编号,车厢类型,定员数,出厂年份信息即可创建一个车厢。方法有四个属性的get方法,无set方法,Carriage也是不可变的,重写了equals()方法便于比较两车厢的异同。
3.Teacher(不可变)
字段如下:
IDnumber:身份证号;name:姓名;sex:性别;title:职称
提供身份证号,姓名,性别,职称信息即可创建一个教师。方法有四个属性的get方法,无set方法,Teacher也是不可变的,重写了equals()方法便于比较两教师的异同。

3.4面向复用的设计:Location
不可变类,用于表示位置。
字段如下:
longitude:经度(非必需);latitude:纬度(非必需);name:名称;isshareable:是否可共享
提供以上信息即可创建一个地点,方法有四个属性的get方法,无set方法,Location是不可变类,我重写了equals()方法,便于比较地点的异同。
3.5面向复用的设计:Timeslot
不可变类,用于表示时间。
字段如下:
starttime:起始时间;endtime:终止时间;dateformat:SimpleDateFormat类型的实例
提供起始时间字符串和终止时间字符串即可创建一个Timeslot(格式为“yyyy-MM-dd HH:mm”),方法有starttime和endtime的get方法。
3.6面向复用的设计:EntryState及State设计模式
State设计模式主要通过将状态改变委托给各个状态自己来完成,在状态的内部实现状态的改变,从而可以避免大量的if-else语句或是switch-case语句。
其主要由四部分构成:
1.抽象上下文角色(Context):维护当前状态并将与状态相关的操作委托给当前状态对象处理
2.具体上下文(Concrete Context):实现具体上下文的管理
3.抽象状态角色(State):定义一个状态接口,共性的方法放入接口中
4.具体状态(Concrete State):实现具体状态对应的行为

抽象状态角色:
EntryState接口如下:

//状态接口
public interface EntryState {
/**
 * change the state to allocated
 * 
 * @param context
 * @return true if change successfully, else false 
 */
public boolean allocate(Context context);

/**
 * change the state to running
 * 
 * @param context
 * @return true if change successfully, else false
 */
public boolean run(Context context);

/**
 * change the state to ended
 * 
 * @param context
 * @return true if change successfully, else false 
 */
public boolean end(Context context);

/**
 * change the state to cancelled
 * 
 * @param context
 * @return true if change successfully, else false 
 */
public boolean cancel(Context context);
}

状态接口中定义了allocate,run,end,cancel四种共性方法,WAITING,ALLOCATED,CANCELLED,ENDED,RUNNING,BLOCKED具体类中分别不同的实现了EntryState接口中的四种方法,在RUNNING类中还添加了block方法,用于高铁的阻塞,在BLOCKED类中加入了unblock方法,用于高铁的重新启动。

抽象上下文接口:

public interface Context {

/**
 * change the state
 * 
 * @param state  the current state
 */
public void changeState(EntryState state);

/**
 * change the state to allocated
 * 
 * @return true if change successfully, else false
 */
public boolean allocate();

/**
 * change the state to running
 * 
 * @return true if change successfully, else false
 */
public boolean run();

/**
 * change the state to cancelled
 * 
 * @return true if change successfully, else false
 */
public boolean cancel();

/**
 * change the state to ended
 * 
 * @return true if change successfully, else false
 */
public boolean end();

/**
 * get the state
 * 
 * @return state
 */
public EntryState getState();
}

在抽象上下文角色中我定义了changeState,allocate,run,cancel,end和getState方法,在其具体类UnblockedContext中我定义了一个EntryState类state,用来存储当前状态。并实现了Context接口中的方法,将其委托给各个具体状态类去实现这些方法。对于高铁这种带有阻塞状态的计划项,我又实现了一个具体类BlockedContext,继承了UnblockedContext,其中添加了block和unblock方法,我将这两种方法的实现分别委托给了RUNNING,和BLOCKED状态类,而不是把这两种方法放在EntryState接口中作为共性方法,因为那样的话,所有的具体状态类都需要实现这两种方法,造成了代码的冗余。
3.7面向应用的设计:Board
Board是某地位置的信息板,用来展示该地的发生或即将发生的计划项,我采用了gui进行展示。
3.8Board的可视化:外部API的复用
我使用了JSwing/JTable来实现我的AirportBoard,StationBoard,ClassroomBoard的可视化,采用Box对界面进行布局,并使用了时间监听器监听当前系统时间,使展示的时间可以动态变化。

时间监听:

为了使计划项可以按顺序排列,我构造了Comparator

并使用迭代器来遍历排好序的计划项

实际结果如下:

3.9可复用API设计及Façade设计模式
3.9.1检测一组计划项之间是否存在位置独占冲突
检查位置冲突方法定义:
使用facade设计模式,将三种计划项的检查位置方法合并为一种方法:
public boolean checkLocationConflict(List<? extends PlanningEntry> entries)
首先通过instanceof判断属于何种计划项,若属于航班或者高铁则直接返回false,
若为课程则继续检查。

3.9.2检测一组计划项之间是否存在资源独占冲突
检查资源冲突方法定义:
使用facade设计模式,将三种计划项的检查资源方法合并为一种方法:
public boolean checkResourceExclusiveConflict(List<? extends PlanningEntry> entries)
首先根据instanceof判断属于何种计划项,再分别去检查是否有资源冲突,若有则返回true,若无,则返回false。不过对于还未分配资源的计划项,不需要考虑。

3.9.3提取面向特定资源的前序计划项
提取特定资源的前序计划项方法定义:
public PlanningEntry findPreEntryPerResource(R r, PlanningEntry e, List<? extends PlanningEntry> entries)
使用facade设计模式,将三种计划项的提取特定资源的前序计划项方法合并为一种方法:
首先使用instanceof判断计划项的类型,然后分别去查找前序计划项,若有则返回计划项,该计划项与指定计划项中没有其他使用该资源的计划项,所以只要挑选结束时间最晚的计划项即可,若无则返回null。

3.10设计模式应用
请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。
3.10.1Factory Method
在创建计划项时,我采用了Factory Method来创建对应的计划项。首先,我定义了一个Factory接口,里面定义了getPlanningEntry方法。

然后派生了三个具体的工厂类,分别用于构造三种不同的计划项。

3.10.2Iterator
在Board类中,我增加了迭代器,并实现Comparator进行比较

3.10.3Strategy
我选用的是检查地点冲突API,为实现Strategy模式,对检查地点策略进行动态切换,我定义了一个CheckLocationStrategy接口。

然后派生两个子类分别实现该接口。

在app中,调用该方法需要传入一个CheckLocationStrategy类型的参数,通过传入不同的子类,从而实现不同策略的切换。

3.11应用设计与开发
利用上述设计和实现的ADT,实现手册里要求的各项功能。
只需保留你选定的三个应用即可。
3.11.1航班应用
航班应用需要实现以下功能:
用户提供必要信息,管理(增加、删除)可用的资源;
用户提供必要信息,管理(增加、删除)可用的位置;
用户提供必要信息,增加一条新的计划项;
用户提供必要信息,取消某个计划项;
用户提供必要信息,为某个计划项分配资源;
用户提供必要信息,启动某个计划项;
用户提供必要信息,结束某个计划项;
用户选定一个计划项,查看它的当前状态;
检测当前的计划项集合中可能存在的位置和资源独占冲突;
针对用户选定的某个资源,列出使用该资源的所有计划项(包含尚未开始执行的、执行中的、已经结束的)。用户选中其中某个计划项之后,可以找出它的前序计划项;
选定特定位置,可视化展示当前时刻该位置的信息板
在FlightScheduleApp中,我定义以下字段:
private Scanner in = new Scanner(System.in);
//存储可用飞机
private List freePlanes = new ArrayList<>();
//存储可用机场
private List freeAirports = new ArrayList<>();
//存储计划项
private List flightEntries = new ArrayList<>();
//航班名及其取消时间
private Map<String, String> cancelNameAndTimes = new HashMap<>();
对于管理飞机和机场,我们只需要让用户输入必要信息,然后判断创建的飞机和机场是否已经存在,做出相应的操作即可。
增加计划项只需要创造对应的计划项的工厂类,由工厂类帮助我们创建即可,我判断了创建的计划项的机场是否存在,若不存在,则会自动添加到可用机场中。
结束,启动计划项,只需要判断该计划项是否存在,然后委托给计划项进行实现即可。
分配资源时,要判断资源是否存在,计划项是否存在,然后委托给计划项即可。
取消计划时,需要输入取消时间,并记录,然后委托给计划项即可。
查看当前状态,选定计划项后,判断计划项是否存在,再委托给计划项即可。
检查资源位置冲突,以及查找特定资源的前序计划项均委托给APIs来实现即可。
信息板的显示,创造Board实例,并传入待查询信息板的位置以及一组航班计划项即可。

3.11.2高铁应用
高铁应用需要实现以下功能:
用户提供必要信息,管理(增加、删除)可用的资源;
用户提供必要信息,管理(增加、删除)可用的位置;
用户提供必要信息,增加一条新的计划项;
用户提供必要信息,取消某个计划项;
用户提供必要信息,为某个计划项分配资源;
用户提供必要信息,启动某个计划项;
用户提供必要信息,结束某个计划项;
用户提供必要信息,以阻塞/挂起某个计划项;
用户提供必要信息,以重启动某个已挂起的计划项;
用户选定一个计划项,查看它的当前状态;
检测当前的计划项集合中可能存在的位置和资源独占冲突;
针对用户选定的某个资源,列出使用该资源的所有计划项(包含尚未开始执行的、执行中的、已经结束的)。用户选中其中某个计划项之后,可以找出它的前序计划项;
选定特定位置,可视化展示当前时刻该位置的信息板
在TrainScheduleApp中,我定义以下字段:
private Scanner in = new Scanner(System.in);
//存储可用车厢
private List freeCarriages = new ArrayList<>();
//存储可用车站
private List freeStations = new ArrayList<>();
//存储计划项
private List trainEntries = new ArrayList<>();
//车次名及其取消时间
private Map<String, String> cancelNameAndTimes = new HashMap<>();
对于管理车厢和车站,我们只需要让用户输入必要信息,然后判断创建的车厢和车站是否已经存在,做出相应的操作即可。
增加计划项只需要创造对应的计划项的工厂类,由工厂类帮助我们创建即可,我判断了创建的计划项的车站是否存在,若不存在,则会自动添加到可用车站中。
结束,启动计划项,只需要判断该计划项是否存在,然后委托给计划项进行实现即可。
分配资源时,要判断资源是否存在,计划项是否存在,然后委托给计划项即可。
取消计划时,需要输入取消时间,并记录,然后委托给计划项即可。
查看当前状态,选定计划项后,判断计划项是否存在,再委托给计划项即可。
检查资源位置冲突,以及查找特定资源的前序计划项均委托给APIs来实现即可。
信息板的显示,创造Board实例,并传入待查询信息板的位置以及一组航班计划项即可。
阻塞计划项时,给定阻塞计划项的名称,判断是否存在,然后输入阻塞时间即可阻塞,委托给计划向即可。
重启计划项时,给定重启计划项的名称,判断是否存在,然后输入重启时间即可重启,委托给计划向即可。

3.11.3进程应用
3.11.4课表应用
课程应用需要实现以下功能:
用户提供必要信息,管理(增加、删除)可用的资源;
用户提供必要信息,管理(增加、删除)可用的位置;
用户提供必要信息,增加一条新的计划项;
用户提供必要信息,取消某个计划项;
用户提供必要信息,为某个计划项分配资源;
用户提供必要信息,启动某个计划项;
用户提供必要信息,结束某个计划项;
用户选定一个计划项,查看它的当前状态;
用户提供必要信息,以变更某个已存在的计划项的位置;
检测当前的计划项集合中可能存在的位置和资源独占冲突;
针对用户选定的某个资源,列出使用该资源的所有计划项(包含尚未开始执行的、执行中的、已经结束的)。用户选中其中某个计划项之后,可以找出它的前序计划项;
选定特定位置,可视化展示当前时刻该位置的信息板
在CourseCalendarApp中,我定义以下字段:
private Scanner in = new Scanner(System.in);
//存储可用教师
private List freeTeachers = new ArrayList<>();
//存储可用教室
private List freeClassrooms = new ArrayList<>();
//存储计划项
private List CourseEntries = new ArrayList<>();
//课程名及其取消时间
private Map<String, String> cancelNameAndTimes = new HashMap<>();
对于管理教师和教室,我们只需要让用户输入必要信息,然后判断创建的教师和教室是否已经存在,做出相应的操作即可。
增加计划项只需要创造对应的计划项的工厂类,由工厂类帮助我们创建即可,我判断了创建的计划项的教室是否存在,若不存在,则会自动添加到可用教室中。
结束,启动计划项,只需要判断该计划项是否存在,然后委托给计划项进行实现即可。
分配资源时,要判断资源是否存在,计划项是否存在,然后委托给计划项即可。
取消计划时,需要输入取消时间,并记录,然后委托给计划项即可。
查看当前状态,选定计划项后,判断计划项是否存在,再委托给计划项即可。
检查资源位置冲突,以及查找特定资源的前序计划项均委托给APIs来实现即可。
信息板的显示,创造Board实例,并传入待查询信息板的位置以及一组课程计划项即可。
重新设置教室时,给定课程名称,并判断是否有该课程,然后委托给计划向即可。

3.11.5学习活动应用
3.12基于语法的数据读入
修改“航班”应用以扩展该功能。
使用正则表达式读取文件信息并匹配,根据指导书上要求进行判断是否合法即可。

最后发现仅文件一有语法错误。

3.13应对面临的新变化
我选用的是航班,高铁和课程三个变化
3.13.1变化1
航班的变化:可应对变化,代价较大,将原本继承SingleLocationEntry改为MultipleLocationEntry接口即可,之后再实现一些该接口的方法。主要是Board,APP等都需要修改一遍。
修改设计:将很多方法的参数改为抽象接口PlanningEntry可能会好一点
3.13.2变化2
高铁的变化:可应对变化,代价较小,修改TrainEntry里的cancelPlan方法即可。
3.13.3变化3
课程的变化:可应对变化,代价较小,修改原本继承的SingleResourceEntry为MultipleSortedResourceEntry即可,之后再稍加修改即可。APP,API,Board都要修改但修改不多。

相关标签: java