[设计模式] 模板方法模式

Posted1 by 呼延十 on March 20, 2019 Hot:

前言

在上一篇文章责任链模式中提到了模板方法模式,因此这里简单介绍一下.

模板方法模式比较简单,或者说比较常用.在开发过程中,许多人在不知不觉的情况下就会使用,只要他具有良好的面对对象思维.

比如当你写了DogCat两个类,发现很多相同的代码,你自然就会将相同模块提取抽象成父类,然后将一些公共的方法放到父类中,这样子就基本实现了模板方式模式.

介绍(摘自《Head FIrst 设计模式》)

在一个方法中定义一个算法的骨架,而将一些详细的步骤延迟到子类中.

模板方法使得子类可以在不改变算法结果的基础上,重新定义算法中的某些步骤.

类图

2019-03-19-23-58-35

角色

抽象模板: 抽象模板一般有一个具体实现的方法,用来定义算法的基础骨架.还有一些抽象方法留给子类去具体实现.此外还有一些有默认实现的钩子方法.子类可选实现.

具体模板: 继承父类的具体方法,实现他们的抽象方法,对于钩子方法,可以根据自身情况决定是都重写.

举个栗子

书上的例子好多了,网络上也有很多,我自己临时瞎想一个吧,不保证一定合适.

假如我们现在要实现两个类,DogCat,并且实现他们的进攻方法.

我们大致实现以下,如下.

Cat:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public class Cat1 {

  private void attack() {

    prepared();
    jump();
    bite();

  }

  private void prepared() {

  }

  private void jump() {

  }

  private void bite() {

  }

}

Dog:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public class Dog1 {

  private void attack(){

    prepared();
    run();
    shout();
    bite();

  }

  private void prepared(){

  }

  private  void run(){

  }

  private void shout(){

  }

  private void bite(){

  }
}

仔细查看代码,发现其实他们的攻击过程很相似.

狗:准备,跑过去,发出声音,咬住
猫:准备,跳过去,咬住

但是这样子编码的话,我们将相同的preparedbite分别写了两次,这是不科学的.

很明显我们可以实现一个父类,将preparedbite在父类里实现他们分别继承就好了.

那么对于shoutjump/run方法呢?就分别实现了嘛?

不是的,jumprun都是移动,只是实现方法不同,也是可以抽象到父类的,那么shout呢?这就是钩子的作用了,可以动态控制当前动物是否会发出声音之后再进行攻击.

改进后的代码如下:

首先是动物模板类:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public abstract class AnimalTemplate {

  protected boolean isShout;

  public final void attack() {
    //
    prepared();
    move();
    if (isShout) {
      shout();
    }
    bite();

  }

  private void prepared() {
    //具体实现准备方案
    System.out.println("准备");
  }

  abstract void move();

  private void bite() {
    //具体实现咬的方式
    System.out.println("咬");
  }

  abstract void shout();

  public void setShout() {
    isShout = true;
  }
}

注意,代码中的setShout是钩子方法,这里简单的用一个变量来当做钩子,此外,preparedbite是具体方法,而moveshout为抽象方法.

下面是狗的具体实现:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public class Dog extends AnimalTemplate {

  @Override
  void move() {
    System.out.println("我是狗,我跑过去");
  }

  @Override
  void shout() {
    System.out.println("我是狗,我叫一下,吓唬吓唬他");
  }
}

猫的具体实现:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public class Cat extends AnimalTemplate {

  @Override
  void move() {
    System.out.println("我是猫,我跳过去.");
  }

  @Override
  void shout() {
    System.out.println("我是猫,我不叫.");
  }

  @Override
  public void setShout() {
    this.isShout = false;
  }
}

测试类:

package design_patterns.template_pattern;

/**
 * created by huyanshi on 2019/3/20
 */
public class Test {

  public static void main(String[] args) {

    AnimalTemplate dog = new Dog();
    dog.setShout();
    dog.attack();

    AnimalTemplate cat = new Cat();
    cat.setShout();
    cat.attack();
  }
}

输出结果:

准备
我是狗,我跑过去
我是狗,我叫一下,吓唬吓唬他
咬
准备
我是猫,我跳过去.
咬

对比上下的代码可以发现,在第二个版本中,没有一些重复的代码,且子类的逻辑更加清晰,仅仅实现了自己与其他类不相同的部分,或者自己想要实现的部分钩子方法.

而且当动物越来越多,代码的总量会越来越少且容易维护,新添加一个动物,只需要继承动物模板,然后实现moveshout即可.

总结

首先注意一下:在模板方法的attack上,添加了final关键字,可以防止该方法被重写,可以保证attack这一方法,在定义及流程上的正确性及安全性,而具体的实现可以交给子类.

模板方法的优点: 1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。

缺点:

类的个数较多.

完。





ChangeLog

2019-03-20 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十