完成Lab3后对代码可复用性的一些思考
在完成Lab3的过程中,我大概尝试了三种ADT设计方法。最开始使用的是朴素的方法(即对于一个具体实例而言,写一个特定的类提供给该实例使用),这种方法自然很快就被否决了;于是对delegation进行了尝试,也实现了全部功能,个人感觉可复用性是较好的;最后在课程中学习了decorator设计方法后,对decorator进行了尝试,并作为了最终版本呈现在了实验代码中。这篇博客,主要想对后两种方法进行比较,讨论一下哪种模式更有利于提高代码的可复用性。
一、Delegation(委派)
delegation的设计思想还是很显然的。例如在Lab3中,每一个计划项都需要对Resource,Location,Time和State这四类属性进行相应操作。那么根据delegation的思想,在计划项类中就不再具体实现这些操作方法,而是委派给R,L,T,S这四个接口去实现;在每一个接口实现的操作中,可能需要满足不同的需求,那么为该接口构建不同的接口实现类,在具体的计划项中调用需要的接口实现类即可(比如在Plane实例中可能存在两个location,那么Plane调用的就是Location接口下的MultiLocation实现类)。由于使用delegation思想设计的代码已经被删除了,不能展示相关代码,我这里就暂且用一张图来演示。
设计的逻辑如上图所示,PlanningEntry通过调用delegattion接口中的不同类实现自身的个性化需求。
二、Decorator(装饰)
decorator的实现相对比较复杂,我们就拿Location的装饰来解释。下面我们来看代码。
//首先我们需要创建PlanningEntry接口
public interface PlanningEntry<R> {
/**
* get the list of locations in the entry
*
* @return the list of locations in the entry
*/
public List<Location> getLocations();
/**
* change the location of the entry
*
* @param SetLocation
* @return true if the location can be changed; false otherwise
*/
public boolean changeLocation(Location SetLocation);
}
//然后创建最一般的PlanningEntry实例,它的设计思路采用delegation,调用一个普通的Location委派类
public class Normal<R> implements PlanningEntry<R> {
private LocationEntry location;
public Normal(LocationEntry SetLocation) {
location = SetLocation;
}
@Override
public List<Location> getLocations() {
return location.getLocations();
}
@Override
public boolean changeLocation(Location SetLocation) {
return location.changeLocation(SetLocation);
}
}
代码写到这里我们就可以实现一个调用一般的location方法的计划项了。接下来就要通过decorator实现个性化操作。
//首先创建一个抽象类作为PlanningEntry接口的子类
public abstract class LocationDec<R> implements PlanningEntry<R> {
protected PlanningEntry<R> entry;
public LocationDec(PlanningEntry<R> SetEntry) {
entry = SetEntry;
}
@Override
public abstract List<Location> getLocations();
@Override
public abstract boolean changeLocation(Location SetLocation);
}
//接着创建抽象类的子类,不需要个性化的方法直接调用entry的delegation实现即可,而需要个性化的方法进行具体的重写
//例如这里的MultiLocationEntry,changeLocation功能不可用,所以直接在方法的实现中return false即可实现个性化操作
public class MultiLocationEntry<R> extends LocationDec<R> {
public MultiLocationEntry(PlanningEntry<R> SetEntry) {
super(SetEntry);
}
@Override
public List<Location> getLocations() {
return entry.getLocations();
}
@Override
public boolean changeLocation(Location SetLocation) {
return false;
}
}
那么对于Location的装饰,我们就做好了。如果想要新建一个调用MultiLocation的计划项,我们只需要“做一个套娃”就行了。
PlanningEntry<R> entry = new MultiLocationEntry<R>(new Normal<R>(...)); //Normal的初始化省略了
同样的,当我们构建好了四个decorator后,只要一层一层的把装饰套进去。
//PlaneEntry的工厂方法
public class FlightFactory {
public static PlanningEntry<Plane> getEntry(LocationEntry location, ResourceEntry<Plane> resource,
EntryState state, TimeEntry time){
return new UnBlockableStateEntry<Plane>(new MultiLocationEntry<Plane>(new Normal<Plane>(location, resource, state, time)));
}
}
//TrainEntry的工厂方法
public class TrainFactory {
public static PlanningEntry<Train> getEntry(LocationEntry location, ResourceEntry<Train> resource,
EntryState state, TimeEntry time, List<Integer> block) {
return new BlockableTimeEntry<Train>(new MultiLocationEntry<Train>(
new Normal<Train>(location, resource, state, time)), block);
}
}
//CourseEntry的工厂方法
public class CourseFactory {
public static PlanningEntry<Teacher> getEntry(LocationEntry location, ResourceEntry<Teacher> resource,
EntryState state, TimeEntry time) {
return new UnBlockableStateEntry<Teacher>(new Normal<Teacher>(location, resource, state, time));
}
}
怎么样,是不是觉得这样套娃很方便而且很有成就感。但是我想在这里泼一盆冷水,decorator也没有我们看上去这么高效便捷。首先看上面的代码就可以看出来实现一个decorator的过程其实还是相当繁琐的,如果仅仅为了一种个性化装饰而开发一整套装饰流程显得大材小用了一点;其次通过上面的代码感受不到的是,一个计划项涉及到的方法可能有十多种,但是一次装饰可能只修改其中一两个方法,其余的方法仍需要通过机械化的Override(如下图所示),这是decorator作为PlanningEntry接口实现类的一个不可避免的局限性。(当然这也可能是我自身设计的问题,希望大家能解答我的疑惑)
注:如上图所示,实际的MultiLocationEntry装饰类中,存在大量的重复化机械化的delegation
所以这样来看,decorator在Lab3中的编写效率可能还不如delegation好,只是当我意识到这样一个情况的时候,decorator的设计模式已经完成,而且decorator设计完成后对计划项的新建和调用真的十分方便,所以就使用了它。(但这并不意味着decorator在Lab3中就是最佳设计方案吧)
总结
我们不能够简单扣上一个decorator一定比delegation好用的帽子,而是要具体问题具体分析。对于那些分类情况多,可能存在组合爆炸的问题而言,使用decorator可能是一个很高效的选择;但是对于Lab3而言,仅仅存在三种组合,那么可能直接的delegation反而可以更加高效。所以我们在设计ADT之前,一定要考虑清楚利弊再下手,我就是缺乏这方面的经验才几次三番的改方案,走了很多弯路。当然,对于我这样的新手,走些弯路,体验各种各样的设计模式,也许本身是件好事。
下一篇: Android与webview混合开发
推荐阅读