• 详解设计模式:模版方法模式


    模板方法模式(Template Method Pattern)也被称为模板模式(Template Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。

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

    本片文章内容包括:关于模版方法模式、观察者模式 Demo、模版方法模式的应用(InputStream 中的模版方法模式)



    一、关于模版方法模式

    1、关于模版方法模式

    模板方法模式(Template Method Pattern)也被称为模板模式(Template Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。

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

    模板方法模式 核心:处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用模版方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤在父类中定义好,具体的实现延迟到子类中定义。

    2、关于模版方法模式的构成

    模版方法模式主要由 2 种角色构成:

    • 抽象类/抽象模板(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成
    • 具体子类/具体实现(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤
    3、关于抽象模板的方法组成

    抽象模板类,由一个模板方法和若干个基本方法构成:

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

    基本方法:是整个算法中的一个步骤,包含以下几种类型:

    • 抽象方法(abstract method):在抽象类中声明,由具体子类实现;
    • 具体方法(specific method):在抽象类中已经实现,在具体子类中可以继承或重写它;
    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
    4、关于模版方法模式的XML

    image-20221203055215523

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

    # 模版方法模式的优点

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

    # 模版方法模式的缺点

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

    二、观察者模式 Demo

    1、Demo 设计

    炒菜的步骤是固定的,我们可以将其简化为倒油、热油、下蔬菜、下酱料、翻炒等步骤,现通过模板方法模式来用代码模拟。

    Demo 类图如下:

    在这里插入图片描述

    2、Demo 实现

    # AbstractClass 抽象模板类

    public abstract class AbstractClass {
        
        /**
         * 模板方法
         */
        public final void cookProcess() {
            //第一步:倒油
            this.pourOil();
            //第二步:热油
            this.heatOil();
            //第三步:倒蔬菜
            this.pourVegetable();
            //第四步:倒调味料
            this.pourSauce();
            //第五步:翻炒
            this.fry();
        }
    
        /**
         * 第一步:倒油 具体方法,直接实现
         */
        public void pourOil() {
            System.out.println("倒油");
        }
    
        /**
         * 第二步:热油 具体方法,直接实现
         */
        public void heatOil() {
            System.out.println("热油");
        }
    
        /**
         * 第三步:倒蔬菜 抽象方法
         */
        public abstract void pourVegetable();
    
        /**
         * 第四步:倒调料 抽象方法
         */
        public abstract void pourSauce();
    
        /**
         * 第五步:翻炒 具体方法,直接实现
         */
        public void fry() {
            System.out.println("翻炒");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    # ConcreteClass 具体子类

    public class ConcreteClass_BaoCai extends AbstractClass {
    
        @Override
        public void pourVegetable() {
            System.out.println("下锅的蔬菜是包菜");
        }
    
        @Override
        public void pourSauce() {
            System.out.println("下锅的酱料是辣椒");
        }
    }
    public class ConcreteClass_CaiXin extends AbstractClass {
    
        @Override
        public void pourVegetable() {
            System.out.println("下锅的蔬菜是菜心");
        }
    
        @Override
        public void pourSauce() {
            System.out.println("下锅的酱料是蒜蓉");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    3、Demo 测试
    public class Client {
        public static void main(String[] args) {
            //炒手撕包菜
            ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
            baoCai.cookProcess();
    
            //炒蒜蓉菜心
            ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
            caiXin.cookProcess();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三、模版方法模式的应用(InputStream 中的模版方法模式)

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

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

  • 相关阅读:
    bean的依赖注入
    LVGL_文件系统FS
    Java之CloneNotSupportedException
    通过条件竞争实现内核提权
    JK405R-SOP16录音芯片ic方案的功能简介,可以内置录音30秒-高采样率
    深度学习篇之tensorflow(3) ---架构介绍篇二
    抽奖基本算法讨论
    Latex——换行换段
    Vue开发好伙伴--Visual Studio Code编辑器
    shell-数组和关联数组
  • 原文地址:https://blog.csdn.net/weixin_45187434/article/details/128159860