本次实验要求从五项要求(航班管理、高铁车次管理、操作系统进程管理、大学课表管理、学习日程管理)里完成三项要求,并且尽量实现复用。
面向可复用性和可维护性的设计:PlanningEntry
1.首先设计一个类CommonPlanningEntry来实现共同的操作,比如创建新计划项、启动、取消、完成、获取名字、获取状态。
2.对于各情形的个性化特征
如果各应用在某个维度上的特征值完全相同,则可以将针对处理该共性特征
值的操作在 PlanningEntry和 CommonPlanningEntry中定义和实现,即实现完全的复用。如果在某个维度上的特征取值完全不同,就必须在各个应用的具体子类中分别实现其对不同特征取值的个性化操作。有以下几种方案。
3.方案1:将所有特殊操作均放入顶层的抽象接口
将各应用中出现的所有特殊操作都放入 PlanningEntry 接口中定义并CommonPlanningEntry 中实现它们。对各应用的具体子类,如果接口中的某个操作不适用于自己,则应用开发者不要使用它们。例如,override 后的方法体设置为空实现,或者直接 assert false,或者 throws new Exception。但是,这么做会导致各个应用子类中大量 override 空实现的方法,代码会很不美。另一方面,这种做法会导致违反 LSP 原则,因为子类中空实现的 block 方法不再符合接口中 block方法的 spec,子类型对象无法替代父类型对象。
4.方案 2:将各特殊操作分别放入底层的应用子类
将针对不同特征取值的具体操作分别放在五个应用的子类中加以实现。例如:针对高铁和进程应用的 PlanningEntry 子类中实现 block 方法,其他三个应用的 PlanningEntry 子类不需实现该方法。该方案的缺点是:某些方法的代码可能是重复的、分散在多个类中,可维护性和可复用性差,将来一旦面临变化就需要修改多处代码。
5.方案 3:为不同特征取值分别定义接口并在子类中实现其特殊操作
为每个维度上的不同特征取值分别定义不同的接口,在接口中定义特殊的操作,各应用的具体子类根据自己的需求来实现不同特征的接口。以“位置数量”维度为例:设置一个位置、两个位置、多个位置的接口。这种方案与方案 2 有同样的缺陷:某些局部共性的操作仍然需要在多个子类中分别 override。
6.方案 4:定义接口并实现具体类,通过继承树实现各应用在多维度上的不同特征取值的组合
在方案 3 基础上,除了为每个维度上的每个特征取值定义相应的接口,另外为每个接口分别构造一个实现类,该类一方面继承 CommonPlanningEntry中的全局共性操作,另一方面在该类中完成对局部共性操作的代码。(有种步步为营的感觉)缺点是如果同时考虑五个维度上的特征,将可能组合出非常多的子类数量,形成了庞大的继承树,给软件系统的维护代码极大困难。
7.方案5:CRP,通过接口组合实现局部共性特征的复用(推荐)
针对方案 4,通过 delegation 机制进行改造。每个维度分别定义自己的接口,针对每个维度的不同特征取值,分别实现针对该维度接口的不同实现类,实现其特殊操作逻辑。进而,通过接口组合,将各种局部共性行为复合在一起,形成满足每个应用要求的特殊接口(包含了该应用内的全部特殊功能),从而该应用子类可直接实现该组合接口。在应用子类内,不是直接实现每个特殊操作,而是通过 delegation 到外部每个维度上的各具体实现类的相应特殊操作逻辑。
8.方案 6:使用 decorator 设计模式
将 CommonPlanningEntry 看作是原始的、未被装饰的计划项实体,将这五个维度看作是五种“装饰”(每个维度的不同特征取值可以产生不同的“装饰”效果)。
面向复用的设计:Location
PlanningEntry 的某些子类型的 rep 中不可避免的需要表达“位置”信息。需设计 Location 类,它可以是接口、抽象类或具体类,是 immutable 的。
一个“位置”对象的属性包括:经度、纬度、名称、是否可共享使用。所谓的“是否可共享”是指:该位置是否可同时被多个计划项所使用。应用 3 中的“位置”是“某个 CPU 核”,它无需使用经度和纬度加以描述,只需使用名称区分即可。
面向复用的设计:Timeslot
不可变类。为一个单独的“起止时间对”设计 Timeslot 类,它是一个带有起始时间和结束时间的ADT,应包含日期(年/月/日)和时间(时/分),符合 yyyy-MM-dd HH:mm 的语法规则。例如:(2020-03-01 12:00, 2020-03-01 14:00)