写点什么

一文搞懂设计模式—模板方法模式

作者:码农BookSea
  • 2024-02-21
    浙江
  • 本文字数:3540 字

    阅读完需:约 12 分钟

一文搞懂设计模式—模板方法模式

本文已收录至 Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录


模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),是一种行为设计模式,它定义了一个操作中的算法框架,将某些步骤的具体实现留给子类。通过模板方法模式,我们可以在不改变算法结构的情况下,允许子类重新定义某些步骤,从而实现代码复用和扩展。


在软件开发中,我们经常会遇到需要定义一组相似操作的场景。这些操作可能在整体上有着相同的结构,但在细节上有所差异。如果每次都重复编写这些操作的通用结构,会导致代码的冗余性,同时也增加了后期维护的难度。为了解决这个问题,模板方法模式应运而生。

使用场景

模板方法模式适用于以下场景:


  • 当存在一组相似的操作,它们具有相同的算法结构,但实现细节各不相同时。

  • 当希望在不改变算法的整体结构的情况下,允许子类自由扩展或修改某些步骤时。

  • 当希望将算法的实现细节封装起来,只暴露出高层接口供调用者使用时。


JUC 下的 AQS 就使用到了模板方法模式,其中 acquire() 是模板方法。tryAcquire() 方法的具体实现去交给子类完成。


    /**     * Acquires in exclusive mode, ignoring interrupts.  Implemented     * by invoking at least once {@link #tryAcquire},     * returning on success.  Otherwise the thread is queued, possibly     * repeatedly blocking and unblocking, invoking {@link     * #tryAcquire} until success.  This method can be used     * to implement method {@link Lock#lock}.     *     * @param arg the acquire argument.  This value is conveyed to     *        {@link #tryAcquire} but is otherwise uninterpreted and     *        can represent anything you like.     */    public final void acquire(int arg) {        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
复制代码

实现方式

结构说明


模板方法模式由抽象类和具体子类组成。抽象类定义了算法的框架,其中包含了一个或多个抽象方法,用于由具体子类实现。具体子类继承抽象类,并根据需要重写其中的抽象方法,从而实现具体的细节。


在模板方法模式中,通常涉及以下几个角色:


  • 抽象类(Abstract Class):抽象类定义了算法的框架,包括一个或多个抽象方法和具体方法。其中的抽象方法由子类实现,具体方法可以被子类直接继承或重写。

  • 具体子类(Concrete Subclass):具体子类继承抽象类,并根据需要实现其中的抽象方法。具体子类提供了算法的具体实现细节。


示例代码


以下是一个简单的代码示例:


// 抽象类,定义模板方法和抽象步骤方法public abstract class AbstractClass {    // 模板方法,定义算法的整体结构    public final void templateMethod() {        step1();        step2();        step3();    }     // 模板公共方法    protected final void step1(){      System.out.println("ConcreteClass: Step 1");    }    // 抽象步骤方法,由子类实现具体的步骤逻辑    protected abstract void step2();    // 抽象步骤方法,由子类实现具体的步骤逻辑    protected abstract void step3();}
// 具体子类,实现抽象步骤方法public class ConcreteClass extends AbstractClass { protected void step2() { System.out.println("ConcreteClass: Step 2"); }
protected void step3() { System.out.println("ConcreteClass: Step 3"); }}
// 客户端代码public class Client { public static void main(String[] args) { AbstractClass abstractClass = new ConcreteClass(); abstractClass.templateMethod(); }}
复制代码


在上述代码中,我们首先定义了一个抽象类 AbstractClass,其中包含了模板方法和抽象方法。然后,我们创建了具体子类 ConcreteClass,根据需要实现了抽象方法。


在客户端代码 Client 中,我们创建了具体子类的对象,并调用了模板方法 templateMethod(),从而执行了定义好的算法。


运行该代码将输出以下结果:


ConcreteClass: Step 1ConcreteClass: Step 2ConcreteClass: Step 3
复制代码


注意:


  • 一般模板方法都加上 final 关键字, 防止子类重写模板方法。

  • 抽象模板中的基本方法尽量设计为 protected 类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为 protected 类型。实现类若非必要,尽量不要扩大父类中的访问权限。

