Lab3 Report

目录

  • 1实验目标概述
  • 2实验环境配置
  • 3实验过程
    • 3.1待开发的三个应用场景
    • 3.2面向可复用性和可维护性的设计:PlanningEntry
      • 3.2.1局部共性特征的设计方案
      • 3.2.2面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)
    • 3.3面向复用的设计:R
    • 3.4面向复用的设计:Location
    • 3.5面向复用的设计:Timeslot
    • 3.6面向复用的设计:EntryState及State设计模式
    • 3.7面向应用的设计:Board
    • 3.8Board的可视化:外部API的复用
    • 3.9PlanningEntryCollection设计
    • 3.10可复用API设计及Façade设计模式
      • 3.10.1检测一组计划项之间是否存在位置独占冲突
      • 3.10.2检测一组计划项之间是否存在资源独占冲突
      • 3.10.3提取面向特定资源的前序计划项
    • 3.11设计模式应用
      • 3.11.1Factory Method
      • 3.11.2Iterator
      • 3.11.3Strategy
    • 3.12应用设计与开发
      • 3.12.1航班应用
      • 3.12.2高铁应用
      • 3.12.3课表应用
    • 3.13基于语法的数据读入
    • 3.14应对面临的新变化
      • 3.14.1变化1 航班
      • 3.14.2变化2 高铁
      • 3.14.3变化3 课程
    • 3.15Git仓库结构

1实验目标概述

本次实验覆盖课程第 3、4、5 章的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
 子类型、泛型、多态、重写、重载
 继承、代理、组合
 常见的 OO 设计模式
 语法驱动的编程、正则表达式
 基于状态的编程
 API 设计、API 复用
本次实验给定了五个具体应用(高铁车次管理、航班管理、操作系统进程管理、大学课表管理、学习活动日程管理),学生不是直接针对五个应用分别编程
实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)

2实验环境配置

3实验过程

3.1待开发的三个应用场景

我选择的三个应用场景:
①航班管理
②高铁车次管理
③大学课表管理
这三个场景的异同点:
①位置的数量:分别为1个、2个和多个
②仅有大学课表的位置可更改
③航班和大学课表为单个资源,高铁为有序多个资源
④仅有高铁车次可阻塞
⑤时间均在创建时设定

3.2面向可复用性和可维护性的设计:PlanningEntry

计划项是一个状态可变的ADT,它保存有一个计划项的时间、地点、资源等有效信息。PlanningEntry在我的设计中是一个接口,设计有各种计划项均要实现的方法以及工厂方法。
PlanningEntry的共性操作接口:
①3个工厂方法分别能够返回指定类型的PlanningEntry实现类,以Flight Schedule为例:

public static<Resource> FlightSchedule<Resource> newFlightEntry(LocalDate planningDate,Location location,Timeslot timeslot,String planningEntryNumber){
		return new FlightSchedule<Resource>(planningDate,location,timeslot,planningEntryNumber);}

②状态的转换,以目标状态进行分类,能够将状态转换为RUNNING、BLOCKED、CANCELLED、ENDED四种之一(其中转换为ALLOCATED是个性化设计),分别用4个方法来实现。以run()为例:

**
 * 将计划项状态转化为RUNNING已启动
 * @return 当状态转化成果返回true
*/
public boolean run();

③Getter()包括了获取Location、Timeslot、State、Type、Number、Type一些共有的信息对象。

3.2.1局部共性特征的设计方案

CommonPlanningEntry类实现了PlanningEntry接口中共性方法,包括了状态转换和Getter方法。
①状态转换,以run()为例:将状态转换委派给state对象的Setter操作,通过常量来进行目标状态的区分。在state对象中,首先判断该转换是否合法(访问EntryStateEnum静态常量进行判断),然后在进行状态覆盖,最后返回操作成功与否的标识:

@Override
public boolean run() {
	// TODO Auto-generated method stub
	return this.state.setNewState(strPlanningEntryType,"Running");
}

②Getter操作访问CommonPlanningEntry中定义的共性成员变量,包括Location、Resource等等。以getLocation()为例:

@Override
public Location getLocation() {
	// TODO Auto-generated method stub
	return this.location;
}

③此外,设计抽象方法getPlanningDate()等Spec相同的方法。

