一、前期回顧
上一篇《Java 設計模式之工廠方法模式與抽象工廠模式》介紹了三種工廠模式,分別是工廠方法模式,簡單工廠方法模式,抽象工廠模式,文中詳細根據實際場景介紹了三種模式的定義,實踐,最後總結了三種方式的區別,以及各個模式的適用場景。這一篇博文我們來學習下模板方法模式和建造者模式。
二、模板方法模式和建造者模式的定義與實踐
2.1 模板方法模式的定義和實踐
定義:Define the skeleton of an algorithm in an operation.deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
翻譯:定義一個操作中的演算法框架,從而延遲子類中的一些步驟。使得子類可以不改變演算法結構的情況下就可以重新定義該演算法的某些特定的步驟。
其實根據上面定義,我們很快就可以想到,這個模式的定義不就是繼承嗎?對,沒錯,模板方法模式最簡單的實現就是繼承。我們先用程式碼來實現一個模板方法模式。相信大家都吃過泡麵吧,我們來用模板方法模式來泡個面吧。
public abstract class AbstractTemplate {
/**
* 燒開水*/
public abstract void boilWater();
/**煮麵條*/
public abstract void cookNoodles();
/**放調料*/
public abstract void putCondiment();
/**定義煮麵的模板,先燒水,再放麵條,最後放調料*/
public void finish(){
boilWater();
cookNoodles();
putCondiment();
System.out.println("煮完啦,開吃咯!");
}
}
/**煮方便麵模板實現類*/
public class InstantNoodlesTemplate extends AbstractTemplate{
@Override
public void boilWater() {
System.out.println("燒開水啦!");
}
@Override
public void cookNoodles() {
System.out.println("放入方便麵啦!");
}
@Override
public void putCondiment() {
System.out.println("可以放調料啦!");
}
}
/**客戶端場景類*/
public class Client {
public static void main(String[] args) {
AbstractTemplate template=new InstantNoodlesTemplate();
template.finish();
}
}
複製程式碼
上面的的finish()
方法就是定義了一個演算法框架,定義了煮麵條先要燒開水,然後放麵條,最後放調料的演算法步驟。然後各個子類實現如何燒水,放什麼麵條,放什麼調料這樣的具體實現。這就是模板方法模式,僅僅通過繼承就實現了。就是這麼簡單,下面我們來看看和模板方法模式相識的另外一個模式,建造者模式。
2.21 建造者模式的定義和實踐
定義:Separate the construction of a complex object from its representation so that the same construction process can create different representations.
翻譯:將一個複雜物件的構建與他的表現分離,使得同樣的構建過程可以建立不同的表示。
這裡化重點,複雜物件的構建和表現分離,這裡用在上面煮麵的場景中就是說,煮麵的演算法和定義是分離的,也就是說由各個具體的麵條品種決定如何去煮麵條,如何去放調料等等這些具體的演算法步驟。我們來用程式碼實現下:
//抽象煮苗條類
public abstract class AbstractNoodlesMode {
private List<String> stepOrders = new ArrayList<>();
/**
* 燒開水
*/
public abstract void boilWater();
/**
* 煮麵條
*/
public abstract void cookNoodles();
/**
* 放調料
*/
public abstract void putCondiment();
/**
* 煮麵條其他步驟
*/
public abstract void doOther();
/**
* 根據傳入的工序進行加工煮麵
*/
final public void finish() {
for (String stepOrder : this.stepOrders) {
switch (stepOrder) {
case "boilWater":
boilWater();
break;
case "cookNoodles":
cookNoodles();
break;
case "putCondiment":
putCondiment();
break;
case "doOther":
doOther();
break;
default:
System.out.println("無法識別的烹飪指令");
break;
}
}
}
final public void setStepOrders(List<String> stepOrders) {
this.stepOrders = stepOrders;
}
}
/**方便麵的實現類,由於只需要燒水,煮麵,放調料就行了,other方法就為空*/
public class InstantNoodles extends AbstractNoodlesMode {
@Override
public void boilWater() {
System.out.println("煮開水");
}
@Override
public void cookNoodles() {
System.out.println("放入方便麵");
}
@Override
public void putCondiment() {
System.out.println("放入調料");
}
@Override
public void doOther() {
}
}
/**義大利麵條實現類*/
public class Spaghetti extends AbstractNoodlesMode {
@Override
public void boilWater() {
System.out.println("煮開水");
}
@Override
public void cookNoodles() {
System.out.println("放入義大利麵");
}
@Override
public void putCondiment() {
System.out.println("放入番茄醬");
}
@Override
public void doOther() {
System.out.println("放入火腿,早餐肉");
}
}
/**抽象建造者類*/
public abstract class AbstractBuilder {
/**定義煮麵條工序*/
public abstract AbstractBuilder cookNoodles();
/**完成麵條*/
public abstract AbstractNoodlesMode build();
}
/**泡麵建造者實現類*/
public class InstantNoodlesBuilder extends AbstractBuilder {
private AbstractNoodlesMode noodles=new InstantNoodles();
@Override
public AbstractBuilder cookNoodles() {
List<String> steps=new ArrayList<>();
//燒水
steps.add("boilWater");
//放麵條
steps.add("cookNoodles");
//放調料
steps.add("putCondiment");
this.noodles.setStepOrders(steps);
return this;
}
@Override
public AbstractNoodlesMode build() {
return this.noodles;
}
}
/*義大利麵條建造者實現類**/
public class SpaghettiBuilder extends AbstractBuilder {
private AbstractNoodlesMode noodle = new Spaghetti();
@Override
public AbstractBuilder cookNoodles() {
List<String> steps = new ArrayList<>();
//燒水
steps.add("boilWater");
//放麵條
steps.add("cookNoodles");
//放調料
steps.add("putCondiment");
//放火腿,放早餐肉
steps.add("doOther");
this.noodle.setStepOrders(steps);
return this;
}
@Override
public AbstractNoodlesMode build() {
return this.noodle;
}
}
/**客戶端場景類*/
public class Client {
public static void main(String[] args) {
AbstractBuilder instantNoodleBuilder = new InstantNoodlesBuilder();
AbstractBuilder spaghettiBuilder = new SpaghettiBuilder();
AbstractNoodlesMode instantNoodle = instantNoodleBuilder.cookNoodles().build();
instantNoodle.finish();
System.out.println("--------------------");
AbstractNoodlesMode spaghe = spaghettiBuilder.cookNoodles().build();
spaghe.finish();
}
}
複製程式碼
我們執行下結果如下:
上述程式碼我們整理成一個類圖如下:
上面類圖我省去了麵條實現類,但是這也不影響整個建造者模式的理解。這裡有同學肯定會問,這個建造者模式雖然比模板方法模式複雜點,但是感覺他們很相似啊,有點弄混的感覺啊。對,沒錯,他們確實很相識。但是我們只要記住一點就能很好的區分他們。
- 模板方法模式已經定義好了建造者的演算法,也就是工序,先做什麼後做什麼。
- 建造者模式沒有定義建造者的工序,而是交給子類去實現建造者的演算法,也就是工序。
我們只要記住這一點就能很好的區分模板方法模式和建造者模式,同時也就知道了什麼時候採用模板方法模式,什麼時候採用建造者模式。
三、模板方法模式和建造者模式的應用場景
3.1 模板方法模式的使用場景
1.多個子類有公有的方法,並且邏輯基本一樣時。
2.有重要核心的複雜演算法,而且需要很多的複用性時,可以把該演算法用模板方法模式實現。
3.程式碼重構升級時,模板方法模式是很常用的模式,一般為了保證向下相容,所以採用模板方法模式,在模板方法內會設定鉤子函式,根據不同的版本和不同的情況執行不同的演算法。
3.2 建造者模式的使用場景
1.相同的方法,不同的執行順序,產生不同的事件結果時,可以採用建造者模式
2.多個部件或零件,都可以裝配到一個物件中,但是產生的結果又不相同。
四、模板方法模式和建造者模式的優點與缺點
4.1模板方法模式
優點:
- 良好的擴充套件性,子類只要實現指定的演算法即可,不用關注後續組合的演算法
- 良好的可維護性。
- 符合依賴倒置原則
缺點:
- 個人理解的缺點可能就是封裝死了核心組合方法,但是這就是模板方法的特點,姑且算一個缺點吧。
4.2建造者模式
優點:
- 和模板方法模式一樣具有良好的擴充套件性和可維護性
- 靈活性,由於連最核心的組合演算法都交給子類去實現,所以更靈活。
缺點:
- 風險性,由於放開了組合演算法,降低了約束性。所以可能會導致子類呼叫定義錯了演算法,給系統帶來潛在的風險。
五、總結
模板方法模式和建造者模式都是屬於創造性模式,兩個模式程式碼很相似,很容易弄混,我們只要記住他們的核心區別就可以知道什麼時候該用模板方法模式,什麼時候該用建造者模式。這裡再說一遍他們的主要區別: 模板方法模式定義了核心演算法,也就是組裝工序,而建造者模式沒有定義建造的順序和構造多少零件,最後的核心工序以及使用零件實現完全由子類去決定。
詳細細心的讀者肯定以及發現了我的建造模式中的實現出現了instantNoodleBuilder.cookNoodles().build()
這樣A.B.C
的程式碼,之前在我們的設計模式開篇我們談到了迪米特法則,迪米特法則有舉例說不要出現A.B.C
的情況,這裡說明下迪米特法則的要求其實是不要出現依賴非朋友類的情況,其實實際上是不要出現A.getB.getC
的情況,而這裡其實每次返回的都是this
,所以都是依賴的自己,並沒有出現非朋友類,所以這樣的寫法沒有違法迪米特法則。
六、參考
《設計模式之禪》