JAVA设计模式之--模板方法模式

JAVA设计模式之--模板方法模式

Scroll Down
小提示,本文编写于  1,029  天前,最后编辑于  580  天前,某些信息可能有些出入,仅供参考。

序言

在现实生活中,很多事情都包含几个实现步骤,例如请客吃饭,无论吃什么,一般都包含点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单 --> 吃东西 --> 买单。在这三个步骤中,点单和买单大同小异,最大的区别在于第二步——吃什么?吃面条和吃满汉全席可大不相同,如下图1所示:
image.png

在软件开发中,有时也会遇到类似的情况,某个方法的实现需要多个步骤(类似“请客”),其中有些步骤是固定的(类似“点单”和“买单”),而有些步骤并不固定,存在可变性(类似“吃东西”)。为了提高代码的复用性和系统的灵活性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计,在模板方法模式中,将实现功能的每一个步骤所对应的方法称为基本方法(例如“点单”、“吃东西”和“买单”),而调用这些基本方法同时定义基本方法的执行次序的方法称为模板方法(例如“请客”)。在模板方法模式中,可以将相同的代码放在父类中,例如将模板方法“请客”以及基本方法“点单”和“买单”的实现放在父类中,而对于基本方法“吃东西”,在父类中只做一个声明,将其具体实现放在不同的子类中,在一个子类中提供“吃面条”的实现,而另一个子类提供“吃满汉全席”的实现。通过使用模板方法模式,一方面提高了代码的复用性,另一方面还可以利用面向对象的多态性,在运行时选择一种具体子类,实现完整的“请客”方法,提高系统的灵活性和可扩展性。

  • 以下介绍的模板方法模式将解决以上类似的问题。

模板方法模式的定义与特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,它是一种类行为型模式。

主要优点

  1. 它封装了不变部分,扩展可变部分。
  2. 它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  3. 它在父类中提取了公共的部分代码,便于代码复用。
  4. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

相对来说主要缺点

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

模板方法模式的结构与实现

  • 模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式。
  1. 模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。
  2. 通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。
  3. 模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。

模板方法模式的结构

模板方法模式主要包含以下角色。

  (1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

  ① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

  ② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  (2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

  • 模板方法模式的结构图如图所示:
    image.png

模板方法模式的基本实现

//TemplateMethodTest.java
package TemplateMethod;

public class TemplateMethodTest {

    public static void main(String[] args) {
        AbstractClass tm = new ConcreteClass();
        tm.TemplateMethod();
    }
}

//抽象类
abstract class AbstractClass {
    //模板方法
    public void TemplateMethod() {
        SpecificMethod();
        abstractMethod1();
        abstractMethod2();
    }

    //具体方法
    public void SpecificMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }

    public abstract void abstractMethod1(); //抽象方法1

    public abstract void abstractMethod2(); //抽象方法2
}

//具体子类
class ConcreteClass extends AbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("抽象方法2的实现被调用...");
    }
}
抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法2的实现被调用...

模板方法模式模式应用场景

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

模板方法模式的扩展

在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如下图所示。
image.png
代码实现:

//HookTemplateMethodTest.java
package TemplateMethod;

public class HookTemplateMethodTest {
    public static void main(String[] args) {
        HookAbstractClass tm = new HookConcreteClass();
        tm.TemplateMethod();
    }
}

//含钩子方法的抽象类
abstract class HookAbstractClass {
    //模板方法
    public void TemplateMethod() {
        abstractMethod1();
        HookMethod1();
        if (HookMethod2()) {
            SpecificMethod();
        }
        abstractMethod2();
    }

    //具体方法
    public void SpecificMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }

    public void HookMethod1() {
    }  //钩子方法1

    //钩子方法2
    public boolean HookMethod2() {
        return true;
    }

    public abstract void abstractMethod1(); //抽象方法1

    public abstract void abstractMethod2(); //抽象方法2
}

//含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("抽象方法2的实现被调用...");
    }

    public void HookMethod1() {
        System.out.println("钩子方法1被重写...");
    }

    public boolean HookMethod2() {
        return false;
    }
}

程序运行结果如下:

抽象方法1的实现被调用...
钩子方法1被重写...
抽象方法2的实现被调用...

实际使用实例

刚好遇到个很搭模板方法设计模式的情况,代码如下

public abstract class ReplyChartDataRenderTemplate {
	private String moduleName;
	private static Logger logger=Logger.getLogger(ReplyChartDataItem.class);