钩子方法

钩子方法(Hook Method)是模板方法模式中的一种特殊方法,用于在抽象类中提供一个默认的实现,但允许具体子类选择性地进行重写或扩展。钩子方法允许子类在不改变算法骨架的情况下,对算法的某些步骤进行定制。


以下是一个包含钩子方法的 Java 示例代码:


// 抽象类,定义模板方法和钩子方法public abstract class AbstractClass {    // 模板方法,定义算法的整体结构    public final void templateMethod() {        step1();        step2();    // 钩子方法的调用        if (hookMethod()) {              step3();        }    }
protected abstract void step1();
protected abstract void step2();
// 钩子方法,默认返回true,子类可以选择性地重写 protected boolean hookMethod() { return true; }
protected abstract void step3();}
// 具体子类1public class ConcreteClass1 extends AbstractClass { protected void step1() { System.out.println("ConcreteClass1: Step 1"); }
protected void step2() { System.out.println("ConcreteClass1: Step 2"); }
protected void step3() { System.out.println("ConcreteClass1: Step 3"); }}
// 具体子类2public class ConcreteClass2 extends AbstractClass { protected void step1() { System.out.println("ConcreteClass2: Step 1"); }
protected void step2() { System.out.println("ConcreteClass2: Step 2"); }
protected boolean hookMethod() { return false; // 重写钩子方法,返回false }
protected void step3() { System.out.println("ConcreteClass2: Step 3"); }}
// 客户端代码public class Client { public static void main(String[] args) { AbstractClass class1 = new ConcreteClass1(); class1.templateMethod();
System.out.println("------------------");
AbstractClass class2 = new ConcreteClass2(); class2.templateMethod(); }}
复制代码


在上述代码中,我们定义了一个抽象类 AbstractClass,其中包含模板方法 templateMethod() 和钩子方法 hookMethod()。在模板方法中,我们先执行了step1() step2() 两个基本操作方法,然后通过调用钩子方法决定是否执行 step3()


具体子类 ConcreteClass1ConcreteClass2 继承了抽象类,并实现了基本操作方法 step1()step2() 和钩子方法 hookMethod()step3()


在客户端代码 Client 中,我们分别创建了具体子类的对象,并调用其模板方法,从而执行了定义好的算法。


运行该示例代码将输出以下结果:


ConcreteClass1: Step 1ConcreteClass1: Step 2ConcreteClass1: Step 3------------------ConcreteClass2: Step 1ConcreteClass2: Step 2
复制代码


通过重写钩子方法,具体子类可以选择性地对算法进行定制化。这就展示了钩子方法在模板方法模式中的应用。

优缺点

优点


  • 代码复用:模板方法模式通过将算法的通用结构定义在抽象类中,可以使子类直接继承这些通用部分,从而达到代码复用的目的。

  • 扩展性:模板方法模式允许子类根据需要重写父类的某些步骤,从而实现对算法的自由扩展和修改,同时保持整体结构的稳定性。

  • 封装性:模板方法模式将算法的实现细节封装在抽象类中,对调用者屏蔽了具体的实现细节,只暴露出高层接口。


缺点


  • 模板方法模式将算法的执行流程固定在抽象类中,可能会导致代码的可读性降低,增加理解和维护的难度。

  • 模板方法中的步骤越多, 其维护工作就可能会越困难。

  • 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。

总结

模板方法是一种简单但非常实用的设计模式,它通过定义一个算法的框架,并将具体实现延迟到子类中,实现了代码复用和扩展的目的。在具体实现步骤相对固定、但又存在差异性的情况下,模板方法模式能够很好地解决代码重复和维护难度的问题。

发布于: 刚刚阅读数: 5
用户头像

码农BookSea

关注

Java开发工程师 2021-12-26 加入

Java开发菜鸟工程师,写博客的初衷是为了沉淀我所学习,累积我所见闻,分享我所体验。希望和更多的人交流学习。

评论

发布
暂无评论
一文搞懂设计模式—模板方法模式_Java_码农BookSea_InfoQ写作社区