PS:轉載請注明出處
作者: TigerChain
地址: http://www.reibang.com/p/6c6191a47197
本文出自 TigerChain 簡書 人人都會設計模式
教程簡介
- 1稳衬、閱讀對象
本篇教程適合新手閱讀,老手直接略過 - 2、教程難度
初級仰楚,本人水平有限爪喘,文章內容難免會出現(xiàn)問題颜曾,如果有問題歡迎指出,謝謝 - 3秉剑、Demo 地址:Android Demo---https://github.com/tigerchain/DesignPattern 對應的 TemplateMethod
Java Demo:https://github.com/tigerchain/designpattern_javademo/tree/master/src/template
正文
一泛豪、什么是模版方法模式
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
二辕坝、模版方法模式舉例
1、把大象裝冰箱
把大象裝冰箱一共分為幾步荐健?我們都知道三步:第一步把冰箱門打開,第二步把大象裝進去,第三步把冰箱門蓋上酱畅。我們把裝大象的這三步運作可以看做一個算法的步驟「這個步驟不變」,但是具體的你是使用松下冰箱裝大象江场,還是海爾冰箱裝大象纺酸,再進一步說使用冰箱裝所有動物,大象只是其中的一種扛稽,那么就需要抽象出一個模版來吁峻,我們使用模版方法模式來實現(xiàn)這一過程
把大象裝冰箱簡單的 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
根據(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() 方法的核心代碼
這里只不過把抽象方法改成了 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ū)域,如下圖
然后不同的界面寫不同的子類繼承即可庐氮,我們使用偽代碼來模擬一下
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 相關代碼即可
最終運行效果如下:
怎么樣,是不是一直在使用模版方法模式「只是不知道而已」
四输枯、模版方法模式的優(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 關鍵字
到上為止碧绞,模版方法模式就介紹完了称诗,還是那句話,一定要動手試試哦头遭,關注博主寓免,更多精彩內容等著你,手把手教你學會知識點