	public ReplyChartDataRenderTemplate(String moduleName) {
		super();
		this.moduleName = moduleName;
	}

	public final void prepareChartData(FbaTaskStateBean taskStateBean, List<Object> dataCollector,
			InspectionMode inspectionMode) {
		List<Object> chartDataList = new ArrayList<>();
		if (this.isDiffType4Adequacy(taskStateBean)) {

			List<Object> level2VolumeIdsMappingList = getXaxis2RollIdsMapping(taskStateBean.getTaskId());

			if (level2VolumeIdsMappingList != null && level2VolumeIdsMappingList.size() > 0) {
				for (int i = 0; i < level2VolumeIdsMappingList.size(); i++) {
					Object[] level2VolumeIdsMapping = (Object[]) level2VolumeIdsMappingList.get(i);
					String level = (String) level2VolumeIdsMapping[0];
					String rollIds = (String) level2VolumeIdsMapping[1];
					ReplyChartDataItem item = this.constructChartDataXyAxis(level, rollIds,inspectionMode);
					chartDataList.add(item);
				}
			}
		}

		if (InspectionMode.SAMPLING == inspectionMode) {
			ReplyChartDataItem itemSampling = constructChartDataXyAxis4SamplingNotSelected(taskStateBean.getTaskId());
			chartDataList.add(itemSampling);
		}

		dataCollector.add(new ReplyDefectChart(this.moduleName, chartDataList));
	}

	/**
	 * memo:子类扩展实现——根据taskId查询对应订单的卷数据 按level分组的sum分布;level作为图表的x轴,sum值作为y轴
	 */
	abstract protected List<Object> getXaxis2RollIdsMapping(Long taskId);

	/**
	 * memo:构造全检模式或抽检模式中level对应的卷数据汇总
	 */
	protected ReplyChartDataItem constructChartDataXyAxis(String level, String rollIds, InspectionMode inspectionMode){
		ReplyChartDataItem item = new ReplyChartDataItem();
		if ("0".equals(level) || "P".equals(level)) {
			item.setxAxis(CommonConstant.SHADING_WITHIN_TOLERANCE);
		} else {
			item.setxAxis(level);
		}

		List<Integer> rollIdList = StringSplitor.strToIntegerList(rollIds);
		String sqlFilter = " AND volumeId IN (" + StringUtils.strip(rollIdList.toString(), "[]") + ")";
		List<Object> queryResult = this.getModuleVerifiedLength(sqlFilter);
		String sumValue;
		if (queryResult.get(0) != null) {
			Object[] metersFull = (Object[]) queryResult.get(0);
			if (inspectionMode == InspectionMode.SAMPLING){
				sumValue= metersFull[1] == null ? null : metersFull[1].toString();
			} else {
				sumValue= metersFull[0] == null ? null : metersFull[0].toString();
			}
		} else {
			logger.warn("getRollVerifiedLength" + sqlFilter + " 返回空");
			sumValue= null;
		}
		item.setyAxis(sumValue);
		return item;
	}

	/**
	 * memo:获取不同模块的数据dao方法
	 */
	protected abstract List<Object> getModuleVerifiedLength(String sqlFilter);

	/**
	 * memo:构造抽检模式中未检卷的数据汇总:以N/A灰色块显示在柱状图中
	 */
	protected ReplyChartDataItem constructChartDataXyAxis4SamplingNotSelected(Long taskId){
		ReplyChartDataItem itemSampling = new ReplyChartDataItem();
		itemSampling.setxAxis(CommonConstant.INSPECT_RESULT_NA);

		String sqlFilter = " AND(samplingTag IS NULL OR samplingTag = 0) ";
		List<Object> packingList = this.getModuleVerifiedLength( sqlFilter);
		if (packingList != null && packingList.get(0) != null) {
			Object[] metersSampling = (Object[]) packingList.get(0);
			itemSampling.setyAxis(metersSampling[1].toString());
		}
		return itemSampling;
	}

