08顶考、模版方法模式--Template-Method

本節(jié)大綱

PS:轉載請注明出處
作者: TigerChain
地址: http://www.reibang.com/p/6c6191a47197
本文出自 TigerChain 簡書 人人都會設計模式

教程簡介

正文

一泛豪、什么是模版方法模式

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

1侦鹏、燒茶诡曙、煮咖啡

身為苦逼的程序猿(媛),一定是茶葉和咖啡的忠實粉絲,多少個夜晚加班加點略水,累了困了喝紅牛---不對是喝茶葉价卤、咖啡「我們無形中使用了一個設計模式--模版方法模式」。我們知道不管是燒茶渊涝、煮咖啡都基本上分為以下幾個步驟:

  • 1慎璧、燒水
  • 2、把茶葉或咖啡放入水壺中
  • 3跨释、加熱不停的煮
  • 4胸私、把煮好的茶葉或咖啡到入杯子中
  • 5、拿起杯子喝「不能直接喝鳖谈,小心你的嘴」

我們看到除了原材料放入的不同「茶葉和咖啡」岁疼,其它的方法都一毛一樣,那么我們把這些方法就可以制定為一個模版「相當于我們有一個既能燒茶又有煮咖啡的器具」缆娃,這就是模版定義了一個基本框架

2捷绒、高考答題

說了上面的例子,大家可能還懵懵的贯要。那么來說一個更實際的例子暖侨,參加過考慮的同學都知道一張考試卷子對所有的同學都是一模一樣的,這個卷子就是一個模版崇渗,以數(shù)學卷子為例吧:有選擇題字逗、填空題函荣、判斷題、應用題「這都是固定的」--這就是一個題的框架扳肛,是一個模版傻挂,至于每位考生如何答題那就考生的事情

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

模版方法模式的定義

定義一個操作算法的骨架「我們知道這個算法所需要的關鍵步驟」挖息,而將一些步驟的實現(xiàn)延遲到子類中去實現(xiàn)金拒。通俗的說模版就是一個抽象類,方法就是策略「一些固定的步驟」套腹。模版方法模式是一個行為型模式

模版方法模式的特點

算法的結構不變绪抛,子類可以改變模版的某些步驟的實現(xiàn)方式,模版方法就是抽象的封裝电禀,一般情況下幢码,模版方法中有一些具體方法「部分邏輯」,抽象方法實現(xiàn)其它剩余的邏輯「子類不同實現(xiàn)的方式就不同」尖飞,并且部分邏輯和剩余邏輯共同組成了算法的結構「一般是執(zhí)行流程症副,這些流程是固定的,但是具體的實現(xiàn)細節(jié)不一樣政基,就可以使用模版方法」

封裝不變的部分贞铣,擴展可變的部分「可變的部分交給子類去實現(xiàn)」

模版方法模式的目的

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

模版方法簡單框架

模版方法模式的結構

角色 類別 說明
AbstractClass 抽象類 抽象模版類
ConcreateClass 具體模版 可以有多個「因為每個具體模版實現(xiàn)的內容可能不一樣」
HookMethod 鉤子方法 不是必須的,是一個開關沮明,用來提供某些方法是否需要調用

模版方法模式簡單的 UML

模版方法模式簡單的 UML

二辕坝、模版方法模式舉例

1、把大象裝冰箱

把大象裝冰箱一共分為幾步荐健?我們都知道三步:第一步把冰箱門打開,第二步把大象裝進去,第三步把冰箱門蓋上酱畅。我們把裝大象的這三步運作可以看做一個算法的步驟「這個步驟不變」,但是具體的你是使用松下冰箱裝大象江场,還是海爾冰箱裝大象纺酸,再進一步說使用冰箱裝所有動物,大象只是其中的一種扛稽,那么就需要抽象出一個模版來吁峻,我們使用模版方法模式來實現(xiàn)這一過程

把大象裝冰箱簡單的 UML

把大象裝冰箱簡單的 UML

