设计模式之模板方法模式

模板方法模式属于行为型模式,定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,不同的子类可以以不同的方式实现这些抽象方法。从而对剩余逻辑有不同的实现。模版方法模式是基于继承的代码复用的基本技术,模版方法模式的结构和用法也是面向对象设计的核心。

模板方法模式的UML类图如下:

从上图可以看出,模板方法模式主要有抽象类角色、具体子类角色两种角色:

  • 抽象类角色:

    定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。

    定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。

  • 具体子类角色:

    实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。

    每一个抽象模版角色都可以有任意多个具体模版角色与之对应,而每一个具体模版角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤〉的不同实现,从而使得顶级逻辑的实现各不相同。

银行存款例子

一个系统需要计算存款利息,主要有两种存款账号,即货币市场账户(Money Market ACcount)和定期账户(CDAccount);这两种账户的存款利率是不通的,因此,在计算一个账户的存款利息额的时候,必须分区两种不同的账户类型。

那么在这个系统中,主要有两个方法,一是确定账户的类型,二是确定利息的百分比。在这里就可以使用模板方法在抽象类中定义两个基本方法,然后有子类根据账号类型的不同而给出具体的方法。

其UML类图如下:

抽象类:

package com.charon.template;

/**
 * @className: Account
 * @description:
 * @author: charon
 * @create: 2022-03-26 14:57
 */
public abstract class Account {

    private String accountNum;

    /**
     * 无参构造,帮助子类初始化
     * 如果没有显示声明父类的无参的构造方法,系统会自动默认生成一个无参构造方法。
     * 但是,如果声明了一个有参的构造方法,而没有声明无参的构造方法,这时系统不会动默认生成一个无参构造方法。
     */
    public Account() {
    }

    public Account(String accountNum) {
        this.accountNum = accountNum;
    }

    /**
     * 模板方法,计算利息的数额
     * 使用final修饰,可以防止子类重写模板方法
     * @return
     */
    public final double calcInterest(){
        double interestRate = calcInterestRate();
        String accountType = calcAccountType();
        double amount = getAmount(accountType,accountNum);
        return interestRate * amount;
    }

    /**
     * 获取账户金额
     * @param accountType 账户类型
     * @param accountNum 账号
     * @return
     */
    protected double getAmount(String accountType, String accountNum){
        // 从数据库中获取该账户的金额,这里直接返回一个
        return 5000;
    }

    /**
     * 计算账户类型
     */
    protected abstract String calcAccountType();

    /**
     * 计算利息利率
     * @return
     */
    protected abstract double calcInterestRate();
}

具体子类:

package com.charon.template;

/**
 * @className: MoneyMarketAccount
 * @description:
 * @author: charon
 * @create: 2022-03-26 15:12
 */
public class MoneyMarketAccount extends Account{

    @Override
    protected String calcAccountType() {
        return "Money Market";
    }

    @Override
    protected double calcInterestRate() {
        return 0.045;
    }
}


package com.charon.template;

/**
 * @className: CDAccount
 * @description:
 * @author: charon
 * @create: 2022-03-26 15:22
 */
public class CDAccount extends Account {
    @Override
    protected String calcAccountType() {
        return "CD";
    }

    @Override
    protected double calcInterestRate() {
        return 0.065;
    }
}

客户端调用:

package com.charon.template;

/**
 * @className: Client
 * @description: 
 * @author: charon
 * @create: 2022-03-25 23:44
 */
public class Client {

    public static void main(String[] args) {
        Account mmAccount = new MoneyMarketAccount();
        System.out.println("货币市场账号的利息是:" + mmAccount.calcInterest());

        Account cdAccount = new CDAccount();
        System.out.println("定期账号的利息是:" + cdAccount.calcInterest());
    }
}

打印:
    货币市场账号的利息是:225.0
    定期账号的利息是:325.0	

模板方法模式的主要优点如下:

  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合“开闭原则”。

模板方法模式的主要缺点如下:

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

模式的应用场景

模板方法模式通常适用于以下场景。

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
posted @ 2022-03-26 15:50  pluto_charon  阅读(415)  评论(0编辑  收藏  举报