美文网首页Android知识程序员架构设计与重构
统一热干面的制作流程---模板方法

统一热干面的制作流程---模板方法

作者: anly_jun | 来源:发表于2016-12-22 17:24 被阅读390次
    cover

    前情提要

    上集讲到, 小光引入了日报制度, 用来从各个分店店长那儿收集信息. 如此一来, 小光每天就通过日报系统了解到各个分店的销售情况, 问题所在, 也好根据收集到的用户反馈来改善系统, 改善经营了.

    不多久, 就有一个来自用户的反馈, 通过店长的日报到了小光这边:

    有用户反馈不同分店的热干面味道不太一样

    所有示例源码已经上传到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) {
    } 
    

    扩展阅读二

    很多继承关系中, 我们都能找到模板方法的影子, 但是并非所有的继承就是用到了模板方法模式. 有的同学可能会回想起我们之前提到的策略模式. 二者看起来很像啊, 貌似都是跟替换算法有关的.

    在这里简单区别下二者:

    二者都是通过用继承关系来达到"算法替换"的目的.
    但是模板方法是不改变算法结构, 只替换/改变其中的步骤.
    而策略模式是改变了整个算法.

    打个比方, 我从北京到武汉的过程, 定义一个回家算法 --- 做火车回家, 分成几步:

    1. 上火车站买票
    2. 检票上车
    3. 一路顺风回家

    如果我改变了买票的方式, 例如我不想去火车站买票了, 在网上买, 或是电话买. 但是算法结构不变, 还是做火车, 还是这三步, 那么这个就是模板方法模式.

    但是如果我不想做火车了, 我开车, 我坐飞机回家, 那么就相当于替换了整个算法, 这个就是策略模式.


    总之, 小光又解决了一个问题, 解决用户痛点是产品呢生存之道, 小光知道自己的热干面正在变得越来越好...哈哈.

    相关文章

      网友评论

        本文标题:统一热干面的制作流程---模板方法

        本文链接:https://www.haomeiwen.com/subject/khrhvttx.html