根據(jù) UML 擼碼

  • 1滑负、抽象出一個冰箱接口 IRefrige.java
/**
 * Created by TigerChain
 * 抽象冰箱
 */
public interface IRefrige {
    //取得品牌的名字
    String getRefrigeModel() ;
    //設置冰箱品牌
    void setModel(String model) ;
}
  • 2在张、抽象一個動物類 Animail.java
/**
 * Created by TigerChain
 * 定義動物的抽象類
 */
public abstract class Animal {
    // 取得動物的名字
    abstract String getAnimailName() ;
}
  • 3、定義抽象模版方法類 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();        //第三步
    }
}

我們看到冰箱裝動物的步驟是固定的帮匾,但是具體步驟內部實現(xiàn)交給子類去處理吧,這就是模版模式的使用場景

  • 4痴鳄、來一個具體的模版 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 步把冰箱門蓋上");
    }
}
  • 5瘟斜、來一個松下冰箱「當然可以是任意品牌的冰箱」 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 ;
    }
}
  • 6、被裝的對象大象「當然還可以任何動物」 Elephant.java
/**
 * Created by TigerChain
 * 創(chuàng)建一個動物--大象
 */
public class Elephant extends Animal {

    @Override
    String getAnimailName() {
        return "大象";
    }
}
  • 7、測試一下 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() ;
        // 執(zhí)行步驟
        work.handle(panasonnicRefrige,elephant);
    }
}
  • 8虽惭、運行查看結果
把大家裝冰箱的結果

到此為止,我們就把大象裝到冰箱里面了蛇尚,當然你也可以把老虎芽唇、狼、貓裝進冰箱「擴展模版即可」取劫,其實我們使用回調也可以實現(xiàn)同樣的功能「本質上是模版方法的一種變異---是什么匆笤?還是模版方法模式」,我們使用回調方式修改上面代碼「不破壞原來的結構谱邪,我們直接新加類」

  • 9炮捧、我們把想要擴展的方法全部抽象成接口,定義 ITemplate.java
抽象接口
  • 10惦银、定義具體的模版 ConCreateTemplate.java 由于我們把想要擴展的模版方法都抽象出來了咆课,所以我們新建模版的時候就不用抽象了「即定義具體的模版就可以了」
定義具體的模版
  • 11、修改測試類 Test.java 只貼出調用代碼
修改測試類

運行結果和 8 中的結果是一樣的扯俱,這里就不貼圖了傀蚌,具體的代碼可以看:https://github.com/tigerchain/designpattern_javademo/tree/master/src/template/putAnimalInRefrigerator

2、數(shù)據(jù)庫增蘸吓、刪善炫、改、查封裝

操作過數(shù)據(jù)庫的朋友對數(shù)據(jù)的增库继、刪箩艺、改、查再熟悉不過了宪萄,數(shù)據(jù)庫無非就是干這個的艺谆。那么我們可以使用模版方法模式把數(shù)據(jù)庫的增、刪拜英、改静汤、查封裝,至于查什么居凶,改什么虫给,交給具體的模版吧,典型的模版方法模式

數(shù)據(jù)庫增侠碧、刪抹估、改、查 簡單的 UML

數(shù)據(jù)庫增弄兜、刪药蜻、改瓷式、查 簡單的 UML

根據(jù) UML 擼碼

  • 1、定義抽象的模版--數(shù)據(jù)庫增语泽、刪贸典、改查抽象類 AbstractDAO.java
/**
 * Created by TigerChain
 * 定義抽象的數(shù)據(jù)庫增、刪踱卵、改瓤漏、查的模版
 */
public abstract class AbstractDAO<T> {
    // 增加數(shù)據(jù)
    abstract void add(T t) ;
    // 根據(jù) id 刪除數(shù)據(jù)
    abstract void delete(int id) ;
    // 更新數(shù)據(jù)
    abstract void update(T t) ;
    // 根據(jù) id 查找數(shù)據(jù)
    abstract T findById(int id);
    // 查找所有數(shù)據(jù)
    abstract List<T> findall() ;
}

