浅析设计模式4——模板方法模式

简介: 我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。本专题着眼于实际开发过程中常用的几种设计模式,从理论和实战两个角度进行讨论和分享,力求逻辑清晰、表述简洁,帮助大家在项目中合理运用设计模式,保障代码的可靠性。本文为此系列第四篇文章,前三篇见——第一篇:浅析设计模式1 —— 工厂模式第二篇:浅析设计模式2 —— 策略模式第三篇:浅析设计模式3 —— 装饰者模式

image.png

image.png

基本概念


模版方法模式的核心思想是:首先在抽象类中定义一个任务的算法骨架,将算法的执行细节延迟到子类中个性化实现。注意,子类可以在不改变算法架构的情况下,重新定义特定步骤,甚至干预算法的执行流程,从而起到控制抽象父类行为的作用。
下面从模式结构和使用步骤两个层面,简单阐述模版方法模式的基本概念。
 结构


模版方法模式的结构相对简单,主要包含两大类:抽象类、具体类,抽象父类中首先定义好算法流程,具体的步骤细节延迟到具体子类中执行。


角色
关系
作用

抽象类    Abstract Class

具体类的父类

定义一个抽象的模版类,给出算法的骨架,包含一个模版方法和若干个基本方法(抽象方法、具体方法、钩子方法)

具体类    Concrete Class

抽象构件的接口实现类

定义一个具体的实现类,实现抽象类中定义的抽象方法和钩子方法。

image.png

 使用


有了上述的基本概念,我们将装饰者模式的使用步骤概括为:

step1:创建抽象类,定义一个算法骨架,包含一个模版方法和若干个基本方法:

  • 模版方法:算法流程,定义基本方法的执行顺序;
  • 基本方法 - 具体方法:在抽象类中实现,可被具体类继承或重写;
  • 基本方法 - 抽象方法:在抽象类中声明,由具体类实现;
  • 基本方法 - 钩子方法:在抽象类中实现,包含一种用于判断的方法、一种由具体类重写的空方法。

step2:创建具体类,实现抽象类中定义的抽象方法和钩子方法;



使用示例


这里还是为网购为例,简单阐述如何使用模版方法模式。我们在网购时,基本流程都是先选择一个商品、确定样式或型号、下单支付(也可能会和购物车其它商品一起支付)、收货(如果不满意还可退换货)、终止一单网购交易。当然这个过程可能还会掺杂多种其它情况,我们将购物流程简化为:选择商品、下单支付、收货(退换货)、终止交易,实现如下。
 代码实现


// 创建抽象父类,定义算法骨架public abstract class TMAbstractClass {
    public final void shopOnline() {        selectItems();        checkoutItems();        if(isReturnItemsHook1()) {            returnItems();        } else if(isExchangeItemsHook2()) {            exchangeItems();            //假设一次性换到满意的商品            confirmTheReceipt();        } else {            confirmTheReceipt();        }        terminateTransaction();    }
    protected abstract void selectItems();
    protected void checkoutItems() {        System.out.println("下单支付");    }
    public boolean isReturnItemsHook1() {        return false;    }    protected void returnItems() {    }
    public boolean isExchangeItemsHook2() {        return false;    }    protected void exchangeItems() {    }
    protected void confirmTheReceipt() {        System.out.println("确认收货");    }
    protected void terminateTransaction() {        System.out.println("终止交易");    }
}
// 定义具体子类1:购买商品1public class TMConcreteClass1 extends TMAbstractClass{
    @Override    protected void selectItems() {        System.out.println("选择商品1");    }
    @Override    public boolean isReturnItemsHook1() {        return true;    }
    @Override    protected void returnItems() {        System.out.println("退货退款");    }
}
// 定义具体子类2:购买商品2public class TMConcreteClass2 extends TMAbstractClass{
    @Override    protected void selectItems() {        System.out.println("选择商品2");    }
    @Override    public boolean isExchangeItemsHook2() {        return true;    }
    @Override    protected void exchangeItems() {        System.out.println("换货");    }}
//客户端调用public class userPayForItem() {    public static void main(String[] args) {        System.out.println("购物记录1:");        TMAbstractClass shopRecord1 = new TMConcreteClass1();        shopRecord1.shopOnline();        System.out.println();        System.out.println("购物记录2:");        TMConcreteClass2 shopRecord2 = new TMConcreteClass2();        shopRecord2.shopOnline();          }}


 结果输出

购物记录1:选择商品1下单支付退货退款终止交易
购物记录2:选择商品2下单支付换货确认收货终止交易


 UML图

image.png

模板方法模式在框架源码中使用也很广泛,比如:JDK 源码中 AbstractList 抽象类、Mybatis 源码中 BaseExecutor 抽象类。本文以 AbstractList 为例,分析模版方法模式在源码中如何应用。AbstractList 是 ArrayList 的父类,也就是模版类,它包含的方法有很多,这里主要介绍一下 addAll() 方法。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {    public boolean addAll(int index, Collection<? extends E> c) {        rangeCheckForAdd(index);        boolean modified = false;        for (E e : c) {            add(index++, e);            modified = true;        }        return modified;    }}