public abstract LocalDate getPlanningDate();

3.2.2面向各应用的PlanningEntry子类型设计(个性化特征的设计方案)

①3个子类型的不同主要在于两方面:Location、Timeslot、Resource等信息的存储模式和信息的修改。
②通过不同子类型的不同Getter来得到相应的信息细节。
以Flight Schedule为例,如下方法获得了起飞、降落机场:

/**
 * 获取航班起点站
 * @return 起点站名
 */
public String getOriginLocation() {
	return super.getLocation().getLocationList().get(0);
}
/**
 * 获取航班终点站
 * @return 终点站名
 */
public String getTerminusLocation() {
	int n=super.getLocation().getLocationList().size()-1;
	return super.getLocation().getLocationList().get(n);
}

而在Class Schedule中如下获得班级地点:

/**
 * 获取上课地点
 * @return
 */
public String getClassroomLocation() {
	return this.getLocation().getLocationList().get(IndexOfLocation);
}

③信息修改:根据不同计划项信息的修改特点进行设计。例如allocateResource(),飞机只能分配一个资源,而高铁为多个有序资源(用List< R >存储),活动为多个无序资源。以高铁的分配资源为例:

public boolean allocateResource(Car... resources) {
	this.resources.addAll(Arrays.asList(resources));
	this.ORIGIN=0;
	this.TERMINUS=this.location.getLocationList().size()-1;
	return this.state.setNewState(strPlanningEntryType, "ALLOCATED");
}

通过不定项的资源作为参数,然后保存到list中,获取长度、起终点标识,并设置状态。
④ClassSchedule可以在开始前设置新地点,则再该计划项子类中添加对应的Setter方法。

	/**
	 * 在分配教师资源但并未上课的计划项中更改上课地址
	 * @param strNewLocation
	 * @return 成功更改返回true,否则false
	 */
	public boolean setNewLocation(String strNewLocation) {
		if(this.getState().getStateString().equals("ALLOCATED")) {
			this.location=new Location(strNewLocation);
			return true;
		}
		return false;
	}

⑤最后写一些判断相同的方法
总体框架如下:
Lab3 Report_第1张图片
Lab3 Report_第2张图片
Lab3 Report_第3张图片

相关JUnit测试已全部通过

3.3面向复用的设计:R

资源有多种,因为Resource被设计为一个接口,有3个实现类:Plane、Train和Teacher。Resource接口中有3种子类的工厂方法。
Lab3 Report_第4张图片
三种子类存储各自特有的信息
Lab3 Report_第5张图片
Lab3 Report_第6张图片
Lab3 Report_第7张图片

3种子类均为immutable,设计有各个成员变量的Getter,并且根据要求重写equals等。以Plane为例:

	/**
	 * 将飞机信息形成形成<编号,型号(座位数,机龄)>格式
	 */
	@Override
	public String toString() {
		String s=new String();
		s="<"+this.number+","+this.type+"("+this.seatNum+","+this.age+")>";
		return s;
	}
	/**
	 * 判断当前飞机和指定飞机是否相同
	 * @param that
	 * @return 相同返回true,否则false
	 */
	public boolean equals(Plane that) {
		if(this.toString().equals(that.toString())) {
			return true;
		}
		else return false;
	}

相关JUnit测试已全部通过

3.4面向复用的设计:Location

由于选择的3种计划项位置数量各不相同,因此采用一个List来存储若干的位置,通过PlanningEntry的不同Getter来获取。
Lab3 Report_第8张图片
相关JUnit测试已全部通过

3.5面向复用的设计:Timeslot

Time Slot和Location是共同设计的,存储有两个List,分别代表对应的位置的到达和离开时间。由此设计,可以精确到每个地点的到达和离开时间,若为第一个地点,则到达和离开时间相同;若为最后一个地点也如此;若只有一个地点,则也如此。
由此,3种不同的计划项,通过不同的Getter实现不同的特征。

	/*
	 * AF:
	 * arrival[i]表示到达locations[i]的时间,leaving同理
	 * 对于航班和高铁来说,起点和终点的到达和离开时间相同,即arrival[0]==leaving[0],arrival[n]==leaving[n]
	 * 航班:size=2,leaving[0]为起点起飞时间,arrival[1]为终点降落时间,为了便于表达,arrival[0]=leaving[0],arrival[1]=leaving[1]
	 * 高铁:size>=2
	 * 课程:size=1,arrival[0]为上课时间,leaving[0]为下课时间
	 * RI:
	 * arrival和leaving数量相等
	 * 任何leaving[i]不可早于arrival[i]
	 * 两列表不可为有空项
	 * Safety:
	 * 不要暴露mutator
	 */
	private final List<LocalDateTime> arrivalTimeList;
	private final List<LocalDateTime> leavingTimeList;

