2020年春季学期计算机学院《软件构造》课程
可复用性和可维护性的设计:PlanningEntry· 2
3.2.1 PlanningEntry的共性操作··· 2
3.2.2 局部共性特征的设计方案··· 2
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)··· 2
3.3 面向复用的设计:R· 4
3.4 面向复用的设计:Location· 5
3.5 面向复用的设计:Timeslot· 5
3.6 面向复用的设计:EntryState及State设计模式··· 6
3.7 面向应用的设计:Board· 7
3.8 Board的可视化:外部API的复用··· 8
3.9 可复用API设计及Façade设计模式··· 9
3.9.1 检测一组计划项之间是否存在位置独占冲突··· 9
3.9.2 检测一组计划项之间是否存在资源独占冲突··· 10
3.9.3 提取面向特定资源的前序计划项··· 10
3.10 设计模式应用··· 10
3.10.1
Factory Method· 11
3.10.2
Iterator 11
3.10.3
Strategy· 12
3.11 应用设计与开发··· 12
3.11.1 航班应用··· 12
3.11.2 高铁应用··· 17
3.11.3 课表应用··· 17
3.12 基于语法的数据读入··· 17
3.13 应对面临的新变化··· 18
3.13.1 变化1· 18
3.13.2 变化2· 18
3.13.3 变化3· 18
3.14 Git仓库结构··· 18
4 实验进度记录··· 19
5 实验过程中遇到的困难与解决途径··· 19
6 实验过程中收获的经验、教训、感想··· 20
6.1 实验过程中收获的经验和教训··· 20
6.2 针对以下方面的感受··· 20
1 实验目标概述
从五个具体应用(高铁车次管理、航班管理、操作系统进程管 理、大学课表管理、学习活动日程管理)中任选三个,不是直接针对五个应用分别编程 实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性) 和更容易面向各种变化(可维护性)。
编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:子类型、泛型、多态、重写、重载 、继承、代理、组合、常见的 OO 设计模式、语法驱动的编程、正则表达式、基于状态的编程、API 设计、API 复用
2 实验环境配置
Eclipse,JDK8,Git,Junit4
3 实验过程
3.1 待开发的三个应用场景
本实验中选用的三个应用场景是航班管理、高铁车次管理、大学课表管理
分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
三个场景的异同点如下:
位置:FlightEntry—2个,且不可更改;
TrainEntry—多个,且不可更改;
CourseEntry—1个,且可以更改;
资源:FlightEntry有单个资源,TrainEntry有多个有序资源,CourseEntry有单个资源
只有高铁车次可以被阻塞;它们的运行时间都可以提前确定
3.2 面向可复用性和可维护性的设计:PlanningEntry
3.2.1 PlanningEntry的共性操作
PlanningEntry 应提供的接口方法,这些方法应当是五个应用的全局共性方法,具体有:开始一个计划项Start();
取消一个计划项Cancel();
结束一个计划项Finish();
得到计划项的类型GetName();
得到计划项的编号GetID();
获取计划项的状态类GetState().
3.2.2 局部共性特征的设计方案
CommonPlanningEntry类实现了PlanningEntry接口中共性方法,实现Start(),Cancel(),Finish()方法时,使用定义的Context对象context来进行和确保状态的正确的转换,在刚开始创建计划项时private WaittingState waitting=new WaittingState(); context=new Context(waitting);作为初始状态,之后使用状态模式进行转换,在3.6中详细介绍。
在此类中定义计划项的编号和类型字符串,其在计划项刚创建时就确定不能再更改,故只构造其getter。
3.2.3 面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
本实验中我采用了方案五,从位置的数量、 位置是否可更改、资源特征(数量、个体是否可区分、多个体是否排序)、计划 项是否可阻塞及其时间描述、时间是否可预先设定几个维度进行设计考虑。
为每个维度上的不同特征值分别定义不同的接口,在接口中定义该特征值对应的操作,举例如位置的数量维度,在此维度下定义三个接口,
interface SingleLocationEntry是单个位置的接口,其中定义了setLocation()方法和getLocation()方法,适用于CourseEntry计划项,其实现类为SingleLocationEntryImpl;
interface TwoLocationEntry是两个位置的接口,定义了setLocation()方法、getEndLocation()方法、getStartLocation()方法,适用于航班计划项,其实现类为TwoLocationEntryImpl;
interface MultipleLocationEntry是多个位置的接口,定义了setLocations()方法、getLocations()方法,适用于高铁计划项,其实现类为MultipleLocationEntryImpl。
其他特征值的接口与实现类与之类似,每个维度分别定义自己的接口,针对每个维度的不同特征取值,分别实现针对该维度接口的实现类,实现特殊操作逻辑。进而通过接口组合,将各种局部共性行为复合到一起,形成满足每个应用要求的特殊接口例如下例航班计划项:
public interface
FlightPlanningEntry extends TwoLocationEntry,
TimePresetEntry,
SingleResourceEntry<R>
从而该应用的子类可以直接实现该组合接口,其继承实现了基础共性操作的CommonPlanningEntry:
public class
FlightEntry extends CommonPlanningEntry
implements
FlightPlanningEntry
在应用子类中,通过delegation到外部每个维度上的各个具体类的相应操作。
如设置其出发和抵达的位置时,定义
private TwoLocationEntryImpl loc=new
TwoLocationEntryImpl();
调用其中的setLocations()方法进行设置,loc.setLocations(Start, End);
同样在获取起点和终点时也是调用loc的方法实现。其他如时间,资源等处理方法也是一样,不再重复。
测试FlightEntry, 创建一个FlightEntry对象,然后分别构造其中的位置类对象、资源类对象、时间类对象,分别测试set方法, 可以测试其中的对象是否包含添加了的位置Location对象,时间Timelot对象,以及改变了之后的状态State
其中的setLocationsTest():构造三个位置对象哈尔滨,长春,北京,设置哈尔滨和长春作为起点和终点,测试是否设置成功; 然后将起点和终点设置成哈尔滨和北京,由于航班的位置是不可更改的所以更改会失败,检查航班的起点和终点是否保持不变
CommonPlanningEntry中的方法测试起来由于已确定的偏序关系,如只能分配资源后才能运行等,部分方法在其中没有实现,故在测试时不能进行,如Allocated状态在其中就无法达到,故在TrainEntryTest测试类中一并将其中的方法进行测试
测试TrainEntry:创建一个TrainEntry对象,然后分别构造其中的位置类对象、资源类对象、时间类对象,分别测试set方法和block方法,可以测试其中的对象是否包含添加了的位置Location对象,时间Timelot对象,以及改变了之后的状态State,由于在测试set方法时已经使用get方法,所以get方法的测试可以视为在测试set方法时一并测试了,
setLocationsTest():构造哈尔滨,长春,北京,西安,将哈尔滨,长春,北京作为火车停靠点,测试是否设置成功,然后将西安加入到停靠点中,将停靠点列表使用setLocations方法设置,由于航班的位置是不可更改的所以更改会失败,检查航班的起点和终点是否保持不变;blockTest()中,首先构造资源类,向其中加入车厢的对象,再设置其成为火车资源。在没有start()之前,测试其状态,应该是“ALLOCATED”;然后block(),测试其状态,由状态的偏序性可知应该不是“BLOCKED”,测试其状态;最后再start()之后block(),此时应该状态变为“BLOCKED”。
CourseEntryTest:创建一个TrainEntry对象,然后分别构造其中的位置类对象、资源类对象、时间类对象,分别测试set方法,可以测试其中的对象是否包含添加了的位置Location对象,时间Timelot对象,以及改变了之后的状态State
由于在测试set方法时已经使用get方法,所以get方法的测试可以视为在测试set方法时一并测试了。
setLocationTest():构造正心23,和格物201两个教室,将正心23设置成上课教室,测试是否设置成功;然后将上课教室更改成为格物201,由于课程计划项是可以更改地点的,所以修改会成功,测试结果是否修改成功。
3.3 面向复用的设计:R
在本实验中R有三个具体类,分别是Plane,Carriage,Teacher。
飞机Plane类中定义的成员变量有String ID,String Model,int SeatNum,double Age,分别表示飞机编号,飞机型号,座位数和机龄。在其中分别构造getter方法,由于其是不可变类型,其中的成员变量在初始定义后就不能再行修改,故不使用setter。
车厢Carriage类中定义的成员变量有:String ID,String Type,int Number,int Year,分别表示车厢编号、车厢类型、厢内座位数、出厂年份。在其中设置getter方法。
教师Teacher类中定义的成员变量有:String ID,String Name,String Gender,String Title ,分别表示教师的身份证号,姓名,性别,职称。在其中设置getter方法。
3.4 面向复用的设计:Location
Location类中主要使用位置的名字作为标签,与其他位置区分开,定义String name,boolean isShare,String
lo,String la,分别表示位置的名字,是否可以被共享,纬度,经度。在其中分别设置getter方法,编写equals方法。
在singleLocationEntryImple类中,单独定义一个Location即可。
在TwoLocationEntryImple类中,定义两个Location对象表示起点和终点即可。
在MultipleLocationEntryImple类中,定义一个List在存放起始站、经停站和终点站。
3.5 面向复用的设计:Timeslot
在本实验设计中,首先定义一个Time类,在其中定义int year,int month,int day,int
hour,int minute 分别表示年月日时分,分别编写isAfter(Time t),isBefore(Time
t),equals(Time t)方法,判断两个Time对象的时间前后关系。
在Timelot类中定义private List
在FlightEntry和CourceEntry中,时间列表中一共有两个元素,timelot.get(0)是起飞和课程开始时间,timelot.get(1)是到达终点和下课时间;
在TrainEntry中,将索引值除以2,就可以得到timelot.get(0)和(1)是第一个时间对,由于第一个位置的到达时间和离开时间应该相等,所以(0)(1)两个时间相同。get(2)和(3)是第二个时间对,get(4),get(5)是第三个时间对;索引值可以被2整除的时间是第i/2个位置的到达时间,模2余1的索引值时间是从第i/2个位置出发的时间。
3.6 面向复用的设计:EntryState及State设计模式
使用状态模式,定义State接口,在其中定义两个方法:
public
State Move(String s,String EntryName);
public String
getStateName();
Move方法传入即将转到的状态与计划项的类型,对应目前状态与计划项类型有着确定的操作规则,返回下一个状态的状态类对象。getStateName方法返回该类的名称。
State接口其下有WaittingState、AllocatedState、RunningState、CancelledState、EndedState、BlockedState六个实现类。
以RunningStatelei为例:定义该类的状态字符串为private final String
state=“RUNNING”; 其构造函数为空,其状态不能因为计划项的类型而影响。在状态转换Move时,通过匹配计划项的类型名,来判断下一状态中是否有BLOCKED状态。
public State Move(String
s, String EntryName) {
if(EntryName.toLowerCase().equals(“flight”)||EntryName.toLowerCase().equals(“course”))
{
switch (s){
case "RUNNING"{
return new RunningState();
case "ENDED":
return new EndedState();
default:
System.out.println("该操作不合法!");
}
}
if(EntryName.toLowerCase().equals("train"))
{
switch (s){
case "RUNNING":
return new RunningState();
case "ENDED":
return new EndedState();
case "BLOCKED":
return new BlockedState();
default:
System.out.println("该操作不合法!");
}
}
定义Context类,在该类中定义State类的对象state,代表目前计划项的状态,其中Move方法委托给当前类的state.Move()方法;还有一个get方法返回当前状态类对象。
在CommonPlanningEntry中,最开始定义一个状态类private WaittingState waitting=new
WaittingState();在构造计划项时,使用context=new Context(waitting);语句来将计划项的状态初始化为WaittingState,如要启动一个航班计划项,Start()中的语句便是context.Move(“RUNNING”,
this.Name);this.Name是定义的类型名”flight”,此操作会让context对象中的state成为RunningState对象。
3.7 面向应用的设计:Board
定义三个数据结构,以航班FlightEntry举例
private
List<FlightEntry> EntryList=new LinkedList<>();
private
Map<FlightEntry,String> ArrivingMap=new HashMap<>();
private
Map<FlightEntry,String> LeavingMap=new HashMap<>();
EntryList保存所有计划项,ArrivingMap保存即将或已经到达该机场的所有航班和其状态,LeavingMap保存即将或者离开机场的所有航班及其状态;
在其中定义MakeMap()方法,在其中遍历所有航班,这里只举例到达航班的例子,起飞航班与其类似。每遍历到一个航班计划项,判断该航班的终点是否是输入的机场位置,假如是的话继续判断该航班到达终点时的时间是否与输入的现在的时间相差一个小时,具体来判断此条件的话就是判断年、月是否相同,如果日相同,则用目前的时间减去预期的抵达时间,若结果大于-60且小于0且状态不为“CANCELLED”,则ArrivingMap.put(entry,“即将降落”);若状态是“CANCELLED”,则ArrivingMap.put(entry,“已取消”);若结果小于60,且大于0且状态不为“CANCELLED”,ArrivingMap.put(entry,
“已降落”);
当目前的day与航班预期到达的day不同时且天数之差的绝对值等于1时,继续判断,
(time.getHour()+24-entry.getTimelot().get(1).getHour())*60+time.getMinute()-entry.getTimelot().get(1).getMinute()的值,与上述结果一样处理。
在TrainEntry中类似只是由于其时间并没有只是两个,所以在最开始判断位置是否在位置列表locs中时,记录其在列表中的位置index,在之后判断是否时间之差小于60或大于-60时,使用index*2(+1)来从时间列表中得到相应的到达该位置和离开该位置的时间,根据此时间进行操作后判断。
在TrainEntry中还需要定义两个List
private List
private List
由于高铁不像航班和课程一样只有一个开始时间和结束时间,所以需要记录其时间,以便在信息板展示时可以知道map中的键值对对应的时间,在向Map中添加键值对的时候,还需要记录其出发或者到达时间。
ArrivingMap.put(entry,“已抵达”);
ArrivingTimeList.add(entry.getTimelot().get(index*2));
或者是LeavingTimeList.add(entry.getTimelot().get(index*2+1));
LeavingMap.put(entry,"已出发");
CourseBoard与FlightBoard类似,不再赘述。
3.8 Board的可视化:外部API的复用
在此部分,我直接使用了eclipse中的System.out.println();来模拟表格的输出,读取ArrivingMap获得即将到达或者以抵达的航班列表及其状态,读取LeavingMap获得即将起飞或者已起飞的航班列表及状态。
以下为效果图:
3.9 可复用API设计及Façade设计模式
3.9.1 检测一组计划项之间是否存在位置独占冲突
为了便于该部分的操作,可以从CommonPlanningEntry中得到资源和位置,以及时间,在CommonPlanningEntry中添加一些getter和成员变量。
Map<String,List<CoursePlanningEntry>>
map=new HashMap<>();该散列表存放位置名称和使用该位置的所有计划项的列表,遍历所有计划项,查看其位置,假如该位置还未加入到map中,则在一列表中添加当前所在计划项,再将该位置和该列表存入Map中,List<CoursePlanningEntry>
l=new ArrayList<CoursePlanningEntry>();
l.add(entry);
map.put(entry.getLocation().getName(),l);
如果该位置已存在在map中,那么取出该位置对应的计划项列表,在其中加入当前计划项,然后在将其存入map中:
if(map.keySet().contains(entry.getLocation().getName()))
{
List<CoursePlanningEntry>
list=new ArrayList<>(map.get(entry.getLocation().getName()));
list.add(entry);
map.remove(entry.getLocation().getName());
map.put(entry.getLocation().getName(),list);
}
之后遍历map的键列表,也就是所有的位置。在每一个位置的遍历中,再加两重循环,比较该位置对应的计划项列表中任意两个计划项,判断是否某一个计划项的开始时间大于另一个计划项的开始时间且小于结束时间,如果判断成功,则说明的确存在位置冲突。
for(String
loc:map.keySet()) {
for(CoursePlanningEntry entry1:map.get(loc))
for(CoursePlanningEntry entry2:map.get(loc)) { if(entry1.getTimelot().get(0).isAfter(entry2.getTimelot().get(0))&&entry1.getTimelot().get(0).isBefore(entry2.getTimelot().get(1)))
return true;
}
}
3.9.2 检测一组计划项之间是否存在资源独占冲突
检查资源冲突与检查位置冲突类似。
3.9.3 提取面向特定资源的前序计划项
查找前序计划项时,首先定义List<CommonPlanningEntry>
list=new ArrayList<>();先遍历传入的所有计划项,如果计划项的资源与传入的资源相同,则将该计划项加入到列表中,使用选择排序将列表中的计划项进行排序,排序完成后,遍历该列表,如果第i个计划项是传入的计划项,那么第i-1个计划项是要返回的计划项。遍历完成后如果还没有查到,就返回null;
3.10 设计模式应用
3.10.1 Factory Method
实验要求中要求尽可能避免通过 new 的方式实例化具体的
PlanningEntry 子类型,故在三个子类中加入工厂方法,
public
static FlightEntry newPlanningEntry(String
ID1,Location startLoc,Location endLoc,Time startTime,Time endTime) {
该工厂方法是静态方法,在生产该类对象时可以直接调用FlightEntry类,向该工程方法中传入航班编号、起始位置、终点位置、开始时间、结束时间,在该工厂方法中定义所需的特征值接口实现类的对象,返回该类的对象。
TrainEntry和CourseEntry中的工厂方法与其类似。
3.10.2 Iterator
在FlightBoard中实现Comparator,
public class
FlightBoard implements Comparator<FlightEntry>
在该类中定义Comparator<FlightEntry>
comparator=new Comparator<FlightEntry>() {
@Override
public int compare(FlightEntry<Plane> o1, FlightEntry<Plane> o2) {
// TODO Auto-generated method stub
if(o1.getTimelot().get(0).isAfter(o2.getTimelot().get(0)))
return 1;
if(o1.getTimelot().get(0).isBefore(o2.getTimelot().get(0)))
return -1;
return 0;
}
};
将计划项列表进行排序,使用Iterator模式
Collections.sort(EntryList,comparator);
Iterator<FlightEntry>
itr=EntryList.iterator();
while(itr.hasNext())
{
修改Comparator中的方法,调换1和-1:
if(o1.getTimelot().get(0).isAfter(o2.getTimelot().get(0)))
return -1;
if(o1.getTimelot().get(0).isBefore(o2.getTimelot().get(0)))
return 1;
return 0;
修改前课表如下:
修改后课表如下:
3.10.3 Strategy
新建checkLocationConflict1和checkLocationConflict2类,调用时创建对应类即可;
Boolean flag=(new checkLocationConflict1()).checkLocationConflict(List
entries)
3.11 应用设计与开发
3.11.1 航班应用
在类里定义三个列表,存放APP所需的信息与计划项
List
locs=new ArrayList<>();//位置集合
List
planes=new ArrayList<>();//飞机资源集合
List<FlightEntry>
entries=new ArrayList<>();//航班集合
在FlightScheduleAPP类中定义Menu类,输出操作菜单:
介绍操作1的实现:输出选择信息,由用户选择是增加一条飞机资源还是删除一条资源
System.out.println(“输入操作序号:\n1.增加一条可用的飞机资源:\n2.删除一条可用的飞机资源:”);
定义Boolean flag,如果flag=1,则提示用户输入飞机编号、机型号、座位数、机龄,创建一个飞机对象,将其加入到APP的资源列表planes中;如果flag=2,就输出现有的飞机信息,提示用户输入想要删除飞机的编号,再根据编号遍历资源列表删除对应飞机。
操作2的实现与操作1类似。
操作3是创建一条新的计划项,输出信息提示用户输入要创建的航班号,输入后输出现有的机场,提示用户输入出发和抵达的机场,再要求输入出发和抵达的时间,创建两个时间Time对象,再将这两个时间对象、位置对象和航班号作为参数传入到FlightEntry中的工厂方法,将返回的计划项对象加入到计划项列表。
FlightEntry
entry=FlightEntry.newPlanningEntry(ID, start, end, startTime, endTime);
entries.add(entry);
操作4:通过输入要删除的航班号和起飞日期来确定唯一一次航班,遍历所有航班,将对应航班的对象执行Cancel()方法
通过操作8来查询此次航班的状态,预期将会是CANCELLED
操作5的实现:为一个计划项设置资源,首先通过匹配状态“ALLOCATED”输出所有还未被匹配资源的计划项,然后提示输入航班号、航班日期,输出所有的飞机资源,然后提示输入飞机编号,通过两重循环遍历所有的航班和飞机,将符合编号的飞机分配给选定的航班。
通过操作8来查询此航班状态,应该为“ALLOCATED”
操作6的实现,与之前的操作一样,由于在航班计划项中没有阻塞状态,故RUNNING状态只能从ALLOCATION转换过来,所以通过匹配状态“ALLOCATED”来输出所有被分配了资源却还没有被启动的计划项。用户输入航班号和起飞日期来匹配唯一确定的航班,将其启动。
通过操作8来查询该航班的状态:
操作7的实现:输入航班号与起飞日期,匹配后执行Finish()方法
执行操作8来查看该计划项状态:
操作8的实现:输出所有航班信息后,匹配输入的航班号和起飞日期,通过getStata()方法得到其状态
之前的几个操作已经验证其结果
操作9的实现:检查是否存在资源冲突:创建PlanningEntryAPIs类对象,调用api.checkResourceExclusiveConflict(entries);读取了给出的文本2检查其不存在资源冲突。
操作10的实现:输入飞机编号后,遍历所有航班,输出匹配到的航班,在从这些航班中选择一个编号输入,调用PlanningEntryAPIs中的findPreEntryPerResource方法
发现其前序计划项为空
操作11的实现:通过输入位置与时间,将所有计划项遍历一遍,假如该航班的开始时间小于输入的时间,那么启动该计划项,假如该航班的结束时间小于输入的时间,那么就结束该计划项。然后调用FlightBoard中的visualize()方法。
、
操作13:调用System.exit(0)来退出程序。
3.11.2 高铁应用
与航班类似,唯一注意的是在创建计划项的时候需要输入多个位置,记录位置的个数n,在设置出发和经停及到达时间的时候,循环n次,在第一次循环和最后一次循环只需要输入一个时间,代表出发和到达时间,之间的循环每次输入两个时间,分别代表到达和离开第i个位置的时间。其余地方的时间调用使用个性化的子类中的方法即可得到时间。求起始位置和终点位置就调用locs.get(0)和locs.get(locs.size())
3.11.3 课表应用
与航班应用类似,不再赘述。
3.12 基于语法的数据读入
读取文本文件
FileReader
reader = new FileReader(“src\App\FlightSchedule_2.txt”);
BufferedReader F = new
BufferedReader(reader);。
然后循环读取,每次读取一行,将每行连接起来,故在正则表达式匹配是不用考虑换行符了,每读取13行就处理一次:
String
line;
String
str="";
for(int
i=1;(line = F.readLine()) != null;i++) {
str=str.concat(line);
if(i%13==0)
{}
使用懒惰模式正则匹配Pattern pattern = Pattern.compile(“Flight:(.+?),(.+?)\{DepartureAirport:(.+?)ArrivalAirport:(.+?)DepatureTime:(.+?)ArrivalTime:(.+?)Plane:(.+?)\{Type:(.+?)Seats:(.+?)Age:(.+?)\}\}”);
如果不能匹配说明文件格式错误,则返回信息
以第一个匹配到的字符串为例
String
EntryDate = matcher.group(1);
Pattern
p1=Pattern.compile("(.+?)-(.+?)-(.+?)");
Matcher
matcher1=p1.matcher(EntryDate);
对其再进行一次匹配,得到相应的信息:
int
year=Integer.valueOf(matcher1.group(1));
int
month=Integer.valueOf(matcher1.group(2));
int
day=Integer.valueOf(matcher1.group(3));
在对此计划项的十个信息匹配读取完之后,调用工厂方法生成计划项,调用setResource()分配资源。
3.13 应对面临的新变化
3.13.1 变化1
之前的设计不能应对变化,所以改变FlightEntry的实现的接口TwoLocationEntry为MultipleLocationEntry,改编FlightEntry其中的方法,重载setLocations()方法,仅传入三个位置参数,在设置位置时判断第二个参数是否为空,如果为空就只将第一个参数和第三个参数加入到位置列表,将该位置列表设置;修改位置的getter,通过位置列表得到起点经停点和终点。
3.13.2 变化2
高铁在配置了车厢资源后就不能再取消了。只有装配了车厢资源之后才能启动、阻塞、结束,由状态转换图可知所有状态中只有WAITTING状态才能被取消因此修改Cancel()方法
if(context.getState().getStateName().equals(“WAITTING”))
context.Move(“CANCELLED”,
this.Name);
修改代价非常小
3.13.3 变化3
需要多名教师,且区分次序,故CourseEntry类实现接口SingleResourceEntry改变为MultipleSortedResourceEntry,
或是修改原来SingleResourceEntry为SingleResourceEntry<List,修改对应的返回值和函数参数,此处采用第二种方法。修改代价也较小。
三个变化实现修改完之后程序出现了些许的bug。
3.14 Git仓库结构
析输入文件并据此构造对象。你对语法驱动编程有何感受?
(6) Lab1和Lab2的大部分工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验是你完全从0开始进行ADT的设计并用OOP实现,经过五周之后,你感觉“设计ADT”的难度主要体现在哪些地方?你是如何克服的?
(7) “抽象”是计算机科学的核心概念之一,也是ADT和OOP的精髓所在。本实验的五个应用既不能完全抽象为同一个ADT,也不是完全个性化,如何利用“接口、抽象类、类”三层体系以及接口的组合、类的继承、设计模式等技术完成最大程度的抽象和复用,你有什么经验教训?
(8) 关于本实验的工作量、难度、deadline。
(9) 到目前为止你对《软件构造》课程的评价。
(1) 面向ADT的编程编写时间和抽象度高于直接面对场景的编程,在本实验中Time、Location等ADT就可以复用多次,大大减小了之后的编程时间消耗。
(2) 为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些操作保证了软件的基础正确性,在出现错误时可以更快地找到问题所在,之后复用时可以知道每个成员变量的意义与不变性,以及保证信息的安全,,防止泄露。
(3) 开发给别人使用的API,在开发时又要考虑各种情况,设计要有一定的普适性,设计时比直接面向自己的场景编程要复杂,但是能被别人复用便是值得的,自己在编码时调用解决了问题也是有很大的快乐。
(4) 在编程中使用设计模式,增加了很多类,但是其复用度增加了,且变得更加灵活一些,许多场景中可以使用这些代码,减少了以后的编程的劳动。这种设计模式还是比较有益的。
(5) 语法驱动编程在学习和练习时有一定的难度,但是在设置好了表达式之后处理文件就有非常大的便利。
(6) 设计ADT”的难度主要在最开始的时候对于代码的结构比较模糊,需要对要设计的场景进行抽象,分析逻辑结构和调用关系。需要经常修改,在实验的前期经常会漏掉一些方法在类中,之后在解决其他问题时补充。
(7) 共性的方法放在接口和抽象类中,具体类实现接口或者是继承父类,进行更加个性化的操作。
(8) 本实验的工作量还是比较大,难度在开始理解了实验的设计方案后就难度一般,deadline差不多合适
(9) 软件构造课程的设计模式的模块实用度很高,对于设计的思想也有较大帮助。