Java设计模式之模板方法模式Template Method Pattern详解

作者:流烟默 时间:2023-09-21 12:28:04 

概述

模板方法

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。那么什么是模板方法呢?我们看下模板方法的定义。

  1. 一个具体方法而非抽象方法,其用作一个算法的模板;

  2. 在模板方法中,算法内的大多数步骤都被某个方法代表;

  3. 模板方法中某些方法是子类处理

  4. 需要由子类提供的方法,必须在超类中声明为抽象方法;

  5. 模板方法通常不能被覆盖,也就是使用final修饰;

模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),指在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。

这种类型的设计模式属于行为型模式。

如下实例,prepareRecipe就是一个模板方法。

public abstract class CaffeineBeverage {
  final void  prepareRecipe(){
      boilWater();
      brew();
      pourInCup();
      addCondiments();
  }
  protected abstract void addCondiments();
  protected abstract void pourInCup();
  protected abstract void brew();
  protected abstract void boilWater();
}

模板方法模式

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

这个模式是用来创建一个算法的模板。什么是模板?如你所见的,模板就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。

对上面实例进一步扩展,我们看下抽象类内可以有哪些类型的方法。

public abstract class CaffeineBeverage {
  final void  prepareRecipe(){
      boilWater();
      brew();
      pourInCup();
      addCondiments();
      concreteOperation();
      hook();
  }
  protected abstract void addCondiments();
  protected abstract void pourInCup();
  protected abstract void brew();
  protected abstract void boilWater();
     final void concreteOperation(){
// 这里是实现
  }
//空方法
  void hook(){};
}

可以看到有一个具体的concreteOperation方法,final表示其不可以被子类覆盖。我们也可以由“默认不做事的方法”,我们称这种方法为“hook”(钩子)。子类可以视情况决定要不要覆盖他们。

钩子是一种被声明在 抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。每一个具体的子类都必须定义所有的抽象方法,并为模板方法算法中未定义步骤提供完整的实现。

那么什么时候使用抽象方法什么时候使用钩子呢?

答,当你的子类必须提供算法中某个算法或步骤的实现时,就是用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现这个钩子,但并不强制这么做。

使用钩子的真正目的是什么?

钩子有几种用法。如我们之前所说的,钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。钩子的另一个用法,是让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。比方说,名为justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上重新显示数据)。正如前面提到的,钩子也可以让子类有能力为其抽象类作一些决定。

好莱坞原则

好莱坞原则简单来讲就是:别调用我们,我们会调用你。

好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。

在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式就是“别调用我们,我们会调用你”。

模板方法模式就契合该原则。当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。

那么低层组件完全不可以调用高层组件的方法吗?并不尽然!

事实上,低层组件在结束时,常常调用从超类中继承来的方法。我们所要做的是,避免让高层和低层组件之间有明显的环状依赖。

好莱坞原则与依赖倒置原则

依赖倒置原则教我们尽量避免使用具体类,而多使用抽象类。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是在于解耦,但是依赖倒置原则更加注重如何在设计中避免依赖。

好莱坞原则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖它们。

真实案例

数组类Arrays的mergeSort方法就是一个模板方法。

private static void mergeSort(Object[] src,
                                 Object[] dest,
                                 int low,
                                 int high,
                                 int off) {
       int length = high - low;
       // Insertion sort on smallest arrays
       if (length < INSERTIONSORT_THRESHOLD) {
           for (int i=low; i<high; i++)
               for (int j=i; j>low &&
                        ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                   swap(dest, j, j-1);
           return;
       }
       // Recursively sort halves of dest into src
       int destLow  = low;
       int destHigh = high;
       low  += off;
       high += off;
       int mid = (low + high) >>> 1;
       mergeSort(dest, src, low, mid, -off);
       mergeSort(dest, src, mid, high, -off);
       // If list is already sorted, just copy from src to dest.  This is an
       // optimization that results in faster sorts for nearly ordered lists.
       if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
       }
       // Merge sorted halves (now in src) into dest
       for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
           if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
               dest[i] = src[p++];
           else
               dest[i] = src[q++];
       }
   }

这里方法中,swap方法是一个具体方法。每一个元素需要实现compareTo方法,&ldquo;填补&rdquo;模板方法的缺憾。

可能会有疑问,上面这个案例真的是一个模板方法吗?

答,其符合模板方法的精神。这个模式的重点在于提供一个算法,并让子类实现某些步骤。而数组的排序做法很明显地并非如此!但是,我们都知道。Java api中的模式并非总是如同教科书例子一般地中规中矩,为了符合当前的环境和实现的约束,它们总是要被适当地修改。这个Arrays类sort方法的设计者受到一些约束。通常我们无法设计一个类继承Java数组,而sort()方法希望能够适用于所有的数组(每个数组都是不同的类)。所以它们定义了一个静态方法,而由被排序的对象内的每个元素自行提供比较大小的算法部分。所以,这虽然不是教科书上的模板方法,但它的实现仍然符合模板方法模式的精神。再者,由于不需要基础数组就可以使用这个方法,这样使得排序变得更有弹性、更有用。

另外一个案例是java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被read(byte b[], int off, int len)模板方法使用。

模板方法模式的注意事项和细节

基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。

实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。

该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。一般模板方法都加上final 关键字, 防止子类重写模板方法。

模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理。

来源:https://janus.blog.csdn.net/article/details/121105533

标签:Java,模板方法模式,Template,Method,Pattern
0
投稿

猜你喜欢

  • JAVA发送HTTP请求的四种方式总结

    2023-08-23 20:27:23
  • java开发RocketMQ生产者高可用示例详解

    2023-04-27 13:27:57
  • Android ViewPager相册横向移动的实现方法

    2023-02-19 07:26:08
  • Java循环队列原理与用法详解

    2023-11-13 20:05:36
  • spring mvc利用ajax向controller传递对象的方法示例

    2022-10-22 15:06:13
  • datagridview实现手动添加行数据

    2022-03-29 16:31:09
  • C# 字符串的连接(实例讲解)

    2021-11-28 13:57:14
  • Android 弹出软键盘所遇到的坑及解决方法

    2022-06-03 23:41:47
  • Unity中的PostProcessBuild实用案例深入解析

    2021-06-08 13:48:51
  • 浅谈Spring Boot 整合ActiveMQ的过程

    2022-03-22 05:20:27
  • 解决java执行cmd命令调用ffmpeg报错Concat error - No such filter '[0,0]'问题

    2023-03-14 20:35:11
  • java底层JDK Logging日志模块处理细节深入分析

    2023-02-04 12:47:31
  • Android入门教程之Fragment的具体使用详解

    2021-09-30 01:21:57
  • Java实战之用springboot+netty实现简单的一对一聊天

    2023-12-03 07:28:19
  • Knife4j 3.0.3 整合SpringBoot 2.6.4的详细过程

    2023-11-25 03:54:34
  • Mybatis中一条SQL使用两个foreach的问题及解决

    2022-02-04 06:05:41
  • C#多线程开发实战记录之线程基础

    2022-11-03 03:21:45
  • JAVA复制数组和重置数组大小操作

    2021-08-29 02:02:41
  • Android异步回调中的UI同步性问题分析

    2022-07-31 14:10:51
  • 自定义对象作为HashMap的Key问题

    2022-11-06 02:52:30
  • asp之家 软件编程 m.aspxhome.com