15.模版模式設計思想

杨充發表於2024-11-20

15.模版模式設計思想

目錄介紹
  • 01.模版模式基礎

    • 1.1 模版模式由來
    • 1.2 模版模式定義
    • 1.3 模版模式場景
    • 1.4 模版模式思考
    • 1.5 模版模式特點
    • 1.6 理解模版唯一性
    • 1.7 主要解決問題
  • 02.模版模式原理

    • 2.1 羅列一個場景
    • 2.2 用例子理解模版
    • 2.3 需求普通實現
    • 2.4 案例演變實現
    • 2.5 模版模式實現步驟
  • 03.模版模式結構

    • 3.1 模版標準案例
    • 3.2 模版模式結構
    • 3.3 模版模式時序圖
  • 04.模版模式案例分析

    • 4.1 角色分析
    • 4.2 擴充一個場景
    • 4.3 模版模式實踐
  • 05.模版者模式分析

    • 5.1 模版模式優點
    • 5.2 模版模式缺點
    • 5.3 使用建議說明
    • 5.4 思考題考察
  • 06.觀察者模式總結

    • 6.1 總結一下學習
    • 6.2 更多內容推薦

推薦一個好玩網站

一個最純粹的技術分享網站,打造精品技術程式設計專欄!程式設計進階網

https://yccoding.com/

關於設計模式,所有的程式碼都放到了該專案。設計模式大全

01.模版模式基礎介紹

1.0 本部落格AI摘要

模版模式是一種行為設計模式,它定義了一個操作中的演算法骨架,而將一些步驟延遲到子類中實現。這種方式讓子類可以在不改變演算法結構的情況下重新定義演算法的某些特定步驟。文章詳細介紹了模版模式的基礎概念、應用場景、實現原理及優缺點,並透過具體案例深入解析了模版模式的使用方法。適合初學者和有一定經驗的開發者深入學習。

1.1 模版模式由來

在軟體構建過程中,對於某一項任務,它常常有穩定的整體操作結構,但各個子步驟卻有很多改變的需求,或者由於固有的原因(比如框架與應用之間的關係)而無法和任務的整體結構同時實現。

如何在確定穩定操作結構的前提下,來靈活應對各個子步驟的變化或者晚期實現需求?如何保證架構邏輯的正常執行,而不被子類破壞 ?這個時候可以用模版模式!

1.2 模版模式定義

模板方法模式是類的行為模式。

模板模式(Template)將定義的演算法抽象成一組步驟,在抽象類種定義演算法的骨架,把具體的操作留給子類來實現。

模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

1.3 模版模式場景

常見模板模式運用場景如下:

  1. Java Servlet:HttpServlet 就是使用了模板模式。HttpServlet 類定義了 service() 方法作為模板方法,子類需要實現 doGet()、doPost() 等方法來處理具體的 HTTP 請求。
  2. Spring Framework:例如,JdbcTemplate 使用了模板模式來簡化 JDBC 資料訪問程式碼,定義了模板方法來執行資料庫操作,具體的 SQL 語句和引數由子類提供。

總結:當存在一些通用的方法,可以在多個子類中共用時。

1.4 模版模式思考

實際開發中常常會遇到,程式碼骨架類似甚至相同,只是具體的實現不一樣的場景。

例如:休假流程都有開啟、編輯、駁回、結束。共享單車都是先開鎖、騎行、上鎖、付款。每個流程都包含這幾個步驟,不同的是不同的流程例項它們的內容不一樣。你該如何設計?

1.5 模版模式特點

模板模式的主要特點包括:

  1. 定義一個演算法的骨架,將一些步驟留給子類實現。
  2. 允許子類在不改變演算法結構的基礎上,重新定義某些步驟。
  3. 適用於處理包含一系列基本相同步驟的過程,但某些步驟可能有不同的實現。

1.6 主要解決問題

解決在多個子類中重複實現相同的方法的問題,透過將通用方法抽象到父類中來避免程式碼重複。

02.模版模式原理

2.1 羅列一個場景

以生活中買菜做飯的例子來寫個Demo,燒飯一般都是買菜、洗菜、烹飪、裝盤四大過程。中國自古有八大菜系,製作方式肯定都避不開這四個過程。那在模板模式中如何實現呢?

2.2 用例子理解模版

