人人都會設計模式---模版方法模式--Template-Method

模版方法模式大綱

PS:轉載請註明出處 做者: TigerChain 地址: www.jianshu.com/p/6c6191a47… 本文出自 TigerChain 簡書 人人都會設計模式java

教程簡介git

  • 一、閱讀對象 本篇教程適合新手閱讀,老手直接略過
  • 二、教程難度 初級,本人水平有限,文章內容不免會出現問題,若是有問題歡迎指出,謝謝
  • 三、Demo 地址:Android Demo---github.com/githubchen0… 對應的 TemplateMethod Java Demo:github.com/githubchen0…

正文github

1、什麼是模版方法模式

一、生活中的模版方法模式

一、燒茶、煮咖啡算法

身爲苦逼的程序猿(媛),必定是茶葉和咖啡的忠實粉絲,多少個夜晚加班加點,累了困了喝紅牛---不對是喝茶葉、咖啡「咱們無形中使用了一個設計模式--模版方法模式」。咱們知道無論是燒茶、煮咖啡都基本上分爲如下幾個步驟:數據庫

  • 一、燒水
  • 二、把茶葉或咖啡放入水壺中
  • 三、加熱不停的煮
  • 四、把煮好的茶葉或咖啡到入杯子中
  • 五、拿起杯子喝「不能直接喝,當心你的嘴」

咱們看到除了原材料放入的不一樣「茶葉和咖啡」,其它的方法都一毛同樣,那麼咱們把這些方法就能夠制定爲一個模版「至關於咱們有一個既能燒茶又有煮咖啡的器具」,這就是模版定義了一個基本框架canvas

二、高考答題設計模式

說了上面的例子,你們可能還懵懵的。那麼來講一個更實際的例子,參加過考慮的同窗都知道一張考試卷子對全部的同窗都是如出一轍的,這個卷子就是一個模版,以數學卷子爲例吧:有選擇題、填空題、判斷題、應用題「這都是固定的」--這就是一個題的框架,是一個模版,至於每位考生如何答題那就考生的事情app

二、程序中的模版方法模式

模版方法模式的定義框架

定義一個操做算法的骨架「咱們知道這個算法所須要的關鍵步驟」,而將一些步驟的實現延遲到子類中去實現。通俗的說模版就是一個抽象類,方法就是策略「一些固定的步驟」。模版方法模式是一個行爲型模式ide

模版方法模式的特色

算法的結構不變,子類能夠改變模版的某些步驟的實現方式,模版方法就是抽象的封裝,通常狀況下,模版方法中有一些具體方法「部分邏輯」,抽象方法實現其它剩餘的邏輯「子類不一樣實現的方式就不一樣」,而且部分邏輯和剩餘邏輯共同組成了算法的結構「通常是執行流程,這些流程是固定的,可是具體的實現細節不同,就可使用模版方法」

封裝不變的部分,擴展可變的部分「可變的部分交給子類去實現」

模版方法模式的目的

模版方法模式的目的就是讓子類擴展或者具體實現模版中的固定算法的中的某些算法的步驟

模版方法簡單框架

模版方法模式的結構

角色 類別 說明
AbstractClass 抽象類 抽象模版類
ConcreateClass 具體模版 能夠有多個「由於每一個具體模版實現的內容可能不同」
HookMethod 鉤子方法 不是必須的,是一個開關,用來提供某些方法是否須要調用

模版方法模式簡單的 UML

模版方法模式簡單的 UML

2、模版方法模式舉例

一、把大象裝冰箱

把大象裝冰箱一共分爲幾步?咱們都知道三步:第一步把冰箱門打開,第二步把大象裝進去,第三步把冰箱門蓋上。咱們把裝大象的這三步運做能夠看作一個算法的步驟「這個步驟不變」,可是具體的你是使用松下冰箱裝大象,仍是海爾冰箱裝大象,再進一步說使用冰箱裝全部動物,大象只是其中的一種,那麼就須要抽象出一個模版來,咱們使用模版方法模式來實現這一過程

把大象裝冰箱簡單的 UML

把大象裝冰箱簡單的 UML

根據 UML 擼碼

  • 一、抽象出一個冰箱接口 IRefrige.java
/** * Created by TigerChain * 抽象冰箱 */
public interface IRefrige {
    //取得品牌的名字
    String getRefrigeModel() ;
    //設置冰箱品牌
    void setModel(String model) ;
}
複製代碼
  • 二、抽象一個動物類 Animail.java
/** * Created by TigerChain * 定義動物的抽象類 */
public abstract class Animal {
    // 取得動物的名字
    abstract String getAnimailName() ;
}
複製代碼
  • 三、定義抽象模版方法類 AbstractMothodWork.java
