前情提要
上集講到, 小光引入了日報制度, 用來從各個分店店長那兒收集資訊. 如此一來, 小光每天就通過日報系統瞭解到各個分店的銷售情況, 問題所在, 也好根據收集到的使用者反饋來改善系統, 改善經營了.
不多久, 就有一個來自使用者的反饋, 通過店長的日報到了小光這邊:
有使用者反饋不同分店的熱乾麵味道不太一樣
所有示例原始碼已經上傳到Github, 戳這裡
統一熱乾麵的製作流程
小光是想將品牌做大, 做統一的. 當然是需要在任何分店給使用者都是統一的體驗啊. 所以, 小光很重視這個問題, 馬上投入了這個問題的解決中.
上次在弄分店的標準時並沒有統一規劃下熱乾麵的製作方法流程, 現在小光想借這個機會正好弄一套, 於是小光做了一個熱乾麵的製作規範:
public class HotDryNoodlesMaker {
public void make() {
// 1, 燙麵
tangmian();
// 2, 裝碗
zhuangwan();
// 3, 加調料(鹽,雞精,胡椒粉之類)
jiatiaoliao();
// 4, 加芝麻醬
jiazhimajiang();
}
// 原諒我, 這些個英文真是不知道怎麼說了, 以下方法名用拼音吧...
// 非我所願
private void tangmian() {
System.out.println("熱乾麵入沸水鍋焯燙十幾秒");
}
private void zhuangwan() {
System.out.println("熱乾麵撈出, 裝入大碗中");
}
private void jiatiaoliao() {
System.out.println("加調料");
}
private void jiazhimajiang() {
System.out.println("加芝麻醬");
}
}複製程式碼
各分店都用這套流程來製作熱乾麵:
public class XiaoGuang {
public static void main(String[] args) {
HotDryNoodlesMaker maker = new HotDryNoodlesMaker();
maker.make();
}
}複製程式碼
熱乾麵入沸水鍋焯燙十幾秒
熱乾麵撈出, 裝入大碗中
加調料
加芝麻醬複製程式碼
看起來沒有問題. 小光是個謹慎的人, 在全面啟用這套流程之前, 小光決定現在光谷店試用下.
試用出了問題
果然, 只有經過使用者的檢驗的產品才是好產品, 剛試用第一天, 小光的熱乾麵製作流程就經受考驗, 出了問題.
吃客們, 有的是在店吃的, 有的是打包帶走的.
當初小光為了品牌性, 在店吃都是用的帶有小光熱乾麵專屬標誌的消毒碗的, 外帶是定製的一次性碗具
現在在"裝碗"這個環節出了問題~~
解決問題
解決問題的第一步是描述問題.
小光深入思考, 探索問題的本質, 發現現在的問題是: 熱乾麵製作必須是這個流程, 而中間的某些步驟又必須可以根據實際場景改變. 也就是說, 不能改變熱乾麵的製作流程, 但是又可以改變(重新定義)這個流程中的某一步(裝碗).
如此, 小光想到了一個辦法:
用一個抽象的類來定義操作步驟(當然並不會抽象所有的步驟), 由具體的子類來實現中間需要定製的步驟.
抽象的製作流程:
public abstract class Maker {
public void make() {
// 1, 燙麵
tangmian();
// 2, 裝碗
zhuangwan();
// 3, 加調料(鹽,雞精,胡椒粉之類)
jiatiaoliao();
// 4, 加芝麻醬
jiazhimajiang();
}
private void tangmian() {
System.out.println("熱乾麵入沸水鍋焯燙十幾秒");
}
private void jiatiaoliao() {
System.out.println("加調料");
}
private void jiazhimajiang() {
System.out.println("加芝麻醬");
}
// 將裝碗這一步抽象出來, 由具體的子類實現
abstract void zhuangwan();
}複製程式碼
然後分別弄一個對應的打包的maker和一個堂食的maker:
// 打包
public class PackingMaker extends Maker {
@Override
void zhuangwan() {
System.out.println("熱乾麵撈出, 裝入一次性碗");
}
}
// 堂食
public class EatInMaker extends Maker {
@Override
void zhuangwan() {
System.out.println("熱乾麵撈出, 裝入店內消毒碗");
}
}複製程式碼
如此這般, 製作的流程有maker控制, 但是也給了不同實現(打包, 堂食)一些自主化空間:
Maker packingMaker = new PackingMaker();
packingMaker.make();
// output
熱乾麵入沸水鍋焯燙十幾秒
熱乾麵撈出, 裝入一次性碗
加調料
加芝麻醬
Maker eatInMaker = new EatInMaker();
eatInMaker.make();
// output
熱乾麵入沸水鍋焯燙十幾秒
熱乾麵撈出, 裝入店內消毒碗
加調料
加芝麻醬複製程式碼
達成要求.
故事之後
照例我們先縷縷類的關係:
在故事過程中, 我們描述了問題及其解決之道. 實際上這個解決之道就是我們今天要說的模板方法模式.
模板方法模式
定義一個操作中的演算法骨架(熱乾麵的製作流程), 而將某些步驟實現延遲到子類中. 使得子類可以根據實際情況不改變演算法骨架(熱乾麵的製作流程), 但是可以重新定義或改變該演算法中的某些特定步驟(例如裝碗).
擴充套件閱讀一
模板方法是程式碼複用的基本技術, 基本上隨處可見. 我們平常編碼上, 如果想複用一些程式碼, 基本上第一個想到的就是提取父類, 抽象可變, 然後子類實現抽象部分的方式來複用. 沒錯, 這種用法往往你就用到模板方法模式.
所以說, 設計模式並不高深, 它實際上就是一種良好程式設計習慣的提煉. 我們可能隨處再用, 只是有的時候我們並沒有意識到.
這種程式碼複用的方式在一些底層庫, SDK中更是被頻繁使用. 例如Android中最為複雜之一的View, 就用到木板方法模式.
其draw()方法就是模板方法, onDraw()是抽象的可定製的原語操作:
// 程式碼節選自View.java
// draw()方法相當於定義了一個View的繪製演算法結構, 其中的onDraw的可變的原語操作.
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
}
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
// 原語操作, 由子類實現, 來定製
protected void onDraw(Canvas canvas) {
}複製程式碼
擴充套件閱讀二
很多繼承關係中, 我們都能找到模板方法的影子, 但是並非所有的繼承就是用到了模板方法模式. 有的同學可能會回想起我們之前提到的策略模式. 二者看起來很像啊, 貌似都是跟替換演算法有關的.
在這裡簡單區別下二者:
二者都是通過用繼承關係來達到"演算法替換"的目的.
但是模板方法是不改變演算法結構, 只替換/改變其中的步驟.
而策略模式是改變了整個演算法.
打個比方, 我從北京到武漢的過程, 定義一個回家演算法 --- 做火車回家, 分成幾步:
- 上火車站買票
- 檢票上車
- 一路順風回家
如果我改變了買票的方式, 例如我不想去火車站買票了, 在網上買, 或是電話買. 但是演算法結構不變, 還是做火車, 還是這三步, 那麼這個就是模板方法模式.
但是如果我不想做火車了, 我開車, 我坐飛機回家, 那麼就相當於替換了整個演算法, 這個就是策略模式.
總之, 小光又解決了一個問題, 解決使用者痛點是產品的生存之道, 小光知道自己的熱乾麵正在變得越來越好...哈哈.