设计模式学习(六):Template Method模板方法模式

简介: 模板的原意是指带有镂空文字的薄薄的塑料板。只要用笔在模板的镂空处进行临摹,即使是手写也能写出整齐的文字,但是具体写出的文字是什么感觉则依赖于所用的笔。如果使用签字笔来临摹,则可以写出签字似的文字;如果使用铅笔来临摹,则可以写出铅笔字;而如果是用彩色笔临摹,则可以写出彩色的字。但是无论使用什么笔,文字的形状都会与模板上镂空处的形状一致。

一、什么是Template Method模式



模板的原意是指带有镂空文字的薄薄的塑料板。只要用笔在模板的镂空处进行临摹,即使是手写也能写出整齐的文字,但是具体写出的文字是什么感觉则依赖于所用的笔。如果使用签字笔来临摹,则可以写出签字似的文字;如果使用铅笔来临摹,则可以写出铅笔字;而如果是用彩色笔临摹,则可以写出彩色的字。但是无论使用什么笔,文字的形状都会与模板上镂空处的形状一致。


本文中所要学习的Template Method模式是带有模板功能的模式,组成模板的方法被定义在父类中。由于这些方法是抽象方法,所以只查看父类的代码是无法知道这些方法最终会进行何种具体处理的,唯一能知道的就是父类是如何调用这些方法的。


实现上述这些抽象方法的是子类。在子类中实现了抽象方法也就决定了具体的处理。也就是说,只要在不同的子类中实现不同的具体处理,当父类的模板方法被调用时程序行为也会不同。但是,不论子类中的具体实现如何,处理的流程都会按照父类中所定义的那样进行。


像这样在父类中定义处理流程的框架,在子类中实现具体处理的模式就称为Template Method模式。


用一句话来概括:将具体的处理交给子类。

1fd9947636c94a0dbc3d771117610f71.png


二、Template Method示例代码



这里的示例程序是一段将字符和字符串循环显示5次的简单程序。


2.1 各类之间的关系


类的功能:

fd4a0e4e4c79484aac521b2d97f97a95.png

类图:


8e19ee0da67f45cab3cefc64ca9e78af.png


2.2  AbstractDisplay类

       

通过查看AbstractDisplay类的代码,我们可以知道这3个方法都是抽象方法。也就是说,如果仅仅查看AbstractDisplay类的代码,我们无法知道这3个方法中到底进行了什么样的处理。这是因为open方法、print方法、close方法的实际处理被交给了AbstractDisplay类的子类。

     

这里将display用final来修饰,就是表示子类不能重写display方法。

public abstract class AbstractDisplay {
    public abstract void open();
    public abstract void print();
    public abstract void close();
    public final void display() {
        open();
        for (int i = 0; i < 5; i++) {
            print();
        }
        close();
    }
}


2.3 CharDisplay类

       

我们来看看子类之一的charDisplay类。由于CharDisplay类实现了父类AbstractDisplay类中的3个抽象方法 open、print、close,因此它并不是抽象类。这样,当dipslay方法被调用时,比如传入一个H,最终显示出来的会是:<<HHHHH>>

public class CharDisplay extends AbstractDisplay{
    private char ch;
    public CharDisplay(char ch) {
        this.ch = ch;
    }
    @Override
    public void open() {
        System.out.print("<<");
    }
    @Override
    public void print() {
        System.out.print(ch);
    }
    @Override
    public void close() {
        System.out.println(">>");
    }
}


2.4 StringDisplay类

       

让我们看看另外一个子类——StringDisplay类。与CharDisplay类一样,它也实现了open、 print、 close方法。


此时,如果dipslay方法被调用,结果会如何呢?假设我们向charDisplay的构造函数中传递的参数是"Hello,world ,"这个字符串,那么最终结果会像下面这样:

5dc58a91b7764e6f9a32f3775646b39f.png

public class StringDisplay extends AbstractDisplay{
    private String string;
    private int width;
    public StringDisplay(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }
    @Override
    public void open() {
        printLine();
    }
    @Override
    public void print() {
        System.out.println("|" + string + "|");
    }
    @Override
    public void close() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}


2.5 用于测试的Main方法

     

在该类中生成了CharDisplay类和StringDisplay类的实例,并调用了display方法。

public class Main {
    public static void main(String[] args) {
        AbstractDisplay d1 = new CharDisplay('H');
        AbstractDisplay d2 = new StringDisplay("Hello, world.");
        AbstractDisplay d3 = new StringDisplay("你好,世界。");
        //虽然都调用的是display方法,但是实际的程序行为
        //取决于CharDisplay和StringDisplay的具体实现
        d1.display();
        d2.display();
        d3.display();
    }
}


2.6 运行结果

       

虽然都调用的是display方法,但是实际的程序行为取决于CharDisplay和StringDisplay的具体实现 。

6e347ca59ed6462b8b529b924b91126a.png


三、拓展思路的要点



3.1 可以使逻辑处理通用化

       