相关JUnit测试已全部通过

3.6面向复用的设计:EntryState及State设计模式

EntryState是一个可变对象,成员变量有类型为enum的state。
在构造方法中,通过字符串参数toUpperCase,再对应到EntryStateEnum中的某一个枚举,进行初始化。
Lab3 Report_第9张图片
Lab3 Report_第10张图片

状态是可变的,因此它需要设置一个Setter,即setNewState()。由于不同的计划项类型,可以设置的state不同,因此参数需要有计划项类型和新状态的字符串。这样的一个方法可以满足各种状态的转换。

/**
	 * 状态转化,将计划项状态转化为NewState
	 * @param strPlanningEntryType(∈{FlightSchedule,TrainSchedule,ClassSchedule})
	 * @param strNewState
	 * @return 成功返回true,否则false
	 */
	public boolean setNewState(String strPlanningEntryType,String strNewState) {
		assert(strPlanningEntryType.toLowerCase().contains("train")
				||!this.getStateString().toLowerCase().equals("blocked"));
		if(this.setability(strPlanningEntryType, strNewState.toUpperCase())) {
			this.stateName=strNewState.toUpperCase();
			this.state=EntryStateEnum.valueOf(strNewState.toUpperCase());
			return true;
		}
		return false;
	}

判断合法性的工作交给另一个范围值为Boolean的方法setability(),而该方法又将这项工作委派给EntryStateEnum中的静态Map变量。该Map分为两种,一种是可能被Block的,一种则不行。判断是否可以Block的工作在EntryStateEnum中进行,用一个List来保存可以Block的类型的关键字(增强鲁棒性)。

/**
	 * 获取当前状态是否可以被转化成其他状态
	 * @param 当前计划项类型strPlanningEntryType(∈{FlightSchedule,TrainSchedule,ClassSchedule})
	 * @param 新的状态strNewState
	 * @return 可以转化返回true,否则false
	 */
	private boolean setability(String strPlanningEntryType,String strNewState) {
		List<EntryStateEnum> availableStateList=new ArrayList<EntryStateEnum>
			(Arrays.asList(this.state.newStateAchievable(strPlanningEntryType)));
		return availableStateList.contains(EntryStateEnum.valueOf(strNewState.toUpperCase()));
	}

Lab3 Report_第11张图片
静态存储能/不能Block的“可以到达的新状态”的Map,Key为该枚举,Value为List。通过匿名对象初始化方法来初始化。

/**
	 * 可以block的可达状态map,key为当前状态,value为可以到达的状态列表
	 */
	public static final Map<EntryStateEnum, EntryStateEnum[]> BlockableNewStateAchievable
	=new HashMap<EntryStateEnum,EntryStateEnum[]>(){
		private static final long serialVersionUID=1L;
		{
			put(WAITING,new EntryStateEnum[] {ALLOCATED,CANCELLED});
			put(ALLOCATED,new EntryStateEnum[]{RUNNING,CANCELLED});
			put(RUNNING,new EntryStateEnum[]{BLOCKED, ENDED });
			put(BLOCKED, new EntryStateEnum[] { RUNNING, CANCELLED });
			put(CANCELLED, new EntryStateEnum[] {});
			put(ENDED, new EntryStateEnum[] {});			
		}
	};
	/**
	 * 不可block的可达状态map,key为当前状态,value为可达的状态列表
	 */
	public static final Map<EntryStateEnum, EntryStateEnum[]> UnblockableNewStateAchievable
	=new HashMap<EntryStateEnum,EntryStateEnum[]>(){
		private static final long serialVersionUID=1L;
		{
			put(WAITING, new EntryStateEnum[] { ALLOCATED, CANCELLED });
			put(ALLOCATED, new EntryStateEnum[] { RUNNING, CANCELLED });
			put(RUNNING, new EntryStateEnum[] { ENDED });
			put(CANCELLED, new EntryStateEnum[] {});
			put(ENDED, new EntryStateEnum[] {});
		}
	};