/** * Created by TigerChain * 抽象的模版類 */
public abstract class AbstractMothodWork {
    //打開冰箱
    abstract void open(IRefrige iRefrige) ;
    //把動物裝進去
    abstract void putin(Animail animail) ;
    //把冰箱門蓋上
    abstract void close() ;

    // 模版方法 定義算法骨架 爲了防止子類篡改模版方法步驟,加一個 final
    public final void handle(IRefrige iRefrige,Animail animal){
        this.open(iRefrige); //第一步
        this.putin(animail); //第二步
        this.close();        //第三步
    }
}
複製代碼

咱們看到冰箱裝動物的步驟是固定的,可是具體步驟內部實現交給子類去處理吧,這就是模版模式的使用場景

  • 四、來一個具體的模版 ConcreateMethodWork.java
/** * Created by TigerChain * 具體的模版類 */
public class ConcreateMethodWork extends AbstractMothodWork {

    private IRefrige iRefrige ;
    private Animail animal ;

    @Override
    void open(IRefrige iRefrige) {
        this.iRefrige = iRefrige ;
        System.out.println("第 1 步把 "+iRefrige.getRefrigeModel()+" 門打開");
    }

    @Override
    void putin(Animail animail) {
        this.animail = animal ;
        System.out.println("第 2 步把 "+animail.getAnimailName()+" 裝進去");
    }

    @Override
    void close() {
        System.out.println("第 3 步把冰箱門蓋上");
    }
}
複製代碼
  • 五、來一個松下冰箱「固然能夠是任意品牌的冰箱」 PanasonnicRefrige.java
/** * Created by TigerChain * 定義一臺松下冰箱 */
public class PanasonnicRefrige implements IRefrige {

    private String model ;
    @Override
    public String getRefrigeModel() {
        return this.model!=null?this.model:"";
    }

    @Override
    public void setModel(String model) {
        this.model = model ;
    }
}
複製代碼
  • 六、被裝的對象大象「固然還能夠任何動物」 Elephant.java
/** * Created by TigerChain * 建立一個動物--大象 */
public class Elephant extends Animal {

    @Override
    String getAnimailName() {
        return "大象";
    }
}
複製代碼
  • 七、測試一下 Test.java
/** * Created by TigerChain * 測試類 */
public class Test {
    public static void main(String args[]){
        // 要有冰箱
        IRefrige panasonnicRefrige = new PanasonnicRefrige() ;
        panasonnicRefrige.setModel("松下冰箱");

        // 要有動物,這裏是裝大象
        Animail elephant = new Elephant() ;

        //來個模版
        AbstractMothodWork  work = new ConcreateMethodWork() ;
		// 執行步驟
        work.handle(panasonnicRefrige,elephant);
    }
}
複製代碼
  • 八、運行查看結果

把你們裝冰箱的結果

到此爲止,咱們就把大象裝到冰箱裏面了,固然你也能夠把老虎、狼、貓裝進冰箱「擴展模版便可」,其實咱們使用回調也能夠實現一樣的功能「本質上是模版方法的一種變異---是什麼?仍是模版方法模式」,咱們使用回調方式修改上面代碼「不破壞原來的結構,咱們直接新加類」

  • 九、咱們把想要擴展的方法所有抽象成接口,定義 ITemplate.java

抽象接口

  • 十、定義具體的模版 ConCreateTemplate.java 因爲咱們把想要擴展的模版方法都抽象出來了,因此咱們新建模版的時候就不用抽象了「即定義具體的模版就能夠了」

定義具體的模版

  • 十一、修改測試類 Test.java 只貼出調用代碼

修改測試類

運行結果和 8 中的結果是同樣的,這裏就不貼圖了,具體的代碼能夠看:github.com/githubchen0…

二、數據庫增、刪、改、查封裝

操做過數據庫的朋友對數據的增、刪、改、查再熟悉不過了,數據庫無非就是幹這個的。那麼咱們可使用模版方法模式把數據庫的增、刪、改、查封裝,至於查什麼,改什麼,交給具體的模版吧,典型的模版方法模式

數據庫增、刪、改、查 簡單的 UML

數據庫增、刪、改、查 簡單的 UML

根據 UML 擼碼

  • 一、定義抽象的模版--數據庫增、刪、改查抽象類 AbstractDAO.java
/** * Created by TigerChain * 定義抽象的數據庫增、刪、改、查的模版 */
public abstract class AbstractDAO<T> {
    // 增長數據
    abstract void add(T t) ;
    // 根據 id 刪除數據
    abstract void delete(int id) ;
    // 更新數據
    abstract void update(T t) ;
    // 根據 id 查找數據
    abstract T findById(int id);
    // 查找全部數據
    abstract List<T> findall() ;
}
複製代碼

