设计模式 -- 模板方法模式

模板方法模式是一种行为设计模式,它定义了操作中的算法骨架,允许子类在不改变算法结构的情况下重定义特定步骤。文章通过炒菜的例子展示了如何使用模板方法模式,包括定义抽象类、具体方法、抽象方法和钩子方法。优点在于提高代码复用性和实现反向控制,缺点则是可能导致类数量增加和代码可读性下降。适用场景包括算法步骤固定但部分可变的情况,以及需要子类决定父类算法中某些步骤是否执行的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

结构:

抽象类(Abstract Class):负责给出一个算法的轮廓以及骨架。它由一个模板方法和若干个基本方法构成。

模板方法:定义了算法的骨架,按照某种顺序调用其包含的基本方法

基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。分为以下三种

1. 抽象方法(Abstract Method):一个抽象方法由抽象类声明,由其具体子类实现
2. 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现。其子类可以进行覆盖也可以直接继承
3. 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。【一般钩子方法是用于判断的逻辑方法,这类方法一般定义为isXXX 布尔返回】

具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

案例实现:

模拟炒菜。炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。

/**
 * 模板模式 抽象类
 * 抽象模板类
 */
public abstract class AbstractClass {

    /**
     * 模板方法定义 炒菜必须按照以下模板顺序,不可被重写更改
     */
    public final void cookProcess(){
        pourOil();
        heatOil();
        pourVegetables();
        pourSauce();
        fry();
    }

    /**
     * 基本方法 炒所有菜都是一样的
     */
    public void pourOil(){
        System.out.println("倒油");
    }

    /**
     * 基本方法 炒所有菜都是一样的
     */
    public void heatOil(){
        System.out.println("热油");
    }

    /**
     * 抽象方法 倒蔬菜是不一样的 【一个下包菜,一个下菜心】
     */
    public abstract void pourVegetables();

    /**
     * 倒调味料 也是不一样的
     */
    public abstract void pourSauce();

    /**
     * 基本方法 炒所有菜都是一样的
     */
    public void fry(){
        System.out.println("翻炒翻炒 炒啊炒熟了");
    }
}
/**
 * 模板方法模式 具体子类
 * 炒包菜
 */
public class FryCabbage extends AbstractClass {

    @Override
    public void pourVegetables() {
        System.out.println("倒入包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("加盐 加生抽 加干辣椒");
    }
}


/**
 * 模板方法模式 具体子类
 * 炒菜心
 */
public class FryCaiXin extends AbstractClass {

    @Override
    public void pourVegetables() {
        System.out.println("倒入菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("加盐 加蒜末 加葱花");
    }
}

优缺点:

优点:1. 提高代码的复用性,将相同的代码放在父类中,将不同的方法放在子类中2. 实现反向控制,通过父类调用子类的操作,通过子类达到具体实现拓展不同的行为。如果需要炒一个新的菜直接继承即可,只需要重写具体方法,实现了反向控制,也符合开闭原则。

缺点:1. 对于每个不同的实现类都需要定义一个子类,导致类个数增加,系统更庞大。2. 父类的抽象方法由子类实现,子类的执行结果会影响父类的结果。反向控制,提高代码可读难度

使用场景:

  • 算法的整体步骤很固定,但其中个别的部分易变时,可以使用模板方法模式。将易变的方法抽象出来,供子类实现。

  • 需要通过子类来决定父类中的算法中某个步骤是否执行,实现子类对父类的反向控制【钩子方法】。

钩子方法含义:

#### 方式一

最简单的钩子方法就是空方法,代码如下:
public virtual void Display() { }
#### 方式二

另一种钩子方法可以实现对其他方法进行约束,这种钩子方法通常返回一个 bool 类型,即返回 true 或 false,用来判断是否执行某一个基本方法,下面通过一个实例来说明这种钩子方法的使用。

代码如下:**抽象父类**
public abstract class AbstractClass {

    public abstract boolean isOpen();

    public final void operating() {
        if(isOpen()) {
            System.out.println("钩子方法开启");
        }else {
            System.out.println("钩子方法关闭");
        }
    }
}
**具体类**
public class AchieveClass extends AbstractClass {

  //钩子方法能挂在到operating能干预到operating业务逻辑
    @Override
    public boolean isOpen() {
        return true;
    }

    public static void main(String[] args) {
        AchieveClass ac = new AchieveClass();
        ac.operating();
    }

}
只要重写isOpen就能干预父类方法的业务流程。相当于将isOpen挂载在了父类的operating()中。

所以子类是可以对父类实现反向控制的。

JDK源码分析:

inputStream 的 read方法 ,简短摘要如下

public abstract class InputStream implements Closeable {
    
    //抽象 read 方法,由子类实现,模板方法模式 抽象方法
    public abstract int read() throws IOException;
    
    //方法重载,一个参数的调用了下面三个参数的 具体方法
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    
    //模板方法定义
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();//在这里调用了子类的实现,下方按照长度一个个for循环放到数组中,是模板顺序
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值