摘要
摘要: 由於業務場景復雜,一個算法需要開發行為變化多端的多個實現類,然后在系統運行時根據不同場景裝載不同的類實例。為了使源碼具有更好的可擴展性和可重用性,在借鑒前人處理方法的基礎上,介紹在Spring項目中,基於模板方法模式介紹一個接口被多個實現類實現時,Spring框架怎樣從容器中正確取出我們想要的實例。
前言
除了《Spring注解之@Autowired:按類型自動裝配Bean到數組、集合和Map》介紹的幾種注入bean方法之外,還有其它方法,本文紹如何使用模板方法模式裝配 bean。
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述模板方法模式(Template Method Pattern)的:
模板方法模式是類的行為模式。准備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然后聲明一些抽象方法來迫使子類實現剩余的邏輯。不同的子類可以以不同的方式實現這些抽象方法(類的多態實現),從而對剩余的邏輯有不同的實現。這就是模板方法模式的用意。
欲了解更多相關知識點請移步《Spring 動態綁定多實現類實例綜述》。
業務場景回顧
需求描述:定制一個繪圖工具,她根據客戶端發送的指令可以畫出正方形、矩形、圓形和三角形等各種各樣的幾何圖形。例如,當客戶端需要繪制三角形的時候,就調用繪制三角形的方法;當需要繪制圓形的時候,就調用繪制圓形的方法。
模板方法模式中的方法
模板方法中的方法可以分為兩大類:模板方法和基本方法。
模板方法
一個模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總算法或一個總行為的方法。
一個抽象類可以有任意多個模板方法,而不限於一個。每一個模板方法都可以調用任意多個具體方法。
基本方法
基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鈎子方法(Hook Method)。
● 抽象方法:一個抽象方法由抽象類聲明,由具體子類實現。在Java語言里抽象方法由關鍵字abstract修飾。
● 具體方法:一個具體方法由抽象類聲明並實現,而子類並不實現或覆蓋。
● 鈎子方法:一個鈎子方法由抽象類聲明,而子類負責實現。
public abstract class ShapeTem {
//模板方法
public final void getTime() {
long start = System.currentTimeMillis();
draw();
System.out.println("耗時 " + (System.currentTimeMillis() - start) + " 毫秒");
}
/**
* 畫圖實現方法
*/
public abstract void draw();
/**
* 基本方法(空方法)<br/>
* 檢驗實現類是否支持 beanName 指定的畫圖方式
* @param beanName 畫圖工具具體實現類的 bean 名稱
* @return
*/
public abstract Boolean hook(String beanName);
}
抽象類ShapeTem中的模板方法實際上是提供了一個外部可訪問接口,使得外部環境由該接口獲得服務;之所以為模板方法添加修飾符 final,是因為這樣它就不會被重寫,避免被惡意操作。在每個具體模板角色類中實現抽象類所聲明的兩個基本方法,下面以畫三角形和長方形為例進行說明,圓形和正方形等的實現類請自行添加:
@Component
public class TriangleTem extends ShapeTem {
/**
* 畫圖實現方法
*/
@Override
public void draw() {
System.out.println("Inside Triangle::draw() method.");
}
/**
* 基本方法<br/>
* 檢驗實現類是否支持 beanName 指定的畫圖方式
*
* @param beanName 畫圖工具具體實現類的 bean 名稱
* @return
*/
@Override
public Boolean hook(String beanName) {
return "triangleTem".equals(beanName);
}
}
@Component
public class RectangleTem extends ShapeTem {
/**
* 畫圖實現方法
*/
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 基本方法<br/>
* 檢驗實現類是否支持 beanName 指定的畫圖方式
*
* @param beanName 畫圖工具具體實現類的 bean 名稱
* @return
*/
@Override
public Boolean hook(String beanName) {
return "rectangleTem".equals(beanName);
}
}
客戶端可根據需要使用不同的模板實現。新增 ShapeTemplate 類,通過 applicationContext 獲取接口不同實現類的bean,並由方法 toDraw(String beanName)根據beanName確認畫圖模板,最終找到畫圖的具體方法。
import com.eg.wiener.service.ShapeTem;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 模板方法模式
*
* @author Wiener
* @date 2021/1/18
*/
@Component
public class ShapeTemplate implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
/**
* 1610543593
* 通過 applicationContext 獲取抽象類的bean,和通過@Autowired注解的方式獲取接口實現類的bean有着異曲同工之妙
*/
private List<ShapeTem> shapeList = null;
@Autowired
private Map<String, ShapeTem> shapeTemMap;
@Override
public void afterPropertiesSet() throws Exception {
if (shapeList == null) {
shapeList = new ArrayList<>();
Map<String, ShapeTem> beansOfType = applicationContext.getBeansOfType(ShapeTem.class);
beansOfType.forEach((key, value) -> shapeList.add(value));
}
if (CollectionUtils.isEmpty(shapeList)) {
System.out.println("--------- 獲取抽象類的bean失敗 --------------");
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void toDraw(String beanName) {
System.out.println("通過 @Autowired 注解的方式獲取實現類:" + shapeTemMap);
System.out.println("通過 ApplicationContext 的方式獲取實現類:" + shapeList);
for (ShapeTem oneShape : shapeList) {
if (oneShape.hook(beanName)) {
oneShape.getTime();
return;
}
}
System.out.println("beanName非法!");
}
}
有了上面的代碼之后,我們在控制層創建測試用例來看一下效果,測試代碼片段如下:
/**
* 由模板方法模式實現
* @param code
* @return
*/
@GetMapping("/drawByTemplate")
public String drawByTemplate(String code) {
shapeTemplate.toDraw(code);
return "由模板方法模式實現成功";
}
執行結果:
通過 @Autowired 注解的方式獲取實現類:{rectangleTem=com.eg.wiener.service.impl.RectangleTem@1f6ce7aa, triangleTem=com.eg.wiener.service.impl.TriangleTem@11ab1a37}
通過 ApplicationContext 的方式獲取實現類:[com.eg.wiener.service.impl.RectangleTem@1f6ce7aa, com.eg.wiener.service.impl.TriangleTem@11ab1a37]
Inside Rectangle::draw() method.
耗時 300 毫秒
服務端根據不同的請求參數code,調用不同的示例。
執行結果分析
- 通過兩種方式所得到的實現類的bean一模一樣,說明二者有着異曲同工之妙。但是,通過注解的方法獲取時,實現邏輯更精簡。
- 通過 applicationContext 獲取實現類的bean后,我們把這些bean放入集合shapeList。在客戶端畫圖的時候,如果傳入的參數beanName跟自己定義的一樣,則調用當前畫圖類實例的draw方法。
當增加一個新的實現類時,不要按照控制流程的思路去思考問題,而應當按照“責任”的思路去規划。換言之,應當考慮哪些操作是新的實現類必須置換掉的,哪些操作是可以置換掉的,以及哪些操作是不可以置換掉的。使用模板方法設計模式可以使這些責任變得清晰。
結束語
這篇文章主要向大家介紹如何使用模板方法模式實現多個接口,就算每個實現類的邏輯變化多端,只要執行流程是固定的,使用該模式都能讓我們的編碼效率、維護效率和擴展效率更上一層樓。
應用實例 做試卷時,考生題目都是一樣的,只是答案千差萬別。