這些大的步驟固定,不同的是每個例項的具體實現細節不一樣。這些類似的業務我們都可以使用模板模式實現。

2.3 需求普通實現

關於實現買菜做飯,做川菜和徽菜。最原始的實現如下所示:

private void cook() {
    System.out.println("----------川菜製作------------");
    Cooking chuanCaiService = new Cooking(0);
    chuanCaiService.process();
    System.out.println("-----------徽菜製作-----------");
    Cooking huiCaiService = new Cooking(1);
    huiCaiService.process();
}

public class Cooking {

    //type是0表示川菜
    //type是1表示徽菜
    private int type;
    public Cooking(int type) {
        this.type = type;
    }

    public void process() {
        shopping();
        wash();
        cooking();
        dishedUp();
    }

    protected void shopping() {
        if (type == 0) {
            System.out.println("買菜:黑豬肉一斤,蒜頭5個");
        } else if (type == 1){
            System.out.println("買菜:新鮮魚一條,紅辣椒五兩");
        }

    }

    protected void wash() {
        if (type == 0) {
            System.out.println("清洗:豬肉洗淨,蒜頭去皮");
        } else if (type == 1){
            System.out.println("清洗:紅椒洗淨切片,魚頭半分");
        }

    }

    protected void cooking() {
        if (type == 0) {
            System.out.println("烹飪:大火翻炒,慢火悶油");
        } else if (type == 1){
            System.out.println("裝盤:用長形盤子裝盛");
        }
    }

    protected void dishedUp() {
        if (type == 0) {
            System.out.println("裝盤:深碗盛起,熱油澆拌");
        } else if (type == 1){
            System.out.println("烹飪:魚頭水蒸,辣椒過油");
        }
    }
}

你有沒有發現這樣寫,要是後期在擴充一個魯菜,粵菜。這樣就會修改原始碼,會破壞程式碼的開閉原則。或者我想修改一下不同菜系的做菜步驟,那就導致程式碼非常難以修改。

2.4 案例演變實現

建立一個抽象類,它的模板方法被設定為 final。為防止惡意操作,一般模板方法都加上 final 關鍵詞。

public abstract class AbstractCookingService {
    //買菜
    protected abstract void shopping();
    //清洗
    protected abstract void wash();
    //烹飪
    protected abstract void cooking();
    //裝盤
    protected abstract void dishedUp();

    public final void process() {
        shopping();
        wash();
        cooking();
        dishedUp();
    }
}

建立實現了上述抽象類的子類。

/**
 * 徽菜製作大廚
 */
public class HuiCaiChef extends AbstractCookingService {

    @Override
    protected void shopping() {
        System.out.println("買菜:新鮮魚一條,紅辣椒五兩");
    }

    @Override
    protected void wash() {
        System.out.println("清洗:紅椒洗淨切片,魚頭半分");
    }

    @Override
    protected void cooking() {
        System.out.println("烹飪:魚頭水蒸,辣椒過油");
    }

    @Override
    protected void dishedUp() {
        System.out.println("裝盤:用長形盤子裝盛");
    }
}

/**
 * 川菜製作大廚
 */
public class ChuanCaiChef extends AbstractCookingService {

    @Override
    protected void shopping() {
        System.out.println("買菜:黑豬肉一斤,蒜頭5個");
    }

    @Override
    protected void wash() {
        System.out.println("清洗:豬肉洗淨,蒜頭去皮");
    }

    @Override
    protected void cooking() {
        System.out.println("烹飪:大火翻炒,慢火悶油");
    }

    @Override
    protected void dishedUp() {
        System.out.println("裝盤:深碗盛起,熱油澆拌");
    }
}

然後測試演示一下

private void test() {
    System.out.println("----------川菜製作------------");
    AbstractCookingService chuanCaiService = new ChuanCaiChef();
    chuanCaiService.process();
    System.out.println("-----------徽菜製作-----------");
    AbstractCookingService huiCaiService = new HuiCaiChef();
    huiCaiService.process();
}

在模板模式中,定義了一個演算法的框架,將演算法的具體步驟延遲到子類中實現。這種模式允許子類重寫演算法的特定步驟而不改變演算法的結構。

模板模式中通常包含兩個角類,一個模板類和一個具體類,模板類就是一個演算法框架。

2.5 模版模式實現步驟