事实上,AbstractList 中的方法除了一些私有方法不能被子类访问,大多数方法都和 addAll() 一样,可由子类选择是否修改:如果子类需要做个性化的实现就要修改,如果不需要则直接按照父类方法的逻辑执行。

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    public boolean addAll(int index, Collection<? extends E> c) {        rangeCheckForAdd(index);
        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount
        int numMoved = size - index;        if (numMoved > 0)            System.arraycopy(elementData, index, elementData, index + numNew,                             numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);        size += numNew;        return numNew != 0;    }}


另外,AbstractList 中有一个 get() 方法,明确要求子类修改实现,如下所示:

private final AbstractList<E> l;private final int offset;
abstract public E get(int index);
public E get(int index) {        rangeCheck(index);        checkForComodification();        return l.get(index+offset);}


左边是父类AbstractList中的,右边是ArrayList中的方法。在父类中没有直接写出实现代码,而是让子类自己手动去实现。除此之外其实还有一个方法就是AbstractList父类AbstractCollection中的toString方法。在ArrayList中是没有的,但是平常在写代码时候,是可以直接调用的,这就是一个公共的方法。



优缺点及适用场景


▐  优点

  1. 封装不变部分, 扩展可变部分。把认为是不变部分的算法封装到父类实现, 而可变部分的则可以通过继承来继续扩展。
  2. 提取公共部分代码, 便于维护。
  3. 行为由父类控制, 子类实现。基本方法是由子类实现的, 因此子类可以通过扩展的方式增加相应的功能, 符合开闭原则。


 缺点

  1. 子类执行结果影响父类结果,这违背了我们平时设计代码的习惯,在复杂项目中,很可能会带来阅读上的难度。
  2. 可能引起子类泛滥、为了继承而继承的问题。


 适用场景

  1. 算法的整体步骤相对固定、其中个别方法容易变化时,这时候可以使用模板方法模式,将易变部分抽象出来,供子类实现。
  2. 当多个子类存在公共行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,即可进行个性化的功能扩展。


总结

模板方法模式只需要简单的继承关系就可以完成,是一种比较简单易用的设计模式。我们在平常写代码时,很可能也会经常使用模板方法模式。根据上文所述,如果我们希望子类不要修改父类的方法,只需要加上 final 修饰即可;如果希望子类一定重写父类的方法,就将父类的方法用 abstract 修饰;如果子类可以修改也可以不修改,就可以参照 addAll() 方法那样设计。模板方法的重点是要理解模板,这个模板需要尽量使用抽象类。因为抽象类比接口更加灵活,能更好得定义模板。

相关文章
|
设计模式 算法
二十三种设计模式全面解析-深入解析模板方法模式的奇妙世界
二十三种设计模式全面解析-深入解析模板方法模式的奇妙世界
164 1
|
设计模式 存储 算法
行为型设计模式02-模板方法模式
行为型设计模式02-模板方法模式
121 0
|
设计模式 算法
设计模式9 - 模板方法模式【Template Method Pattern】
设计模式9 - 模板方法模式【Template Method Pattern】
95 0
|
7月前
|
设计模式 数据采集 算法
【设计模式】【行为型模式】模板方法模式(Template Method)
一、入门 1.1、什么是模板方法模式? 模板模式(Template Method Pattern)是一种行为设计模式,它定义了一个算法的框架,并允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
250 13
|
10月前
|
设计模式 SQL 算法
「全网最细 + 实战源码案例」设计模式——模板方法模式
模板方法模式是一种行为型设计模式,定义了算法的骨架并在父类中实现不变部分,将可变部分延迟到子类实现。通过这种方式,它避免了代码重复,提高了复用性和扩展性。具体步骤由抽象类定义,子类实现特定逻辑。适用于框架设计、工作流和相似算法结构的场景。优点包括代码复用和符合开闭原则,缺点是可能违反里氏替换原则且灵活性较低。
260 7
「全网最细 + 实战源码案例」设计模式——模板方法模式
|
设计模式 算法 Java
Java设计模式-模板方法模式(14)
Java设计模式-模板方法模式(14)
122 1
|
设计模式 JavaScript 算法
js设计模式【详解】—— 模板方法模式
js设计模式【详解】—— 模板方法模式
140 6
|
设计模式 算法 Java
Java 设计模式:深入模板方法模式的原理与应用
【4月更文挑战第27天】模板方法模式是一种行为设计模式,主要用于定义一个操作中的算法的框架,允许子类在不改变算法结构的情况下重定义算法的某些特定步骤。
163 1
|
设计模式 Go
[设计模式 Go实现] 行为型~模板方法模式
[设计模式 Go实现] 行为型~模板方法模式
105 2
|
设计模式 算法 关系型数据库
设计模式第七讲-外观模式、适配器模式、模板方法模式详解
系统要求所有的数据库帮助类必须实现ISqlHelp接口,面向该接口编程,如SQLServerHelp类。 此时第三方提供了一个新的MySql的帮助类(假设是dll,不能修改),它的编程规范和ISqlHelp不兼容,这个时候就需要引入适配器类,使二者能相互兼容。
265 0

热门文章

最新文章