《设计模式》模板方法模式

定义

定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。

介绍

  • 模板方法模式属于行为型模式。
  • 模板方法模式主要是用来定义一套流程下来的固定步骤,而具体的步骤实现则可以是不固定的。

UML类图

模板方法UML类图

角色说明:

  • AbstractClass(抽象类):定义了一整套算法框架。
  • ConcreteClass(具体实现类):具体实现类,根据需要去实现抽象类中的方法。

实现

继续以送快递为例,快递员送快递基本就是一套固定的流程:收到快递,准备派送->联系收货人->确定结果。

1、创建抽象类。定义算法框架,这里是快递员派送快递的步骤:

// 抽象快递员类
public abstract class Postman {

  // 派送流程。(这里申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序)
  public final void post() {
    // 准备派送
    prepare();
    // 联系收货人
    call();
    if (isSign()) {
      // 签收
      sign();
    } else {
      // 拒签
      refuse();
    }
  }

  // 准备操作,固定流程,父类实现
  protected void prepare() {
    System.out.println("快递已达到,准备派送");
  }

  // 联系收货人,联系人不一样,所以为抽象方法,子类实现
  protected abstract void call();

  // 是否签收,这个是钩子方法,用来控制流程的走向
  protected boolean isSign() {
    return true;
  }
  
  // 签收,这个是固定流程,父类实现
  protected void sign() {
    System.out.println("客户已签收,上报系统");
  }

  // 拒签,空实现,这个也是钩子方法,子类可以跟进实际来决定是否去实现这个方法
  protected void refuse() {
  }
}

需要注意的是上面的抽象类(Postman)包含了三种类型的方法:抽象方法、具体方法和钩子方法。

  • 抽象方法:需要子类去实现。如上面的call()。
  • 具体方法:抽象父类中直接实现。如上面的prepare()和sign()。
  • 钩子方法:有两种,第一种,它是一个空实现的方法,子类可以视情况来决定是否要覆盖它,如上面的refuse();第二种,它的返回类型通常是boolean类型的,一般用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行,如上面的isSign()。

2、创建具体实现类。根据需要去实现抽象类中的方法,下面以派送给两个不同的人为例,其中一个签收,另一个拒收:

// 派送给A先生
public class PostA extends Postman {
  // 联系收货,实现父类的抽象方法
  @Override
  protected void call() {
    System.out.println("联系A先生并送到门口");
  }
}

// 派送给B先生
public class PostB extends Postman {
  // 联系收货,实现父类的抽象方法
  @Override
  protected void call() {
    System.out.println("联系B先生并送到门口");
  }

  // 是否签收,覆盖父类的钩子方法,控制流程的走向
  @Override
  protected boolean isSign() {
    return false;
  }

  // 拒签,覆盖父类的钩子方法
  @Override
  protected void refuse() {
    System.out.println("拒绝签收:商品不符");
  }
}

3、客户端测试

public void test() {
  System.out.println("----派送A----");
  Postman postA = new PostA();
  postA.post();
  System.out.println("----派送B----");
  Postman postB = new PostB();
  postB.post();
}

输出结果:

----派送A----
快递已达到,准备派送
联系A先生并送到门口
客户已签收,上报系统
----派送B----
快递已达到,准备派送
联系B先生并送到门口
拒绝签收:商品不符

应用场景

  • 一次性实现算法的执行顺序和固定不变部分,可变部分则交由子类来实现。
  • 多个子类中拥有相同的行为时,可以将其抽取出来放在父类中,避免重复的代码。
  • 使用钩子方法来让子类决定父类的某个步骤是否执行,实现子类对父类的反向控制。
  • 控制子类扩展。模板方法只在特定点调用钩子方法,这样就只允许在这些点进行扩展。

优缺点

优点

  • 提高代码复用性,去除子类中的重复代码。
  • 提高扩展性,不同实现细节放到不同子类中,易于增加新行为。

缺点

  • 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。

Android中的源码分析

AndroidViewdraw()方法就是使用了模板方法模式:

1、Viewdraw()方法

public class View {
  // 钩子方法,空实现
  protected void onDraw(Canvas canvas) {
  }

  // 钩子方法,空实现
  protected void dispatchDraw(Canvas canvas) {
  }

  // 绘制方法,定义绘制流程
  public void draw(Canvas canvas) {
    // 其他代码略

    /**
     *  绘制流程如下:
     *
     *      1. 绘制view背景
     *      2. 如果有需要,就保存图层
     *      3. 绘制view内容
     *      4. 绘制子View
     *      5. 如果有必要,绘制渐变框和恢复图层
     *      6. 绘制装饰(滑动条等)
     */

    // 步骤1. 绘制view背景
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }

    // 如果可能的话跳过第2步和第5步(常见情况)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
      // 步骤3. 绘制view内容
      if (!dirtyOpaque) onDraw(canvas);

      // 步骤4. 绘制子View
      dispatchDraw(canvas);

      // 覆盖一部分内容,绘制前景
      if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
      }

      // 步骤6. 绘制装饰(滑动条等)
      onDrawForeground(canvas); 

      return;
    }
  }
}

2、说明

  • Viewdraw()方法中定义了一整套的绘制流程,这个流程是固定的,所有的Android中的View都是按照这个流程来绘制的。其中drawBackground()这个方法在View类中是实现了具体过程的,而onDraw()方法和dispatchDraw()方法在View中都是空实现,即都是钩子方法。不同的子类通过重写这些空实现来实现自身不同的绘制效果。
  • 具体的View,像TextView这些单一的View,就会重写onDraw()方法,由于TextView没有子View,所以dispatchDraw()还是空实现;而ViewGroup类含有子View,需要遍历子View并绘制,因此需要重写onDraw()dispatchDraw()
  • 所以,我们自定义View时必须且只需重写onDraw();自定义ViewGroup时则需要重写onDraw()dispatchDraw()

3、其他
另外,像Activity的生命周期,AsyncTask等等也是用到了模板方法模式,也兴趣的可以研究一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352