1. 程式人生 > 實用技巧 >JAVA設計模式之--模板方法模式

JAVA設計模式之--模板方法模式

序言

在現實生活中,很多事情都包含幾個實現步驟,例如請客吃飯,無論吃什麼,一般都包含點單、吃東西、買單等幾個步驟,通常情況下這幾個步驟的次序是:點單 --> 吃東西 --> 買單。在這三個步驟中,點單和買單大同小異,最大的區別在於第二步——吃什麼?吃麵條和吃滿漢全席可大不相同

在軟體開發中,有時也會遇到類似的情況,某個方法的實現需要多個步驟(類似“請客”),其中有些步驟是固定的(類似“點單”和“買單”),而有些步驟並不固定,存在可變性(類似“吃東西”)。為了提高程式碼的複用性和系統的靈活性,可以使用一種稱之為模板方法模式的設計模式來對這類情況進行設計,在模板方法模式中,將實現功能的每一個步驟所對應的方法稱為基本方法(例如“點單”、“吃東西”和“買單”),而呼叫這些基本方法同時定義基本方法的執行次序的方法稱為模板方法(例如“請客”)。在模板方法模式中,可以將相同的程式碼放在父類中,例如將模板方法“請客”以及基本方法“點單”和“買單”的實現放在父類中,而對於基本方法“吃東西”,在父類中只做一個宣告,將其具體實現放在不同的子類中,在一個子類中提供“吃麵條”的實現,而另一個子類提供“吃滿漢全席”的實現。通過使用模板方法模式,一方面提高了程式碼的複用性,另一方面還可以利用面向物件的多型性,在執行時選擇一種具體子類,實現完整的“請客”方法,提高系統的靈活性和可擴充套件性。

  • 以下介紹的模板方法模式將解決以上類似的問題。

模板方法模式的定義與特點

模板方法(Template Method)模式的定義如下:定義一個操作中的演算法骨架,而將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟,它是一種類行為型模式。

主要優點

  1. 它封裝了不變部分,擴充套件可變部分。
  2. 它把認為是不變部分的演算法封裝到父類中實現,而把可變部分演算法由子類繼承實現,便於子類繼續擴充套件。
  3. 它在父類中提取了公共的部分程式碼,便於程式碼複用。
  4. 部分方法是由子類實現的,因此子類可以通過擴充套件方式增加相應的功能,符合開閉原則。

相對來說主要缺點

  1. 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
  2. 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了程式碼閱讀的難度。

模板方法模式的結構與實現

  • 模板方法模式是一種基於繼承的程式碼複用技術,它是一種類行為型模式。
  1. 模板方法模式是結構最簡單的行為型設計模式,在其結構中只存在父類與子類之間的繼承關係。
  2. 通過使用模板方法模式,可以將一些複雜流程的實現步驟封裝在一系列基本方法中,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執行次序,而通過其子類來覆蓋某些步驟,從而使得相同的演算法框架可以有不同的執行結果。
  3. 模板方法模式提供了一個模板方法來定義演算法框架,而某些具體步驟的實現可以在其子類中完成。

模板方法模式的結構

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

(1) 抽象類(Abstract Class):負責給出一個演算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。這些方法的定義如下。

① 模板方法:定義了演算法的骨架,按某種順序呼叫其包含的基本方法。

② 基本方法:是整個演算法中的一個步驟,包含以下幾種型別。

抽象方法:在抽象類中申明,由具體子類實現。
具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。
  (2) 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的一個組成步驟。

模板方法模式的基本實現

//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() 來改變抽象父類中的執行結果,其結構圖如下圖所示。

程式碼實現:

//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;
}

個人感覺這個設計模式在實際工作中挺實用的,能避免很多重複的冗餘程式碼,提高程式碼的整潔度;