咱們這裏以泛型去接收實體類,至於查那個交給子類去實現--這樣就把共同點抽象出來了

  • 二、咱們操做用戶表吧,定義一個 Person.java
/** * Created by TigerChain * 定義一個 JavaBean 對應數據庫中的表 */
public class Person {
    private int id ;         // id
    private String name ;    // 姓名
    private int age ;        // 年齡
    private String address ; // 地址
    // 省略 setter 和 getter 方法
    ...   
}
複製代碼
  • 三、定義一個操做用戶表的 DAO PersonConCreateDAO.java
**
 * Created by TigerChain
 * 一個具體的模版對用戶表的增、刪、改、查
 */
public class PersonConCreateDAO extends AbstractDAO<Person> {
    // 庫中的用戶列表
    private List<Person> persons = new ArrayList<>() ;

    @Override
    void add(Person person) {
        // 實際上應該作插入數據庫操做,爲了簡單咱們直接輸出語句
        persons.add(person) ;
        System.out.println("添加了 person "+person.toString());
    }

    @Override
    void delete(int id) {
        System.out.println("刪除了 id 爲 "+id+" person "+persons.get(id-1));
        persons.remove(id-1) ;
    }

    @Override
    void update(Person person) {
        person.setId(1);
        person.setName("TigerChain");
        person.setAge(30);
        person.setAddress("中國陝西西安");

        System.out.println("更新了 person "+person.toString());
    }

    @Override
    Person findById(int id) {
        // 實際這裏應該從數據庫中查出數據,爲了簡單模擬一個數據
        Person person = new Person() ;
        if(id ==1){
            person.setId(1);
            person.setName("TigerChain");
            person.setAge(28);
            person.setAddress("中國陝西");
        }
        System.out.println("查找id 爲 "+id+" 的 person "+person.toString());
        return person;
    }

    @Override
    List<Person> findall() {
        System.out.println("查找全部的 person "+ persons.toString());
        return persons;
    }
}
複製代碼
  • 四、測試一下 Test.java
public class Test {
    public static void main(String args[]){
        // 模擬兩個用戶數據
        Person person1 = new Person() ;
        person1.setId(1);
        person1.setName("TigerChain");
        person1.setAge(28);
        person1.setAddress("中國陝西");

        Person person2 = new Person() ;
        person2.setId(2);
        person2.setName("小陳");
        person2.setAge(30);
        person2.setAddress("中國陝西西安");

        PersonConCreateDAO personConCreateDAO = new PersonConCreateDAO() ;

        // 給庫中添加用戶
        personConCreateDAO.add(person1);
        personConCreateDAO.add(person2);

        // 更新用戶 1 的數據
        personConCreateDAO.update(person1);
        personConCreateDAO.findById(1);
        personConCreateDAO.findall() ;

        // 刪除一條數據
        personConCreateDAO.delete(1);
        // 查找全部庫中的數據
        personConCreateDAO.findall() ;

    }
}
複製代碼
  • 五、運行查看結果

運行結果

至此,咱們就使用模版方法模式實現了數據庫的增、刪、改、查、功能,至於你想操道別的表那直接寫一個具體的模版繼承抽象模版便可,你們動手寫一下,好好的體驗一下模版方法模式

三、考試答題

考試卷對每一個考生來講都是同樣的「考試卷就是一個模版」,至於每一個人如何答題那是每一個考生的事情,針對考試答題咱們可使用模版方法模式來模擬這一過程,代碼就不貼了「我上傳到了 github 上」,具體看這裏:github.com/githubchen0…

**PS:**模版方法模式除了抽象模版、具體模版以外,還可能會有一個鉤子方法「Hook」,也就是說抽象模版中把規定好了算法的步驟 1 2 3 4 ,若是我只想使用 1 2 3 ,不想使用 4 呢?Hook 方法就派上用場了,如下是抽象模版帶 Hook 的僞代碼

public abstract class AbstractTemplateMethod{

	abstract void step1() ;
	abstract void step2() ;
	abstract void step3() ;
	abstract void step4() ;
    void step5() ;
	// 模版方法
    public final void execute(){
		
	 this.step1() ;
     this.step2() ;	
     this.step3() ;
	 if(isUseStep4()){
       this.step4() ;
     }
     this.step5() ;
	}
    // 鉤子方法
    protected boolean isUseStep4(){
		return false ;
    }
}
複製代碼

子類重寫 isUseStep4() 的方法返回 true 或 fals 決定是否使用 step4 步驟,這就是鉤子方法,你們自行感覺一下,其實就是一個開關而已

