設計模式之模板方法模式
(文章的部分內容參考了《設計模式之禪》一書,大家也可以讀讀看,內容寫的非常好)
什麽是模板方法模式
它的定義如下:
定義一個操作中的算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
讀起來很繞口,其實通俗的說就是父類裏面定義一些抽象方法,自己不去實現,交由子類去具體實現,父類只負責管理這些抽象方法的運行順序,管理運行順序的方法就是模板方法。
舉個具體的使用場景:我們畫畫一般都是先畫線稿,然後再進行上色(不要和我說厚塗。。),不過不同的畫師的繪畫習慣是不同的,比如有的人畫線稿畫的很細致,有的只是粗略的畫出大概的輪廓,就進行上色了。不同畫師上色的時候處理也不同,有的喜歡高飽和的色彩,有的喜歡淡色系。這時我們想建立一個通用的模板,用來歸納畫師的繪畫過程。
那麽這時就可以用到模板方法模式了。
我們第一想到的就是先創建一個接口(IPainter)
public interface IPainter { void drawOutLine(); //繪制線稿 void setColor(); //上色 }
畫師A實現 IPaInter 接口
public class PainterA implements IPainter{ //繪制線稿 @Override public void drawOutLine(){ System.out.println("繪制精致的線稿"); }//上色 @Override public void setColor(){ System.out.println("塗上色彩鮮艷的顏色"); } }
畫師B實現 IPainter 接口
public class PainterB implements IPainter{ //繪制線稿 @Override public void drawOutLine(){ System.out.println("繪制粗略的線稿"); } //上色 @Override public voidsetColor(){ System.out.println("塗上淡色"); } }
我們的兩個畫師 A 和 B 繪畫的過程如下:
public class Context { public static void main(String[] args) { PainterA a=new PainterA(); a.drawOutLine(); a.setColor(); PainterB b=new PainterB(); b.drawOutLine(); b.setColor(); } }
這裏我們可以看到兩個畫師都依次執行了drawOutline()方法和setColor()方法,故可以把這兩個方法的執行順序看做是一個固定的流程,我們再創建一個方法對其進行封裝
在IPainter接口中添加 draw()方法
public interface IPainter { void drawOutLine(); void setColor(); void draw(); //繪畫 }
PainterA,PainterB 中實現該方法
//繪畫 @Override public void draw(){ drawOutLine(); setColor(); }
這樣我們的繪畫過程就變成了這樣
public class Context { public static void main(String[] args) { PainterA a=new PainterA(); a.draw(); PainterB b=new PainterB(); b.draw(); } }
過程簡潔了許多,但還是有個問題,就是draw()方法在 PainterA 和 PainterB 中的邏輯是相同的,相當於同一段代碼被寫了兩次,代碼的冗余不是我們所想要看到的。
我們可以考慮創建一個父類,將冗余的代碼放到父類中,子類再繼承父類,就避免了代碼冗余。
//父類Painter public abstract class Painter implements IPainter{ //繪畫 @Override public void draw(){ drawOutLine(); setColor(); } } //PainterA類 public class PainterA extends Painter{ //繪制線稿 @Override public void drawOutLine(){ System.out.println("繪制精致的線稿"); } //上色 @Override public void setColor(){ System.out.println("塗上色彩鮮艷的顏色"); } } //PainterB類 public class PainterB extends Painter{ //繪制線稿 @Override public void drawOutLine(){ System.out.println("繪制粗略的線稿"); } //上色 @Override public void setColor(){ System.out.println("塗上淡色"); } }
到這裏我們可以看到整個結構體系,父類是負責實現draw()方法,接口裏面的其他方法交由子類去強制實現。記不記得繼承裏面也有一種要強制子類實現的方法?對,就是抽象方法。那麽我們此時完全可以使用抽象方法來取代接口的作用,使得整個系統中只包含父類和子類兩層結構,結構更加清晰。
對父類進行修改:
public abstract class Painter{ //畫線稿 protected abstract void drawOutLine(); //上色 protected abstract void setColor(); //繪畫 public final void draw(){ drawOutLine(); setColor(); } }
子類PainterA,PainterB 不需要進行任何改動, 這時的父類Painter就是一個標準的模板了,它的draw()方法就是模板方法。(註意:我們一般會在模板方法加上 final 關鍵字防止被覆寫)
假如我們有這樣一個擴展,我們需要增加一個畫師對象PainterC,但是這個畫師不按常理出牌,他畫畫一般不畫線稿,直接上色(厚塗大大上線。。。),如果按照之前的模板就無法滿足畫師C( 即使drawOutLine()方法什麽都不寫,但也確實是被執行了,並不算跳過了這一環節 ),對於這種擴展,我們可以使用鉤子方法:
public abstract class Painter{ //畫線稿 protected abstract void drawOutLine(); //上色 protected abstract void setColor(); //繪畫 public final void draw(){ if(needDrawLine()){ drawOutLine(); } setColor(); } public boolean needDrawLine(){ return true; } }
我們可以看到,Painter中加了一個新的方法 needDrawLine() ,然後在draw()方法中進行了判斷,如果needDrawLine()方法返回 true,則繪制線稿,我們將該方法默認返回true,也就是說默認情況下都需要畫線稿。
對於畫師C,我們可以重寫needDrawLine()方法,返回false,這樣就可以不用執行drawOutLine()的操作了,代碼如下:
public class PainterC extends Painter{ //繪制線稿 @Override public void drawOutLine(){ System.out.println("該方法不會被執行"); } //上色 @Override public void setColor(){ System.out.println("開始厚塗"); } @Override public boolean needDrawLine() { return false; } }
當然,我們還可以暴露一個方法,讓外部自己去控制是否調用,將PainterC 進行修改
public class PainterC extends Painter{ private boolean need; //繪制線稿 @Override public void drawOutLine(){ System.out.println("該方法不會被執行"); } //上色 @Override public void setColor(){ System.out.println("開始厚塗"); } @Override public boolean needDrawLine() { return this.need; } public void setDrawLine(boolean need){ this.need=need; } }
我們可以通過給setDrawLine()方法傳入不同的boolean值,來控制是否調用drawOutLine()方法,去畫線稿。
模板方法模式就分析到這裏。
當我們在重構代碼,或者去除代碼冗余時,模板方法模式會是一個不錯的選擇。
設計模式之模板方法模式