	/*
	 * memo:判断不同模块是否适用
	 */
	abstract protected boolean isDiffType4Adequacy(FbaTaskStateBean taskStateBean);
}
public static List<Object> getShadingDataItem(Long taskId, InspectionMode inspectionMode) {
    	logger.info("taskId:"+taskId);
    	//初始化dao
        List<Object> data = new ArrayList<>();
        FbaTaskStateBean taskStateBean = new FbaTaskStateBean();
        taskStateBean = DaoService.getFbaTaskStateDao().loadByTaskId(taskId);
        /**
         * 获取头尾差等级和检验米数
         */
        new ReplyChartDataRenderTemplate(CommonConstant.BEGIN_END){
            FbaHeadTailDifferenceDao headTailDao = DaoService.getFbaHeadTailDifferenceDao();
            @Override
            protected List<Object> getXaxis2RollIdsMapping(Long taskId) {
                return headTailDao.getHeadLevelAndVolumeIds(taskId, "");
            }

            @Override
            protected List<Object> getModuleVerifiedLength(String sqlFilter) {
                return headTailDao.getVerifiedLength(taskId,sqlFilter);
            }

            @Override
            protected boolean isDiffType4Adequacy(FbaTaskStateBean taskStateBean) {
                return "adequacy".equals(taskStateBean.getHeadTailType());
            }
        }.prepareChartData(taskStateBean, data, inspectionMode);
        /**
         * 获取边中差等级和检验米数
         */
        new ReplyChartDataRenderTemplate(CommonConstant.SIDE_TO_SIDE){
            FbaSideMiddleDifferenceDao sideMiddleDao = DaoService.getFbaSideMiddleDifferenceDao();

            @Override
            protected List<Object> getXaxis2RollIdsMapping(Long taskId) {
                return sideMiddleDao.getSideLevelAndVolumeIds(taskId,"");
            }

            @Override
            protected List<Object> getModuleVerifiedLength(String sqlFilter) {
                return sideMiddleDao.getSideVerifiedLength(taskId,sqlFilter);
            }

            @Override
            protected boolean isDiffType4Adequacy(FbaTaskStateBean taskStateBean) {
                return "adequacy".equals(taskStateBean.getSideMiddleType());
            }
        }.prepareChartData(taskStateBean,data,inspectionMode);
		/**
		 * memo:获取匹差等级和检验米数
		 */
		new ReplyChartDataRenderTemplate(CommonConstant.ROLL_TO_ROLL) {
			FbaRollDifferenceDao rollDifferenceDao = DaoService.getFbaRollDifferenceDao();

			@Override
			protected List<Object> getXaxis2RollIdsMapping(Long taskId) {
				return rollDifferenceDao.getRollLevelAndVolumeIds(taskId, "");
			}

            @Override
            protected List<Object> getModuleVerifiedLength(String sqlFilter) {
                return rollDifferenceDao.getRollVerifiedLength(taskId,sqlFilter);
            }

            @Override
            protected boolean isDiffType4Adequacy(FbaTaskStateBean taskStateBean) {
                return "adequacy".equals(taskStateBean.getRollDifferenceType());
            }
        }.prepareChartData(taskStateBean, data, inspectionMode);
        /**
         * 获取缸差等级和检验米数
         */
        new ReplyChartDataRenderTemplate(CommonConstant.COMPREF){
            FbaSampleDifferencePicDao sampleDifferencePicDao = DaoService.getFbaSampleDifferencePicDao();
            FbaRollDifferenceDivideDyelotDao rollDivideDao = DaoService.getFbaRollDifferenceDivideDyelotDao();

            @Override
            protected List<Object> getXaxis2RollIdsMapping(Long taskId) {
                List<Object> resultDyelot  = sampleDifferencePicDao.getDyelotLevelAndDyelots(taskId,"");
                List<Object> resultVolumeList = new ArrayList<>();
                for (int i = 0; i < resultDyelot.size();i++){
                    Object[] list = (Object[]) resultDyelot.get(i);
                    String dyelotIds = (String) list[1];
                    List<Integer> dyelotIdList = StringSplitor.strToIntegerList(dyelotIds);
                    List<String> volumeIdList = rollDivideDao.getVolumeIdByDyelotList(taskId, dyelotIdList);
                    if (volumeIdList != null && volumeIdList.size()>0){
                        String volumeIdStr = volumeIdList.get(0);
                        resultVolumeList.add(new Object[]{list[0],volumeIdStr});
                    }
                }
                return resultVolumeList;
            }

            @Override
            protected List<Object> getModuleVerifiedLength(String sqlFilter) {
                return rollDivideDao.getDyelotVerifiedLength(taskId, sqlFilter);
            }

            @Override
            protected boolean isDiffType4Adequacy(FbaTaskStateBean taskStateBean) {
                return "adequacy".equals(taskStateBean.getSampleContrastType());
            }
        }.prepareChartData(taskStateBean, data, inspectionMode);
        return data;
    }

个人感觉这个设计模式在实际工作中挺实用的,能避免很多重复的冗余代码,提高代码的整洁度;