使用Template Method模式究竟能带来什么好处呢?这里,它的优点是由于在父类的模板方法中编写了算法,因此无需在每个子类中再编写算法。


例如,我们没使用Template Method模式,而是使用文本编辑器的复制和粘贴功能编写了多个ConcreteClass角色。此时,会出现ConcreteClass1、ConcreteClass2、Concreteclass3等很多相似的类。编写完成后立即发现了Bug还好,但如果是过一段时间才发现在Concreteclass1中有Bug,该怎么办呢?这时,我们就必须将这个Bug 的修改反映到所有的ConcreteClass角色中才行。


而如果是使用Template Method模式进行编程,当我们在模板方法中发现 Bug时,只需要修改模板方法即可解决问题。


3.2 父类与子类之间的协作

       

在Template Method模式中,父类和子类是紧密联系、共同工作的。因此,在子类中实现父类中声明的抽象方法时,必须要理解这些抽象方法被调用的时机。在看不到父类的源代码的情况下,想要编写出子类是非常困难的。


3.3 父类与子类的一致性

       

在示例程序中,不论是CharDisplay的实例还是StringDisplay 的实例,都是先保存在AbstractDisplay类型的变量中,然后再来调用display方法的。


使用父类类型的变量保存子类实例的优点是,即使没有用instanceof等指定子类的种类,程序也能正常工作。


无论在父类类型的变量中保存哪个子类的实例,程序都可以正常工作,这种原则称为里氏替换原则(The Liskov Substitution Principle,LSP )。当然,LSP并非仅限于Template Method模式,它是通用的继承原则。


四、相关的设计模式



4.1 Factory Method模式


Factory Method模式是将Template Method模式用于生成实例的一个典型例子。

设计模式学习(七):Factory Method工厂模式_玉面大蛟龙的博客-CSDN博客


4.2 Strategy模式


在Template Method模式中,可以使用继承改变程序的行为。这是因为Template Method模式在父类中定义程序行为的框架,在子类中决定具体的处理。


与此相对的是Strategy模式,它可以使用委托改变程序的行为。与Template Method模式中改变部分程序行为不同的是,Strategy模式用于替换整个算法。


 设计模式学习(四):Strategy策略模式_玉面大蛟龙的博客-CSDN博客


五、思考题



题目

       

Java中的接口与抽象类很相似。接口同样也是抽象方法的集合,但是在TemplateMethod模式中,我们却无法使用接口来扮演AbstractClass角色,请问这是为什么呢?


答案:


这是因为TemplateMethod模式中的AbstractClass角色必须实现处理的流程。在抽象类中可以实现一部分方法(例如AbstractDisplay类中的display方法),但是在接口中是无法实现方法的。因此,在TemplateMethod模式中,无法用接口替代抽象类。


相关文章
|
3月前
|
设计模式 算法 Java
Java设计模式-模板方法模式(14)
Java设计模式-模板方法模式(14)
|
5月前
|
设计模式 JavaScript 算法
js设计模式【详解】—— 模板方法模式
js设计模式【详解】—— 模板方法模式
50 6
|
6月前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
48 2
|
6月前
|
设计模式 uml
设计模式学习心得之前置知识 UML图看法与六大原则(下)
设计模式学习心得之前置知识 UML图看法与六大原则(下)
46 2
|
6月前
|
设计模式 安全 Java
设计模式学习心得之五种创建者模式(1)
设计模式学习心得之五种创建者模式(1)
43 0
|
6月前
|
设计模式 数据可视化 程序员
设计模式学习心得之前置知识 UML图看法与六大原则(上)
设计模式学习心得之前置知识 UML图看法与六大原则(上)
48 0
|
7月前
|
设计模式 安全 Java
【设计模式】JAVA Design Patterns——Curiously Recurring Template Pattern(奇异递归模板模式)
该文介绍了一种C++的编程技巧——奇异递归模板模式(CRTP),旨在让派生组件能继承基本组件的特定功能。通过示例展示了如何创建一个`Fighter`接口和`MmaFighter`类,其中`MmaFighter`及其子类如`MmaBantamweightFighter`和`MmaHeavyweightFighter`强制类型安全,确保相同重量级的拳手之间才能进行比赛。这种设计避免了不同重量级拳手间的错误匹配,编译时会报错。CRTP适用于处理类型冲突、参数化类方法和限制方法只对相同类型实例生效的情况。
【设计模式】JAVA Design Patterns——Curiously Recurring Template Pattern(奇异递归模板模式)
|
7月前
|
设计模式 安全 Java
【JAVA学习之路 | 基础篇】单例设计模式
【JAVA学习之路 | 基础篇】单例设计模式
|
6月前
|
设计模式 算法 关系型数据库
设计模式第七讲-外观模式、适配器模式、模板方法模式详解
系统要求所有的数据库帮助类必须实现ISqlHelp接口,面向该接口编程,如SQLServerHelp类。 此时第三方提供了一个新的MySql的帮助类(假设是dll,不能修改),它的编程规范和ISqlHelp不兼容,这个时候就需要引入适配器类,使二者能相互兼容。
177 0
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式