我們這里以泛型去接收實體類,至于查那個交給子類去實現(xiàn)--這樣就把共同點抽象出來了

  • 2颊埃、我們操作用戶表吧蔬充,定義一個 Person.java
/**
 * Created by TigerChain
 * 定義一個 JavaBean 對應數(shù)據(jù)庫中的表
 */
public class Person {
    private int id ;         // id
    private String name ;    // 姓名
    private int age ;        // 年齡
    private String address ; // 地址
    // 省略 setter 和 getter 方法
    ...   
}
  • 3、定義一個操作用戶表的 DAO PersonConCreateDAO.java
**
 * Created by TigerChain
 * 一個具體的模版對用戶表的增班利、刪饥漫、改、查
 */
public class PersonConCreateDAO extends AbstractDAO<Person> {
    // 庫中的用戶列表
    private List<Person> persons = new ArrayList<>() ;

    @Override
    void add(Person person) {
        // 實際上應該做插入數(shù)據(jù)庫操作罗标,為了簡單我們直接輸出語句
        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) {
        // 實際這里應該從數(shù)據(jù)庫中查出數(shù)據(jù)庸队,為了簡單模擬一個數(shù)據(jù)
        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;
    }
}
  • 4、測試一下 Test.java
public class Test {
    public static void main(String args[]){
        // 模擬兩個用戶數(shù)據(jù)
        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 的數(shù)據(jù)
        personConCreateDAO.update(person1);
        personConCreateDAO.findById(1);
        personConCreateDAO.findall() ;

        // 刪除一條數(shù)據(jù)
        personConCreateDAO.delete(1);
        // 查找所有庫中的數(shù)據(jù)
        personConCreateDAO.findall() ;

    }
}
  • 5、運行查看結果
運行結果

至此,我們就使用模版方法模式實現(xiàn)了數(shù)據(jù)庫的增蜓洪、刪井誉、改丛版、查、功能,至于你想操作別的表那直接寫一個具體的模版繼承抽象模版即可,大家動手寫一下煌贴,好好的體驗一下模版方法模式

3、考試答題

考試卷對每個考生來說都是一樣的「考試卷就是一個模版」锥忿,至于每個人如何答題那是每個考生的事情牛郑,針對考試答題我們可以使用模版方法模式來模擬這一過程,代碼就不貼了「我上傳到了 github 上」敬鬓,具體看這里:https://github.com/tigerchain/designpattern_javademo/tree/master/src/template/examination_page

PS:模版方法模式除了抽象模版淹朋、具體模版之外,還可能會有一個鉤子方法「Hook」,也就是說抽象模版中把規(guī)定好了算法的步驟 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 步驟,這就是鉤子方法者甲,大家自行感受一下春感,其實就是一個開關而已

三砌创、Android 源碼中的模版方法模式

1虏缸、View 中的 draw(Canvas canvas) 方法

我們自定義 View 的時候有時調用 ondraw(Canvas canvas) 方法,這里就用到了模版方法模式嫩实,我們來看一下 ondraw 在什么情況下調用「在 View 的 draw() 方法中調用了」刽辙,看看 draw() 方法的核心代碼

draw 核心代碼

這里只不過把抽象方法改成了 protected 的一個空方法而已「本質上是一樣的」,具體代理就不貼了甲献,大家動手扒扒這部分源碼宰缤,其實模版方法模式我們經常用「只不過沒有意識到而已」

2、最熟悉的 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 方法,那么這個方法既可以是固定的也可以是不固定的「子類實現(xiàn)就不固定球及,如果不實現(xiàn)就是固定的,很靈活」氧骤,Activity 就是一個模版方法模式「你天天使用 Activity 知道它是模版模式嗎吃引?」

3筹陵、封裝 BaseActivity