3、Android 源碼中的模版方法模式

一、View 中的 draw(Canvas canvas) 方法

咱們自定義 View 的時候有時調用 ondraw(Canvas canvas) 方法,這裏就用到了模版方法模式,咱們來看一下 ondraw 在什麼狀況下調用「在 View 的 draw() 方法中調用了」,看看 draw() 方法的核心代碼

draw 核心代碼

這裏只不過把抽象方法改爲了 protected 的一個空方法而已「本質上是同樣的」,具體代理就不貼了,你們動手扒扒這部分源碼,其實模版方法模式咱們常常用「只不過沒有意識到而已」

二、最熟悉的 Activity

Activity 就是一個模版,其中生命週期的方法就是"不固定"的方法,若是要改變子類重寫便可

public class Activity extends ApplicationContext {
      protected void onCreate(Bundle savedInstanceState);
 
      protected void onStart();
 
      protected void onRestart();
 
      protected void onResume();
 
      protected void onPause();
 
      protected void onStop();
 
      protected void onDestroy();
}
複製代碼

這裏定義成 protected 方法,那麼這個方法既能夠是固定的也能夠是不固定的「子類實現就不固定,若是不實現就是固定的,很靈活」,Activity 就是一個模版方法模式「你每天使用 Activity 知道它是模版模式嗎?」

三、封裝 BaseActivity

作過 Android 的朋友確定都封裝過 BaseActivity ,把一些共公的部分抽象出來,而後封裝變化,好比咱們的 app 應用界面都有共公的頭、下面是內容區域,以下圖

封裝 BaseActivity

而後不一樣的界面寫不一樣的子類繼承便可,咱們使用僞代碼來模擬一下

public abstract class TemplateMethodActivity extends AppCompatActivity {
    private Button titlebar_btn_left,titlebar_btn_right ;// 左右按鈕 
    private TextView titlebar_tv_center ; // 中間文字
    private RelativeLayout content ;      // 內容佈局

    private View titleView ;
	 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		// 加載模版 xml 文件
        setContentView(R.layout.templatemethod_activity);
        initView() ;
		// 設置子類佈局
        setContentLayout(getLayoutResID());
        getLayoutResID() ;
        
        init() ;
    }
	// 初始化操做,好比修改按鈕樣式等
    protected abstract void init();
    // 取得子佈局的 xml 文件
    protected abstract int getLayoutResID();
	// 設置到 content 佈局上
    private void setContentLayout(int ResId) {
        LayoutInflater.from(this).inflate(ResId, content);
    }
    // 省略若干方法
}
複製代碼

而後子類繼承這個 Activity 重寫抽象方法便可實現本身的界面內容,咱們寫一個登陸界面繼承 TemplateMethodActivity ,代碼不貼了,直接上地址:github.com/githubchen0… 查看 TemplateMethod 相關代碼便可

最終運行效果以下:

模版方法模式實現 BaseActivity 結果

怎麼樣,是否是一直在使用模版方法模式「只是不知道而已」

4、模版方法模式的優缺點

優勢

  • 一、封裝不變的部分,擴展可變的部分「交給子類去實現」,這是設計模式一慣的原則「開、閉原則」
  • 二、實現了代碼複用

缺點

  • 繼承關係自己的缺點,若是父類添加一個新的抽象方法,全部的子類都要改一遍--痛苦

5、模版方法模式 VS 策略模式

之前介紹過策略模式,是對算法的封裝,而模版方法模式也是對算法執行,可是它們之間有明顯的區別

  • **策略模式:**目的是使不一樣的算法能夠被相互替換,不影響客戶端的使用

  • **模版方法模式:**針對定義一個算法的流程,而將一些不太同樣的「具體實現步驟」交給子類去實現「不改變算法的流程」

6、總結

  • 一、抽象類就是一個模版,而接口是一個標準,按這樣的規則能夠肯定該使用接口仍是抽象類
  • 二、模版方法模式就是把不固定的步驟實現方式延遲到子類實現的一種方式,它是一種行爲模式
  • 三、模版方法模式基本步驟是固定的「實際開發中會有不少變種,好比回調替換,沒有固定的步驟全是不固定的等」
  • 四、 通常狀況下爲了防止子類去更新算法的實現步驟,在抽象的模版方法上加一個 final 關鍵字

到上爲止,模版方法模式就介紹完了,仍是那句話,必定要動手試試哦,關注博主,更多精彩內容等着你,手把手教你學會知識點

公衆號:TigerChain 歡迎你們關注--更多精彩內容等着你

TigerChain
相關文章
相關標籤/搜索