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方法那樣設計即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章