做過 Android 的朋友肯定都封裝過 BaseActivity ,把一些共公的部分抽象出來,然后封裝變化镊尺,比如我們的 app 應用界面都有共公的頭朦佩、下面是內容區(qū)域,如下圖

封裝 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 重寫抽象方法即可實現(xiàn)自己的界面內容,我們寫一個登錄界面繼承 TemplateMethodActivity 弄砍,代碼不貼了颅筋,直接上地址:https://github.com/tigerchain/DesignPattern 查看 TemplateMethod 相關代碼即可

最終運行效果如下:

模版方法模式實現(xiàn) BaseActivity 結果

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

四输枯、模版方法模式的優(yōu)缺點

優(yōu)點

  • 1议泵、封裝不變的部分,擴展可變的部分「交給子類去實現(xiàn)」桃熄,這是設計模式一慣的原則「開先口、閉原則」
  • 2、實現(xiàn)了代碼復用

缺點

  • 繼承關系本身的缺點瞳收,如果父類添加一個新的抽象方法碉京,所有的子類都要改一遍--痛苦

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

以前介紹過策略模式螟深,是對算法的封裝谐宙,而模版方法模式也是對算法執(zhí)行,但是它們之間有明顯的區(qū)別

  • 策略模式:目的是使不同的算法可以被相互替換界弧,不影響客戶端的使用

  • 模版方法模式:針對定義一個算法的流程凡蜻,而將一些不太一樣的“具體實現(xiàn)步驟”交給子類去實現(xiàn)「不改變算法的流程」

六搭综、總結

  • 1、抽象類就是一個模版划栓,而接口是一個標準兑巾,按這樣的規(guī)則可以確定該使用接口還是抽象類
  • 2、模版方法模式就是把不固定的步驟實現(xiàn)方式延遲到子類實現(xiàn)的一種方式忠荞,它是一種行為模式
  • 3蒋歌、模版方法模式基本步驟是固定的「實際開發(fā)中會有很多變種,比如回調替換委煤,沒有固定的步驟全是不固定的等」
  • 4堂油、 一般情況下為了防止子類去更新算法的實現(xiàn)步驟,在抽象的模版方法上加一個 final 關鍵字

到上為止碧绞,模版方法模式就介紹完了称诗,還是那句話,一定要動手試試哦头遭,關注博主寓免,更多精彩內容等著你,手把手教你學會知識點

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末计维,一起剝皮案震驚了整個濱河市袜香,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鲫惶,老刑警劉巖蜈首,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異欠母,居然都是意外死亡欢策,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門赏淌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踩寇,“玉大人,你說我怎么就攤上這事六水“乘铮” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵掷贾,是天一觀的道長睛榄。 經常有香客問我,道長想帅,這世上最難降的妖魔是什么场靴? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上旨剥,老公的妹妹穿的比我還像新娘咧欣。我一直安慰自己,他們只是感情好泞边,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布该押。 她就那樣靜靜地躺著疗杉,像睡著了一般阵谚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烟具,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天梢什,我揣著相機與錄音,去河邊找鬼朝聋。 笑死嗡午,一個胖子當著我的面吹牛,可吹牛的內容都是我干的冀痕。 我是一名探鬼主播荔睹,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼言蛇!你這毒婦竟也來了僻他?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤腊尚,失蹤者是張志新(化名)和其女友劉穎吨拗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婿斥,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡劝篷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了民宿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娇妓。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖活鹰,靈堂內的尸體忽然破棺而出峡蟋,到底是詐尸還是另有隱情,我是刑警寧澤华望,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布蕊蝗,位于F島的核電站,受9級特大地震影響赖舟,放射性物質發(fā)生泄漏蓬戚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一宾抓、第九天 我趴在偏房一處隱蔽的房頂上張望子漩。 院中可真熱鬧豫喧,春花似錦、人聲如沸幢泼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缕棵。三九已至孵班,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間招驴,已是汗流浹背篙程。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留别厘,地道東北人虱饿。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像触趴,于是被迫代替她去往敵國和親氮发。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345