保存可以Block的计划项名称:

/**
	 * 可以被block的计划项,即Train
	 */
	public static final List<String> keyWords=new ArrayList<String>(){
		private static final long serialVersionUID=1L;
		{
			add("Train");
		}
	};

建立一个属于枚举的成员方法,返回“可以到达的新状态”。调用原状态的枚举对象查询该List(即this代表当前状态):

/**
	 * 获取可以转化成的状态的列表
	 * @param strPlanningEntryType
	 * @return 可以转化成的状态的列表
	 */
	public EntryStateEnum[] newStateAchievable(String strPlanningEntryType) {
	    for (String str : keyWords)
	        if (strPlanningEntryType.contains(str)) {
	        //检索对应map,获取当前状态this的对应可转化状态列表
	        	return EntryStateEnum.BlockableNewStateAchievable.get(this);
	        }      
	    return EntryStateEnum.UnblockableNewStateAchievable.get(this);
	}

因此,在状态模式的设计种,一次设置新状态的操作,经过:

PlanningEntryCollection
-> PlanningEntry
-> EntryState.setNewState() {
EntryState.setability() -> EntryStateEnum.newStateAchievable()}

完成一次指定操作。其中,在PlanningEntryCollection和PlanningEntry中采用外观模式包装成5个方法,分别到达5种状态。

3.7面向应用的设计:Board

这部分我的设计其实有所欠缺,因为board大体上还是相近的,设置一个抽象类和三个子类其实更为合适。但是在本实验中,由于对于JTable掌握过于薄弱话费了太多精力,所以直接将三种board分开写。内容思路相近,几乎是重复性操作而已。具体如下:

3.8Board的可视化:外部API的复用

三个应用的board设计是类似的,以flightSchedule的board为例:
Lab3 Report_第12张图片
在board中我们需要传入计划项集合。
然后根据输入的航班集合、机场名和日期信息来获取当日到达以及飞离指定机场的计划项
根据输入的航班集合、飞机资源、和截止航班获取截止到指定航班使用了指定资源的计划项
并用这些计划项构建JTable
JTable建立过程大致如下:

  JTable table = new JTable(answer, columnNames);

        // 设置表格内容颜色
        table.setForeground(Color.BLACK);                   // 字体颜色
        table.setFont(new Font(null, Font.PLAIN, 14));      // 字体样式
        table.setSelectionForeground(Color.DARK_GRAY);      // 选中后字体颜色
        table.setSelectionBackground(Color.LIGHT_GRAY);     // 选中后字体背景
        table.setGridColor(Color.GRAY);                     // 网格颜色

        // 设置表头
        table.getTableHeader().setFont(new Font(null, Font.BOLD, 14));  // 设置表头名称字体样式
        table.getTableHeader().setForeground(Color.RED);                // 设置表头名称字体颜色
        table.getTableHeader().setResizingAllowed(false);               // 设置不允许手动改变列宽
        table.getTableHeader().setReorderingAllowed(false);             // 设置不允许拖动重新排序各列

        // 设置行高
        table.setRowHeight(30);

        // 第一列列宽设置为40
        table.getColumnModel().getColumn(0).setPreferredWidth(40);

        // 设置滚动面板视口大小(超过该大小的行数据,需要拖动滚动条才能看到)
        table.setPreferredScrollableViewportSize(new Dimension(1000, 100));
        table.getColumnModel().getColumn(0).setPreferredWidth(250);
        table.getColumnModel().getColumn(1).setPreferredWidth(200);
        table.getColumnModel().getColumn(2).setPreferredWidth(350);
        table.getColumnModel().getColumn(3).setPreferredWidth(200);
        
        return table;

3.9PlanningEntryCollection设计

