Lab3基本计划项设计
面向可复用性和可维护性的设计:PlanningEntry
为了提高软件构造的可复用性和可维护性,需为其设计和构造一套统一的 ADT
。以下内容描述的就是如何设计一套统一的ADT
。3个计划项都包括了时间对的设计,因此在实现过程中首先应该进行对时间的设计。同时还需要有位置信息,因此设计计划项时还需要补充位置信息,对位置信息进行设定。同时资源的设计也需要在这里进行,不过不同的计划项可能使用的不是相同的资源。计划项的名字可以作为公共的属性添加到这个类的设计中。首先设计一个接口 PlanningEntry<R>
用于刻画经过抽象的、可复用的“计划项”,其中R
代表“计划项”中涉及的资源类型,将其共性的、可复用的操作抽象出来放入接口。
PlanningEntry的共性操作
在这个共性的操作中,我们将PlanningEntry<R>
设计成了一个顶层的接口,在其中我们保留了所有计划项之间的共性操作。他们是取消、完成、启动3个基本的行为项。这个抽象接口的设计如下:
/**
* 将计划项状态设置为取消
* @return 成功设置返回true,否则返回false
*/
public boolean Cancelled();
/**
* 将计划项状态设置为完成
* @return 成功设置返回true,否则返回false
*/
public boolean Ended();
/**
* 将计划项状态设置为启动
* @return 成功设置返回true,否则返回false
*/
public boolean Running();
局部共性特征的设计方案
在定义了基本的操作之后我们需要在具体的类中对这个接口中的方法进行实现。因此我们设计一个类CommonPlanningEntry<R>
来实现PlanningEntry<R>
。这个类首先需要实现接口中定义的基本方法,同时还需要增加两个属性,一个属性用来保存计划项的名字,一个属性用来保存计划项的状态。这个类的属性如下:
protected EntryState state;
private final String EntryName;
同时它的表示不变性和防止内存泄露方法如下:
// Abstraction function:
// 以保存的state、EntryName信息
// 来维护一个计划项的管理
// Representation invariant:
//
// Safety from rep exposure:
// 所有属性都定义成private保护属性不会直接泄露
// 对于返回可变类型的对象,使用防御式拷贝的方式进行保护,避免表示泄露
对于这个类中具体方法的实现,例如状态的改变等,都委托给状态变量进行完成。其中具体的实现为:
@Override
public boolean Cancelled() {
return this.state.Cancelled();
}
@Override
public boolean Ended() {
return this.state.Ended();
}
@Override
public boolean Running() {
return this.state.Running();
}
最后还需要得到计划项的名字,这个函数的设计就是简单的get
函数即可。
面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
如果各应用在某个维度上的特征值完全相同,则可以将针对处理该共性特征 值的操作在 PlanningEntry<R>
和CommonPlanningEntry<R>
中定义和实现,如果在某个维度上的特征取值完全不同,就必须在各个应用的具体子类中分别实现其对不同特征取值的个性化操作。但是选定的3个具体应用之间的特征并不是完全相同,也不是完全不同,此处可以参见设计的表格,如下:
因此我们需要设计一个思路使得程序能够正常运行并且同时能够支持这些差异性的设计,我们在实验报告提供的方案中选择方案5,即CRP
,通过接口组合实现局部共性特征的复用。要理解方案5,首先我们通过方案3,即为不同特征取值分别定义接口并在子类中实现其特殊操作。这个方案为每个维度上的不同特征取值分别定义不同的接口,在接口中定义特殊的操作,各应用的具体子类根据自己的需求来实现不同特征的接口。这个设计思路需要在多个子类的设计中不断进行Override
。方案4定义接口并实现具体类,通过继承树实现各应用在多维度上的不同特征取值的组合。这个方案是指在方案3基础上,除了为每个维度上的每个特征取值定义相应的接口,另外为每个接口分别构造一个实现类,该类一方面继承CommonPlanningEntry
中的全局共性操作,另一方面在该类中完成对局部共性操作的代码。这个方案可能会出现组合爆炸的情况。
而我们所选择的方案5,针对方案4,通过delegation
机制进行改造。每个维度分别定义自己的接口,针对每个维度的不同特征取值,分别实现针对该维度接口的不同实现类,实现其特殊操作逻辑。进而,通过接口组合,将各种局部共性行为复合在一起,形成满足每个应用要求的特殊接口,从而该应用子类可直接实现该组合接口。在应用子类内,不是直接实现每个特殊操作,而是通过delegation
到外部每个维度上的各具体实现类的相应特殊操作逻辑。
飞机计划项
FlightEntry
:代表一个具有时间表、有起飞和降落机场、由一架飞机执飞的航班。其中这个类的设计中,我们需要先设置一个接口,这个接口拓展了TwoLocationEntry
,SingleResourceEntry
,SingleTimeslotEntry
接口,这些接口的含义在接下来的设计中会提及,同时需要完成具体的实现类。
这个实现类的属性和表示不变性,防止泄露的方法为:
private TwoLocationEntryImpl tle;
private SingleResourceEntryImpl<FlightR> sre;
private SingleTimeslotEntryImpl ste;
// Abstraction function:
// 以保存的两个位置、单个资源、单个时间对
// 来维护一个飞机计划项
// Representation invariant:
//
// Safety from rep exposure:
// 所有属性都定义成private保护属性不会直接泄露
// 对于返回可变类型的对象,使用防御式拷贝的方式进行保护,避免表示泄露
从这个定义我们可以知道,我们在飞机项的具体操作中都是委托给具体的属性子类进行完成的,因此这个类中方法的实现基本都是利用委托进行实现的。这里举个例子来看,以分配资源方法来举例,如下所示:
/**
* 为计划项分配资源
* @param resource 所需资源
* @return 返回true
*/
public boolean addResource(FlightR resource) {
this.sre.addResource(resource);
super.state.Allocated();
return true;
}
从这个例子我们容易看出这个具体的实现是委托给了属性来完成的。在飞机项中除了一些设置方法之外,还添加了一些get
方法,主要是为了实现后续的功能而添加的,这里以得到计划项信息举例,其他方法类似进行处理,这里不做详细描述。计划项的信息的详细返回如下:
/**
* 得到计划项得到信息
* @return 返回字符串列表
*/
public List<String> getInf(){
List<String> inf = new ArrayList<>();
StringBuffer sBuffer = new StringBuffer();
StringBuffer sBuffer2 = new StringBuffer();
Calendar cal = ste.getTime().getStart();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
sBuffer.append(hour).append(":").append(minute);
Calendar cal2 = ste.getTime().getEnd();
int hour2 = cal.get(Calendar.HOUR_OF_DAY);
int minute2 = cal.get(Calendar.MINUTE);
sBuffer2.append(hour2).append(":").append(minute2);
inf.add(sBuffer.toString());
inf.add(super.getName());
inf.add(tle.getStart().getLoc() + "-" + tle.getEnd().getLoc());
inf.add(super.state.getState().getStatestring());
inf.add(sBuffer2.toString());
return inf;
}
火车计划项
TrainEntry
:代表一个具有时间表、经过一组站点、由一组车厢构成的高铁车次。这个类的设计思路与FlightEntry
的相似,先设置接口。这里接口拓展了MultipleLocationEntry
,BlockableEntry
,MultipleSortedResourceEntry
,MultipleTimeslotEntry
的接口,通过将具体的应用委托给这些具体属性完成。这个类的实现的属性和对应的表示不变性如下:
private MultipleLocationEntryImpl mle;
private BlockableEntryImpl be;
private MultipleSortedResourceEntryImpl<TrainR> msre;
private MultipleTimeslotEntryImpl mte;
// Abstraction function:
// 以保存的多个位置、可阻塞、多个资源、多个时间对
// 来维护一个火车计划项
// Representation invariant:
//
// Safety from rep exposure:
// 所有属性都定义成private保护属性不会直接泄露
// 对于返回可变类型的对象,使用防御式拷贝的方式进行保护,避免表示泄露
同样这里的具体实现都是委托给这些属性进行完成的,以分配资源方法为例:
/**
* 为计划项分配资源
* @param resource 所需资源
* @return 返回true
*/
public boolean addResource(List<TrainR> resource) {
for(TrainR re : resource) {
this.msre.addResource(re);
}
super.state.Allocated();
return true;
}
为了完成一些类的设计,我们还增加了一些get
方法,如下:
/**
* 得到计划项得到信息
* @param i 选择的元素个数
* @return 返回字符串列表
*/
public List<String> getInf(int i){
List<String> inf = new ArrayList<>();
StringBuffer sBuffer = new StringBuffer();
StringBuffer sBuffer2 = new StringBuffer();
Calendar cal = mte.getTime().get(i).getStart();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
sBuffer.append(hour).append(":").append(minute);
Calendar cal2 = mte.getTime().get(i).getEnd();
int hour2 = cal2.get(Calendar.HOUR_OF_DAY);
int minute2 = cal2.get(Calendar.MINUTE);
sBuffer2.append(hour2).append(":").append(minute2);
inf.add(sBuffer.toString());
inf.add(super.getName());
inf.add(mle.getLocation().get(0).getLoc() + "-" + mle.getLocation().get(mle.getLocation().size()-1).getLoc());
inf.add(super.state.getState().getStatestring());
inf.add(sBuffer2.toString());
return inf;
}
其他get方法的设计与之类似。
课程计划项
CourseEntry
:代表一次课,具有明确的日期/时间、地点、教师。为了实现这个类,同样我们先进行辅助接口的设计,在这个设计中,我们拓展了以下接口SingleLocationEntry
,SingleResourceEntry<CourseR>
,SingleTimeslotEntry
,在具体的实现类中,我们设计的属性和对应的表示不变性如下:
private SingleLocationEntryImpl sle;
private SingleResourceEntryImpl<CourseR> sre;
private SingleTimeslotEntryImpl ste;
// Abstraction function:
// 以保存的单个位置、单个资源、单个时间对
// 来维护一个课程计划项
// Representation invariant:
//
// Safety from rep exposure:
// 所有属性都定义成private保护属性不会直接泄露
// 对于返回可变类型的对象,使用防御式拷贝的方式进行保护,避免表示泄露
这个计划项的设计同样是将具体的实现委托给了属性进行完成,以分配资源方法为例子,如下:
/**
* 为计划项分配资源
* @param resource 所需资源
* @return 返回true
*/
public boolean addResource(CourseR resource) {
this.sre.addResource(resource);
super.state.Allocated();
return true;
}
这个计划项设计中的get
方法与上述两个类的设计相似,举一个得到计划项信息的例子,如下:
/**
* 得到计划项得到信息
* @return 返回字符串列表
*/
public List<String> getInf(){
List<String> inf = new ArrayList<>();
StringBuffer sBuffer = new StringBuffer();
Calendar cal = ste.getTime().getStart();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
Calendar cal2 = ste.getTime().getEnd();
int hour2 = cal2.get(Calendar.HOUR_OF_DAY);
int minute2 = cal2.get(Calendar.MINUTE);
sBuffer.append(hour).append(":").append(minute).append("-").append(hour2).append(":").append(minute2);
inf.add(sBuffer.toString());
inf.add(super.getName());
inf.add(sre.getResource().getName());
inf.add(super.state.getState().getStatestring());
return inf;
}
这个类中其他的get
方法与之类似。