模板模式也沒什麼高深莫測的,簡單來說就是三大步驟:

  1. 建立一個抽象類,定義幾個抽象方法和一個final修飾的模板方法,而模板方法中設定了抽象方法的執行順序或邏輯。
  2. 無論子類有多少個,只需要繼承該抽象類,實現父類的抽象方法重寫自己的業務。
  3. 根據不同的需求建立不同的子類實現,每次呼叫的地方只需呼叫模板方法,即可完成特定的模板流程。

03.模版模式結構

3.1 模版標準案例

3.2 模版模式結構

模板模式的主要角色包括:

  1. 抽象類(AbstractClass):負責實現模板方法,並宣告在模板方法中所使用的抽象方法。
  2. 具體類(ConcreteClass):實現抽象類中的抽象方法。
  3. 客戶端(Client):使用具體類繼承的模板方法。

3.3 模版模式時序圖

04.模版模式案例分析

4.1 角色分析

抽象類(Abstract):定義了演算法骨架,包含一個或多個抽象方法,這些方法由子類來具體實現。抽象類中通常還包含一個模板方法,用來呼叫抽象方法和具體方法,控制演算法的執行順序;還可以定義鉤子方法,用於在演算法中進行條件控制。

具體類(Concrete Class):繼承抽象類,實現抽象方法。

4.2 擴充一個場景

以訂外賣為例,解釋一下模板模式。假設訂外賣的過程包含三個步驟:選擇外賣、支付、取外賣、是否打賞。

我們可以定義一個OderFood的抽象類,那麼選擇外賣就可以是抽象方法,需要子類取實現它,支付和取外賣可以定義為具體方法,另外是否打賞為鉤子方法,子類可以決定是否對演算法的不同進行掛鉤。

還需要定義一個模板方法,用以控制流程;不同的商家,如KFC、星巴克就是具體類。

4.3 模版模式實踐

OrderFood——抽象類(Abstract)

/**
 * @author Created by njy on 2023/6/24
 * 1.抽象類(Abstract Class):點外賣
 * 包含選擇外賣、支付、取外賣三個方法,
 * 其中選擇外賣為抽象方法,需要子類實現
 * 支付、取外賣為具體方法
 * 另外是否打賞為鉤子方法,子類可以決定是否對演算法的不同進行掛鉤
 */
public abstract class OrderFood {

    //模板方法
    public void order(){
        selectFood();
        pay();
        getFood();
    }
    //選擇外賣   抽象方法 由子類實現具體細節
    public abstract void selectFood();
    //是否打賞   鉤子方法 可以重寫來做條件控制
    public boolean isGiveAward(){
        return false;
    }
    //-------具體方法----------
    public void pay(){
        System.out.println("支付成功,外賣小哥正在快馬加鞭~~");
    }

    //取外賣
    public void getFood(){
        System.out.println("取到外賣");
        if (isGiveAward()){
            System.out.println("打賞外賣小哥");
        }
    }
}

具體類(Concrete Class)

/**
 * 具體類(Concrete Class):星巴克
 */
public class Starbucks extends OrderFood{

    //實現父類方法
    @Override
    public void selectFood() {
        System.out.println("一杯抹茶拿鐵");
    }

    //重寫鉤子方法,打賞外賣小哥
    @Override
    public boolean isGiveAward(){
        return true;
    }
}

/**
 * 具體類(Concrete Class):KFC
 */
public class KFC extends OrderFood{
    @Override
    public void selectFood() {
        System.out.println("一份漢堡炸雞四件套");
    }
}

測試一下,程式碼如下:

//星巴克(重寫了鉤子方法,打賞外賣小哥)
OrderFood orderFood=new Starbucks();
orderFood.order();
System.out.println("--------KFC------------");
//KFC
OrderFood orderFood1=new KFC();
orderFood1.order();

05.模版者模式分析

5.1 模版模式優點

  1. 封裝不變部分:演算法的不變部分被封裝在父類中。
  2. 擴充套件可變部分:子類可以擴充套件或修改演算法的可變部分。
  3. 提取公共程式碼:減少程式碼重複,便於維護。

5.2 模版模式缺點

  1. 類數目增加:每個不同的實現都需要一個子類,可能導致系統龐大。