该ADT是PlanningEntry的集合类。该集合类应该能够存储所有计划项、所有位置和可用资源,以及作为一个“管理者”的身份来操作这些成员变量。
Lab3 Report_第13张图片
在PlanningEntryCollection中实现了一些共性方法,并定义了一些抽象方法。不同子类实现有差异的方法有:添加计划项、分配资源、排序计划项;其余为相同的实现。
开始、取消、暂停、结束一个计划项这4种操作利用外观模式,将单个计划项中的成员方法进行封装。首先在所有计划项中找到该计划项,若找到则再进行对应的操作。以cancelPlanningEntry()为例:

	/**
	 * cancel a plan
	 * @param planningEntryNumber
	 * @return true if successfully the plan is successfully canceld
	 */
	public boolean cancelPlanningEntry(String planningEntryNumber) {
		PlanningEntry<Resource> planningEntry=this.getPlanningEntryByStrNumber(planningEntryNumber);
		return planningEntry==null?false:planningEntry.cancel();
	}

此外,共性方法还有计划项、资源、位置的Getter,以及删除单个资源和位置的方法。
接下来是有差异的方法。首先是新增一个计划项,以Flight Schedule为例。首先addPlanningEntry()有一个重载方法,可以将已经提取好的参数直接输入并新建:

/**
	 * create a new flightSchedule plan
	 * @param planningDatestr
	 * @param planningEntryNumber
	 * @param departureAirport
	 * @param arrivalAirport
	 * @param departureTime
	 * @param arrivalTime
	 * @return the new flight schedule
	 */
	public FlightSchedule<Resource> addPlanningEntry(String planningDatestr,String planningEntryNumber,
			String departureAirport,String arrivalAirport,
			String departureTime,String arrivalTime){
		Location location=new Location(departureAirport,arrivalAirport);
		Timeslot timeslot=new Timeslot(Arrays.asList(departureTime,arrivalTime),Arrays.asList(departureTime,arrivalTime));
		DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd");
		LocalDate planningDate=LocalDate.parse(planningDatestr);
		this.collectionLocation.addAll(location.getLocationList());
		PlanningEntry<Resource> flightSchedule=PlanningEntry.newFlightEntry(planningDate,location, timeslot, planningEntryNumber);
		this.planningEntries.add(flightSchedule);
		return (FlightSchedule<Resource>)flightSchedule;
	}

通过重载的方法,提供两种不同的新建方式,方便在之后的GUI客户端新建计划项的操作。接下来实现在抽象类中定义的方法,主要是要通过正则表达式提取所需的要素,并调用上述方法。用Pattern对象定义模式,用Matcher对象进行匹配;若匹配成功,则用group方法提取参数输入上述方法。

@Override
	public PlanningEntry<Resource> addPlanningEntry(String stringInfo) {
		Pattern pattern=Pattern.compile(
				"Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");
		Matcher matcher=pattern.matcher(stringInfo);
		if(!matcher.find()) {
			return null;
		}
		String planningDatestr=matcher.group(1);
		String planningEntryNumber=matcher.group(2);
		String departureAirport=matcher.group(3);
		String arrivalAirport=matcher.group(4);
		String departureTime=matcher.group(5);
		String arrivalTime=matcher.group(6);
		return this.addPlanningEntry(planningDatestr,planningEntryNumber, departureAirport, arrivalAirport, departureTime, arrivalTime);

	}

分配资源也是有差异的方法,与新建计划项的方法类似。新增了几个重载方法,模块化、也方便调用。有两种Pattern,对应两种输入模式。

@Override
	public boolean allocatePlanningEntry(String planningEntryNumber, String stringInfo) {
		if(this.getPlanningEntryByStrNumber(planningEntryNumber)==null) {
			return false;
		}
		Pattern pattern1=Pattern.compile("Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");
		Pattern pattern2=Pattern.compile("Plane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n");
		Matcher matcher=pattern1.matcher(stringInfo);
		if(!matcher.find()) {
			matcher=pattern2.matcher(stringInfo);
			if(!matcher.find()) {
				return false;
			}
		}
		String number=matcher.group(7);
		String strType=matcher.group(8);
		int intSeats=Integer.valueOf(matcher.group(9));
		String age=matcher.group(10);
		this.allocateResource(planningEntryNumber,number,strType,intSeats,age);	
		Plane plane=new Plane(number,strType,intSeats,age);
		if(this.collectionResource.contains(plane)){
			return true;
		}
		else return false;
	}

