目录
1.概念
在父类中定义算法的框架,让子类根据业务需要,来填充框架的具体实现步骤。模板方法使子类可以重新定义算法的某些实现,而无需更改算法的结构。
为确保子类不会重写template方法,应声明模板方法为final
2.适用性
- 父类实现算法的不变部分,并让子类来实现可能变化的行为。
- 子类之间的共同行为应分解并集中在一个共同类中,以避免代码重复。
3.程式范例
我们以农民伯伯播种为例,比如去年种植了小麦,今年想种植玉米了,而种植的流程大致分为:购买种子、播撒、浇水等,不管是种啥,都按这个流程走,那么这个现象,就可以归结为一个模板方法模式了。
我们看看具体的代码实现吧!
3.1.抽象类来封装算法的框架和核心算法
public abstract class AbstractPlantMethod {
/**
* 购买种子
*/
public abstract String buySeeds();
/**
* 播撒
*/
public abstract void sow(String seeds);
/**
* 浇水
*/
public abstract void watering(String seeds);
/**
* 种植方法(定义为final,防止子类重写该核心算法)
*/
public final void planting() {
String seeds = this.buySeeds();
this.sow(seeds);
this.watering(seeds);
}
}
3.2.播种小麦
public class WheatMethod extends AbstractPlantMethod {
@Override
public String buySeeds() {
System.out.println("购买小麦作物中...");
return "wheats";
}
@Override
public void sow(String seeds) {
System.out.println("正在播撒" + seeds + "中...");
}
@Override
public void watering(String seeds) {
System.out.println("正在给" + seeds + "浇水中...");
}
}
3.3.播种玉米
public class CornMethod extends AbstractPlantMethod {
@Override
public String buySeeds() {
System.out.println("购买玉米作物中...");
return "corns";
}
@Override
public void sow(String seeds) {
System.out.println("正在播撒" + seeds + "中...");
}
@Override
public void watering(String seeds) {
System.out.println("正在给" + seeds + "浇水中...");
}
}
3.4.农民伯伯
public class Farmers {
private AbstractPlantMethod plantMethod;
public Farmers(AbstractPlantMethod plantMethod) {
this.plantMethod = plantMethod;
}
public void plant() {
plantMethod.planting();
}
public void changeMethod(AbstractPlantMethod plantMethod) {
this.plantMethod = plantMethod;
}
}
3.5.客户端调用者
public class Client {
public static void main(String[] args) {
Farmers farmers = new Farmers(new WheatMethod());
//播种小麦
farmers.plant();
farmers.changeMethod(new CornMethod());
//播种玉米
farmers.plant();
}
}
3.6.结果打印
购买小麦作物中...
正在播撒wheats中...
正在给wheats浇水中...
购买玉米作物中...
正在播撒corns中...
正在给corns浇水中...
4.模板方法模式在JDK1.8中的使用
ArrayList相信我们每天都在用,但是我们从来没关注过它的父类和实现的接口,下面我列出它的父类AbstractList的部分关键代码。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
//get方法(抽象的方法,必须让子类按照自己的业务去实现)
abstract public E get(int index);
//addAll方法,允许子类去重写该方法,如果不重写,也可用父类中已经定义好的方法。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
}
再来看看ArrayList中的关键代码吧
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//实现的get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
//重写父类的addAll方法
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
}
5.模板方法模式在Spring中的使用
spring在构建Servlet体系的时候,用到了我们的模板方法模式,我们先来看父类的关键代码。
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//重写了Javax提供的init方法,并将方法用final修饰,那么它的子类就没有权限修改init方法了
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
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) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
}
而它的子类FrameworkServlet和DispatcherServlet就不能再去重写init方法了,只需根据需要重写部分方法,公用的是一个init方法。
6.总结
如果我们希望子类不要修改父类的方法,只需要加上final修饰即可;如果希望子类一定重写父类的方法,就将父类的方法用abstract修饰;如果子类可以修改也可以不修改,就可以像addAll方法那样设计即可。