5.3 使用建議說明

  1. 當有多個子類共有的方法且邏輯相同時,考慮使用模板方法模式。
  2. 對於重要或複雜的方法,可以考慮作為模板方法定義在父類中。

注意事項:為了防止惡意修改,模板方法通常使用final關鍵字修飾,避免被子類重寫。

5.4 思考題考察

需求1: 實現字元列印和字串列印兩種不同的列印樣式。

分析和實現:定義一個抽象類AbstractDisplay其中包含模板方法display,兩個實現模板的具體類,CharDisplay和StringDisplay。具體可以看:TemplateDesign

需求2: 做菜的步驟一般是:洗鍋 --> 炒菜 --> 洗碗 ,不同的菜,只是炒菜這一個步驟具體細節是不同的。然後做一個煮糖醋鯉魚,小炒肉,你該如何實現?

分析和實現:整體流程是一樣的,有些步驟一樣,有些步驟不一樣,但是不使用模板模式,需要每個類都重寫一遍方法,這個時候可以用模版模式實現。具體可以看:TemplateDesign

06.觀察者模式總結

6.1 總結一下學習

  1. 定義:在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。
  2. 意圖:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟
  3. 主要解決:一些方法通用,卻在每一個子類都重新寫了這一方法。
  4. 何時使用:有一些通用的方法。
  5. 如何解決:將這些通用演算法抽象出來。
  6. 關鍵程式碼:在抽象類實現,其他步驟在子類實現。
  7. 優點: 1、封裝不變部分,擴充套件可變部分。 2、提取公共程式碼,便於維護。 3、行為由父類控制,子類實現。
  8. 缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
  9. 使用場景: 1、有多個子類共有的方法,且邏輯相同。 2、重要的、複雜的方法,可以考慮作為模板方法。

6.2 更多內容推薦

模組描述備註
GitHub多個YC系列開源專案,包含Android元件庫,以及多個案例GitHub
部落格彙總匯聚Java,Android,C/C++,網路協議,演算法,程式設計總結等YCBlogs
設計模式六大設計原則,23種設計模式,設計模式案例,物件導向思想設計模式
Java進階資料設計和原理,物件導向核心思想,IO,異常,執行緒和併發,JVMJava高階
網路協議網路實際案例,網路原理和分層,Https,網路請求,故障排查網路協議
計算機原理計算機組成結構,框架,儲存器,CPU設計,記憶體設計,指令程式設計原理,異常處理機制,IO操作和原理計算機基礎
學習C程式設計C語言入門級別系統全面的學習教程,學習三到四個綜合案例C程式設計
C++程式設計C++語言入門級別系統全面的教學教程,併發程式設計,核心原理C++程式設計
演算法實踐專欄,陣列,連結串列,棧,佇列,樹,雜湊,遞迴,查詢,排序等Leetcode
Android基礎入門,開源庫解讀,效能最佳化,Framework,方案設計Android

23種設計模式

23種設計模式 & 描述 & 核心作用包括
建立型模式
提供建立物件用例。能夠將軟體模組中物件的建立和物件的使用分離
工廠模式(Factory Pattern)
抽象工廠模式(Abstract Factory Pattern)
單例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
結構型模式
關注類和物件的組合。描述如何將類或者物件結合在一起形成更大的結構
介面卡模式(Adapter Pattern)
橋接模式(Bridge Pattern)
過濾器模式(Filter、Criteria Pattern)
組合模式(Composite Pattern)
裝飾器模式(Decorator Pattern)
外觀模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行為型模式
特別關注物件之間的通訊。主要解決的就是“類或物件之間的互動”問題
責任鏈模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
直譯器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
備忘錄模式(Memento Pattern)
觀察者模式(Observer Pattern)
狀態模式(State Pattern)
空物件模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
訪問者模式(Visitor Pattern)

6.3 更多內容

  • GitHub:https://github.com/yangchong211
  • 我的程式設計網站:https://yccoding.com
  • 部落格彙總:https://github.com/yangchong211/YCBlogs
  • 設計模式專欄:https://github.com/yangchong211/YCDesignBlog
  • Java高階進階專欄:https://github.com/yangchong211/YCJavaBlog
  • 網路協議專欄:https://github.com/yangchong211/YCNetwork
  • 計算機基礎原理專欄:https://github.com/yangchong211/YCComputerBlog

相關文章