这需要调用

public Resource allocateResource(String planningEntryNumber, String number, String strType, int intSeats,
			String age) {
		Plane plane=new Plane(number,strType,intSeats,age);
		//****
		FlightSchedule<Resource> flightSchedule=(FlightSchedule<Resource>) this.getPlanningEntryByStrNumber(planningEntryNumber);
		this.collectionResource.add(plane);
		return flightSchedule.allocateResource(plane)==true?plane:null;
	}

最后是按时间顺序排序计划项sortPlanningEntries()。首先定义一个Comparator,并重写其compare方法,然后调用Collections.sort()方法进行排序。在Flight Schedule中的Comparator对象,compare方法通过获取时间进行比较,具体如下:

//定义一个Comparator
		Comparator<PlanningEntry<Resource>> comparator=new Comparator<PlanningEntry<Resource>>() {
			@Override
			/*
			 * 比较p1计划项起飞时间是否早于p2计划项终点降落时间,早则返回-1,否则返回1
			 * 返回1时前一个计划项应当排在后一个计划项之后
			 */
			public int compare(PlanningEntry<Resource> p1,PlanningEntry<Resource> p2) {
				return ((FlightSchedule<Resource>) p1).getLeavingTime()
						.isBefore(((FlightSchedule<Resource>) p2).getArrivalTime())?-1:1;
			}
		};

3.10可复用API设计及Façade设计模式

下列应用已全部通过JUnit测试

3.10.1检测一组计划项之间是否存在位置独占冲突

要检测一组计划项之间是否存在位置冲突,主要应该检测每一个位置的若干计划项是否有时间冲突。首先要保存下每个位置的所有计划项,我使用的是一个Map、键为位置String、值为使用该位置的所有计划项的List。
接下来,遍历所有计划项。对于每个计划项:若该计划项的位置未被加入Map的键集合,则加入并将值赋值为仅有该计划项的List;否则,将该计划项加入原有的值的List中,并考察List中是否有冲突,最后更新该值。
考察List是否有冲突,要遍历任意两个计划项,是否存在这时间重叠。对于任意两个不同计划项c1、c2,分别获取它们的起始时间和结束时间,进行比较:若一方的起始时间早于另一方的结束时间且结束时间晚于起始时间,则认为冲突。

3.10.2检测一组计划项之间是否存在资源独占冲突

与判断地点冲突类似

3.10.3提取面向特定资源的前序计划项

提取特定资源的前序计划项,是要搜索使用同一资源、计划时间在选定计划项之前且最晚(与选定计划项时间最近)的计划项。该方法类似与在一组数据中选取符合条件的最大值。整体思路就是遍历、筛选、比较
首先,初始化“最晚时间”和“前序计划项”;
Lab3 Report_第14张图片
然后,遍历所有计划项,选出使用相同资源的计划项(在迭代时比较资源是否相同来进行筛选):

