设计模式学习13(Java实现)——模板方法模式

写在前面

  • 记录学习设计模式的笔记
  • 提高对设计模式的灵活运用

学习地址

https://www.bilibili.com/video/BV1G4411c7N4

https://www.bilibili.com/video/BV1Np4y1z7BU

参考文章

http://c.biancheng.net/view/1317.html

项目源码
https://gitee.com/zhuang-kang/DesignPattern

15,模板方法模式

15.1 模板方法模式的定义和特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,它是一种类行为型模式。

该模式的主要优点如下。

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

该模式的主要缺点如下。

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

15.2 模板方法模式的结构与实现

15.2.1 模板方法模式的结构

模板方法(Template Method)模式包含以下主要角色:

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

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

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

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

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

image

15.2.2 代码实现

AbstractClass

package com.zhuang.template;

/**
 * @Classname AbstractClass
 * @Description 抽象类
 * @Date 2021/3/26 20:06
 * @Created by dell
 */

public abstract class AbstractClass {
    public final void work() {
        //起床
        this.wake();
        //刷牙
        this.brush();
        //吃早饭
        this.breakfast();
        //交通工具
        this.transport();
        //睡觉
        this.sleep();
    }

    //步骤一样 直接实现
    public void wake() {
        System.out.println("起床...");
    }

    //步骤一样 直接实现
    public void brush() {
        System.out.println("刷牙...");
    }

    // 步骤不一样 (一个是吃面包 一个是喝牛奶)
    public abstract void breakfast();

    // 步骤不一样 (一个是开车 一个是坐地铁)
    public abstract void transport();

    // 步骤一样 直接实现
    public void sleep() {
        System.out.println("睡觉...");
    }

}

ConcreteClass_BreakFast

package com.zhuang.template;

/**
 * @Classname ConcreteClass_BreakFast
 * @Description 具体类 早饭类 继承
 * @Date 2021/3/26 20:13
 * @Created by dell
 */

public class ConcreteClass_BreakFast extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("吃面包...");
    }

    @Override
    public void transport() {
        System.out.println("坐公交...");
    }
}

ConcreteClass_Transport

package com.zhuang.template;

/**
 * @Classname ConcreteClass_Transport
 * @Description 交通工具类 继承
 * @Date 2021/3/26 20:14
 * @Created by dell
 */

public class ConcreteClass_Transport extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("喝牛奶...");
    }

    @Override
    public void transport() {
        System.out.println("乘地铁...");
    }
}

Client

package com.zhuang.template;

/**
 * @Classname Client
 * @Description 模板方法模式 测试类
 * @Date 2021/3/26 20:16
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        //吃面包 坐公交
        System.out.println("周一");
        AbstractClass breakFast = new ConcreteClass_BreakFast();
        breakFast.work();

        System.out.println("========================");
        System.out.println("周五");
        AbstractClass transport = new ConcreteClass_Transport();
        transport.work();
    }
}
image

钩子方法

AbstractClass

package com.zhuang.template.hook_method;

/**
 * @Classname AbstractClass
 * @Description 抽象类
 * @Date 2021/3/26 20:06
 * @Created by dell
 */

public abstract class AbstractClass {
    public final void work() {
        //起床
        this.wake();
        //刷牙
        this.brush();
        //吃早饭
        this.breakfast();
        //交通工具
        if (isSunday()) {
            this.transport();
        }
        //睡觉
        this.sleep();
    }

    //步骤一样 直接实现
    public void wake() {
        System.out.println("起床...");
    }

    //步骤一样 直接实现
    public void brush() {
        System.out.println("刷牙...");
    }

    // 步骤不一样 (一个是吃面包 一个是喝牛奶)
    public abstract void breakfast();

    // 步骤不一样 (一个是开车 一个是坐地铁)
    public abstract void transport();

    // 步骤一样 直接实现
    public void sleep() {
        System.out.println("睡觉...");
    }

    //钩子方法 是否为周末 周末不用交通工具
    boolean isSunday() {
        return false;
    }

}

ConcreteClass_BreakFast

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_BreakFast
 * @Description 具体类 早饭类 继承
 * @Date 2021/3/26 20:13
 * @Created by dell
 */

public class ConcreteClass_BreakFast extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("吃面包...");
    }

    @Override
    public void transport() {
        System.out.println("坐公交...");
    }
}

ConcreteClass_Transport

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_Transport
 * @Description 交通工具类 继承
 * @Date 2021/3/26 20:14
 * @Created by dell
 */

public class ConcreteClass_Transport extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("喝牛奶...");
    }

    @Override
    public void transport() {
        System.out.println("乘地铁...");
    }
}

ConcreteClass_Sunday

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_Sunday
 * @Description 周末 不用上班 空实现交通方法
 * @Date 2021/3/26 20:28
 * @Created by dell
 */

public class ConcreteClass_Sunday extends AbstractClass{

    @Override
    public void breakfast() {
        System.out.println("吃面包或者喝牛奶...");
    }

    @Override
    public void transport() {
        //空实现
    }

    @Override
    boolean isSunday() {
        System.out.println("今天周末,休息...");
        return true;
    }
}

Client

package com.zhuang.template.hook_method;

/**
 * @Classname Client
 * @Description 模板方法 测试钩子方法
 * @Date 2021/3/26 20:26
 * @Created by dell
 */

public class Client {

    public static void main(String[] args) {
        AbstractClass sunday = new ConcreteClass_Sunday();
        sunday.work();
    }
}

15.3 模板方法模式的应用场景

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

15.4 JDK源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

public abstract class InputStream implements Closeable {
    //抽象方法,要求子类必须重写
    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(); //调用了无参的read方法,该方法是每次读取一个字节数据
        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;
    }
}

从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。

在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现

写在最后

  • 如果我的文章对你有用,请给我点个👍,感谢你😊!
  • 有问题,欢迎在评论区指出!💪
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容