23种设计模式(5):模板方法模式

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方法那样设计即可。

发布了39 篇原创文章 · 获赞 13 · 访问量 2287

猜你喜欢

转载自blog.csdn.net/weixin_45612794/article/details/103957287