for (int i = 0; i < entries.size(); i++) {
    if (entries.get(i).getResource().equals(e.getResource())) {
        ……}

在迭代中比较,若符合筛选条件,且比原最晚时间更晚,则更新:
Lab3 Report_第15张图片
最后返回prePlanningEntry即可。

3.11设计模式应用

请分小节介绍每种设计模式在你的ADT和应用设计中的具体应用。

3.11.1Factory Method

设置3个PlanningEntry接口的工厂方法,分别新建1种计划项子类型。以Flight Schedule为例,需要输入Location、TimeSlot和计划项编号3个参数,返回一个FlightSchedule计划项类型:

	public static<Resource> FlightSchedule<Resource> newFlightEntry(LocalDate planningDate,Location location,Timeslot timeslot,String planningEntryNumber){
		return new FlightSchedule<Resource>(planningDate,location,timeslot,planningEntryNumber);
	}

3.11.2Iterator

在Collection中,用一个List存储所有的计划项;
在board中直接继承Iterator父类

3.11.3Strategy

如设立抽象的allocate资源
然后再每个子类中根据不同资源特征进行分配

3.12应用设计与开发

其实三类应用的设计几乎相同,只是需要注意高铁应用中多个资源和经停站的问题
从eclipse中下载WindowBuilder,用JFrame进行app开发

3.12.1航班应用

Lab3 Report_第16张图片
再比如
Lab3 Report_第17张图片
Apis
Lab3 Report_第18张图片
其实就是调用board实现功能即可。
我的理解是这个app就是各个应用的集成,之前设计的接口应当在这里有一个很好的耦合表现。

!注:本实验中判断资源冲突时采取的遍历方式非常基础,导致对于内容量比较大的file1、3、4判断时需要花费很长的时间

3.12.2高铁应用

为了便捷不作规范输入
Lab3 Report_第19张图片Lab3 Report_第20张图片Lab3 Report_第21张图片

状态转换时通过按钮调用在planningentry中的函数即可,比如可以run一下
Lab3 Report_第22张图片
在running状态下allocate则会报错
Lab3 Report_第23张图片
现在给plan2 allocate相同的resource,可判断resource冲突
检测某一车站情况
Lab3 Report_第24张图片

3.12.3课表应用

这个应用相比前两个主要多出了location冲突问题
Lab3 Report_第25张图片
考虑实际,在allocated阶段可以换教室
Lab3 Report_第26张图片
但是running等情况下就不可以换教室了
Lab3 Report_第27张图片
在这个面板中我还添加了某一位老师的教学经历面板,这个功能同样在board类中实现
Lab3 Report_第28张图片

3.13基于语法的数据读入

读取file5示例文件
对于文件的读取,可以分组进行,每组13行作为一个整体Stringinfo处理

BufferedReader bReader=null;
				try {
					bReader = new BufferedReader(new FileReader(new File("data/FlightSchedule//FlightSchedule_5.txt")));
				} catch (FileNotFoundException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				String line="";
				int cntLine=0;
				StringBuilder stringInfo=new StringBuilder("");
				try {
					while((line=bReader.readLine())!=null) {
						if(line.equals("")) {
							continue;
						}
						stringInfo.append(line+"\n");
						cntLine++;
						if(cntLine%INPUT_ROWS_PER_UNIT==0) {
							FlightSchedule<Resource> flightSchedule
							=(FlightSchedule<Resource>) flights.addPlanningEntry(stringInfo.toString());
							if(flightSchedule!=null) {
								flights.allocatePlanningEntry(
										flightSchedule.getPlanningEntryNumber(), stringInfo.toString());
								stringInfo=new StringBuilder("");
							}
						}
					}
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					bReader.close();
					lblNewLabel_26.setText("success");
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}

3.14应对面临的新变化

只考虑你选定的三个应用的变化即可。

3.14.1变化1 航班

由于本身location就采取字符串列表的形式,所以按照之前思路,三个站类似高铁取下标即可。
在这里插入图片描述
获取位置方法则添加Lab3 Report_第29张图片
当然为了方便也可以直接输入int获取对应位置
Lab3 Report_第30张图片
Timeslot同理
Lab3 Report_第31张图片
底层构架严格意义上只需要多加三个getter即可,改动不大。
主要的改动在于之后的board考虑经停的部分,这一部分同理和高铁的想法类似,将原本分开的起飞和到达融合到一起,原本确定的equals()关系编程contains()表达
由于board并没有设计成可复用的类型,但好在代码几乎和TrainBoard照搬
接着就是按照train的逻辑调整app
(我突然发现之前trainApp里写了两个一模一样的展示板)
Lab3 Report_第32张图片

3.14.2变化2 高铁

直接重写cancel()方法,增加判断语句限制前置状态即可。
Lab3 Report_第33张图片
代价非常小

3.14.3变化3 课程

课程可以有多个教师一起上课,且需要区分次序(即多名教师的优先级)
这还是按照高铁的想法,利用一个集合来存储多个资源,之于优先级可以给教师加一个属性,也可以建立一个Map,在匹配资源时连接对应的优先级大小,优先级用int表示
此处用前一种方法修改
为了尽量减少修改,各种方法向高铁的资源处理靠拢
由于本实验中代码基层考虑的是课程和航班这样单个资源的耦合,所以讲课程剥离出来改动非常大,需要层层修改代码

3.15Git仓库结构

在这里插入图片描述

你可能感兴趣的:(软件构造)