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

2024-02-29 13:48:45 浏览数 (1)

本文字数:2579字,阅读大约需要 8 分钟。

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

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

使用场景

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

  • 当存在一组相似的操作,它们具有相同的算法结构,但实现细节各不相同时。
  • 当希望在不改变算法的整体结构的情况下,允许子类自由扩展或修改某些步骤时。
  • 当希望将算法的实现细节封装起来,只暴露出高层接口供调用者使用时。

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

代码语言:javascript复制
    /**
     * 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):具体子类继承抽象类,并根据需要实现其中的抽象方法。具体子类提供了算法的具体实现细节。

示例代码

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

代码语言:javascript复制
// 抽象类,定义模板方法和抽象步骤方法
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(),从而执行了定义好的算法。

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

代码语言:javascript复制
ConcreteClass: Step 1
ConcreteClass: Step 2
ConcreteClass: Step 3

注意:

  • 一般模板方法都加上 final 关键字, 防止子类重写模板方法。
  • 抽象模板中的基本方法尽量设计为 protected 类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为 protected 类型。实现类若非必要,尽量不要扩大父类中的访问权限。

钩子方法

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

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

代码语言:javascript复制
// 抽象类,定义模板方法和钩子方法
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();
}

// 具体子类1
public 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");
    }
}

// 具体子类2
public 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 中,我们分别创建了具体子类的对象,并调用其模板方法,从而执行了定义好的算法。

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

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

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

优缺点

优点

  • 代码复用:模板方法模式通过将算法的通用结构定义在抽象类中,可以使子类直接继承这些通用部分,从而达到代码复用的目的。
  • 扩展性:模板方法模式允许子类根据需要重写父类的某些步骤,从而实现对算法的自由扩展和修改,同时保持整体结构的稳定性。
  • 封装性:模板方法模式将算法的实现细节封装在抽象类中,对调用者屏蔽了具体的实现细节,只暴露出高层接口。

缺点

  • 模板方法模式将算法的执行流程固定在抽象类中,可能会导致代码的可读性降低,增加理解和维护的难度。
  • 模板方法中的步骤越多, 其维护工作就可能会越困难。
  • 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。

总结

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

希望这篇文章能给你带来收获和思考,如果你也有可借鉴的经验和深入的思考,欢迎评论区留言讨论。

0 人点赞