模板方法模式(Template Method)及应用
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。(来自百度百科)
模板方法模式在框架中经常使用,了解此模式对于阅读框架源码十分有用。我先描述生活中的实际场景,引申出模板方式模式,然后分析此模式在JDK中的使用,最后分析在框架SpringMVC中的使用。
1、冲咖啡和泡奶茶如何抽取成一个模板
1.1 模板思路
冲咖啡步骤
(1)把水煮沸
(2)把沸水冲咖啡
(3)倒进杯子里
(4)加奶加糖
泡奶茶步骤
(1)把水煮沸
(2)把沸水泡茶叶
(3)倒进杯子里
(4)加点奶
可以发现,冲咖啡和泡奶茶有两个步骤是一样的:把水煮沸和倒入杯子中,有两个步骤不一样。那么是不是将两个相同的方法抽取在父类当中,用final修饰,让咖啡和奶茶两个子类去继承这个父类,两个子类就具有了父类把水煮沸和倒入杯子中的能力。再去分别实现另外两个步骤就可以了,最后完成冲咖啡和泡奶茶的流程。
可是,这种做法对于父类来说,整个步骤是不可控的,虽然子类具有了父类的方法,但是采用了怎样的步骤顺序去制作咖啡和奶茶,父类完全不清楚。所以,这里要做出一些改进,如果我们能在父类中将咖啡或者奶茶的每一个步骤都定义好,需要子类实现的方法定义成抽象类,然后将整个流程封装在一个方法里,子类继承父类后直接调用父类的这个方法,就能完全控制住整个操作步骤了。
1.2 具体的代码实现
/** * 茶和咖啡的抽象父类 */ public abstract class CaffeineBeverage { public final void prepareRecipe(){ boilWater(); brew(); pourCup(); addCondiment(); } abstract void brew(); abstract void addCondiment(); void boilWater(){ System.out.println("烧水"); } final void pourCup(){ System.out.println("倒入杯子中"); } }
public class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("加入咖啡"); } @Override void addCondiment() { System.out.println("加入奶泡和糖"); } }
public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("放入茶叶"); } @Override void addCondiment() { System.out.println("加点奶"); } }
public class TemplateMethodPattern { public static void main(String[] args) { Coffee coffee = new Coffee(); coffee.prepareRecipe(); } }
1.3 代码分析
使用了模板方法,子类只需要去继承父类并实现父类中的抽象方法,但具体的执行步骤还是在父类CaffeineBeverage中定义并用final修饰的,这一点保证了步骤的统一。另外,在父类中具体的方法并不一定要被final修饰,可以由子类去决定用不用或怎么使用。重点的是模板方法的思想,父类控制流程,子类控制某些步骤。
另外,还有一点,创建模板对象时,要不要用模板对象去接收?这个问题我的想法是可以,看具体的使用场景。
2、 Java API中的模板方法思想
2.1 数组的排序
平时我们对操作数据排序时,会使用Arrays.sort(Object[] o)方法。sort()调用的最重要的方法是mergeSort(),他就是一个模板方法,依赖实现类实现comparaTo()完成排序。sort()方法和mergeSort()方法这些都是静态方法之间的调用,在比较数据中的对象时,会先将对象强转成Comparable,调用CompareTo()进行比较,如果需要交换位置,则调用swap()。这也就是为什么,当我们自定义类排序时,一定要实现Compareble并重写compareTo()。
public class Arrays { ... public static void sort(Object[] a) { if (LegacyMergeSort.userRequested) legacyMergeSort(a); else ComparableTimSort.sort(a, 0, a.length, null, 0, 0); } private static void legacyMergeSort(Object[] a) { Object[] aux = a.clone(); mergeSort(aux, a, 0, a.length, 0); }
//排序中的模板方法,依赖comparaTo()方法的实现完成 private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) { int length = high - low; // Insertion sort on smallest arrays if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); return; } // Recursively sort halves of dest into src int destLow = low; int destHigh = high; low += off; high += off; int mid = (low + high) >>> 1; mergeSort(dest, src, low, mid, -off); mergeSort(dest, src, mid, high, -off); // If list is already sorted, just copy from src to dest. This is an // optimization that results in faster sorts for nearly ordered lists. if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } // Merge sorted halves (now in src) into dest for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) dest[i] = src[p++]; else dest[i] = src[q++]; } } ... }
2.2 代码分析
看到这里,可能会有些迷惑,这里为什么没有用继承,模板方法的核心不应该是继承吗?其实并不是这样,只要能让流程在类中控制住,子类或实现类实现需要实现的部分,这就是一个完整的模板方法模式。sort方法是静态方法,就可以在所有数组中使用,只需要这个数组中的对象实现Comparable,这样用起来更加方便,假如是继承的话,是没法保证每一个类都能继承Comparable,毕竟Java只支持单继承。
3、SpringMVC中的模板方法
3.1使用过SpringMVC框架都会了解,整个SpringMVC的流程是由DispatcherServlet控制的,下面是DispatherServlet的类继承图。
注:使用idea可以很容易的看到其结构图:ctrl+shift+alt+U
DispatcherServlet本质是Servlet,具有init(),service(),destroy(),tomcat运行时,会加载web.xml将中的servlet,servlet调用init()方法。init()中有很多工作需要准备,如初始化web容器,导入配置,初始化HandlerMappings、HandlerAdapters等等,这些工作不是全部都在DispatcherServlet中完成,有一部分工作是在其父类中完成的。
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { ...
//这是一个模板方法 @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); //让其子类实现的方法 if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } ... }
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
...
@Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
... }
当DispatcherServlet初始化,会调用HttpServletBean中的init(),然后执行FrameworkServlet中的initServletBean()完成初始化。
参考资料:《Head First 设计模式(中文版)》,SpringMVC源码