TemplateMethod模式

什么是模板
模板的愿意是指带有镂空文字的薄薄的塑料板.只要用笔在模板的镂空处进行临摹,即使是手写也能写出整齐的文字.虽然只要看到这些镂空的洞,我们就可以知道能写出哪些文字,但是具体写出的文字是什么感觉则依赖于所用的笔.如果使用签字笔来临摹,则可以写出签字似的文字;如果使用铅笔来临摹,则可以写出铅笔字;而如果使用彩色笔临摹,则可以写出彩色的字.但是无论使用什么笔,文字的形状都会与模板上镂空的形状一致
什么是TemplateMethod模式
本章所要学习的TemplateMethod模式是带有模板功能的模式,组成模板的方法被定义在父类中.由于这些方法是抽象方法,所以只能查看父类的代码是无法知道这些方法最终会进行何种具体处理的,唯一能知道的就是父类是如何调用这些方法的
实现上述这些抽象方法的是子类.在子类中实习了抽象方法也就决定了具体的处理.也就是说,只要在不同的子类中实现不同的处理,当父类的模板方法被调用时程序行为也会不同.但是,不论子类中的而具体实现如何,处理的流程都会按照父类中所定义的那样进行.
像这样在父类中定义处理流程的框架,在子类中实现具体处理的模式就称为TemplateMethod模式.
示例程序
首先让我们来看一段TemplateMethod模式的示例程序.这里的示例程序是一段将字符和字符串循环显示5次的简单程序
在示例程序中会出现AbstractDisplay CharDisplay StringDisplay Main这四个类
在AbstractDisplay类中定义了display方法,而且在该方法中依次调用了open print close 这3个方法.虽然这3个方法已经在AbstractDisplay中被声明了,但都是没有实体的抽象方法.这里,调用抽象方法的display方法就是模板方法
而实际上实现了open print close 这3个抽象方法的是AbstractDisplay的子类CharDisplay类和S他ringDisplay类
Main类是用于测试程序行为的类
类的一览表
类的一览表
实例程序的类图
在这里插入图片描述
AbstractDisplay类
AbstractDisplay类有4个方法,分别是display open print close.其中只有display方法实现了,open print close 都是抽象方法.通过查看AbstractDisplay类中display方法的代码,我们可以知道display方法进行了以下处理.
调用open方法
调用5次print方法
调用close方法
那么在open方法 print方法 close方法中各进行了什么处理呢?通过查看AbstractDisplay类的代码,我们可以知道这3个方法都是抽象方法.也就是说,如果仅仅查看AbstractDisplay类的代码,我们无法知道这3个方法中进行了什么样的处理.这是因为open方法 print方法 close方法 的实际处理被交给了AbstractDisplay类的子类

public abstract class AbstractDisplay {          //抽象类AbstractDisplay
    public abstract void open();        //交给子类去实现的抽象方法(1)open
    public abstract void print();       //交给子类去实现的抽象方法(2)print
    public abstract void close();       //交给自乐去实现的抽象方法(3)close
    public final void display(){        //本抽象类中实现的display方法
        open();
        for (int i = 0;i < 5;i++){      //循环调用5次print...........
            print();
        }
        close();            //最后关闭.这就是display方法所实现的功能
    }
}

CharDisplay类
理解了前面的内容后,我们再来看看子类之一的CharDisplay类.由于CharDisplay类实现了父类AbstractDisplay类中的3个抽象方法open.print.close,因此它并不是抽象类.
CharDisplay类中的open.print.close方法的处理如下
在这里插入图片描述
这样,当display方法被调用时,结果会如何呢?假设我们向CharDisplay的构造函数中传递的参数是H这个字符,那么最终显示出来的会是如下结果.
<>

CharDisplay类(CharDisplay.java)

public class CharDisplay extends AbstractDisplay{       //CharDisplay是AbstractDisplay的子类

    private char ch;                                    //需要显示的字符



    public CharDisplay(char ch){                        //构造函数中几首的字符被
                                                        // 保存在字段中
        this.ch = ch;
    }
    @Override
    public void open() {                                 //open在父类中是抽象方法
                                                         //此处重写该方法
        System.out.print("<<");                                                 //显示开始字符"<<"
    }

    @Override
    public void print() {                                 //同样地,此处重写print方法
                                                          //该方法会在display中国被重复调用
        System.out.println(ch);                                                  //显示保存在字段ch中的字符
    }

    @Override
    public void close() {                                  //同样地,此处重写close方法
                                                           //显示结束字符">>"
        System.out.println(">>");
    }


}

StringDisplay类
接下来让我们看看另外一个子类——StringDisplay类.与CharDisplay类一样,它也实现了open,print,close方法.这次,这3个方法中会进行怎样的处理呢?
StringDisplay类中的opne,print,close方法的处理如表
此时,如果display方法被调用,结果会如何呢?假设我们向CharDisplay的构造函数中传递的参数是"Hello,world"这个字符串,那么最终结果会像下面这样.文字会被显示在方框内部
±------------+
|Hello,world|
|Hello,world|
|Hello,world|
|Hello,world|
|Hello,world|
±------------+
StringDisplay类中的open,print,close方法的处理
在这里插入图片描述
StringDisplay类(StringDisplay.java)

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() {               //被open和close方法调用                       
                                            //由于可见性是private,因此只能在本类中被调用
        System.out.println("+");             //显示表示方框的角的"+"  
        for (int i= 0;i < width;i++){        //显示width个"-"   
            System.out.println("-");        // 组成方框的边框                                       
        }
        System.out.println("+");            //显示表示方框的角的"+"
    }
}

Main类
Main类的作用是测试程序的行为.在该类中生成了CharDisplay类和StringDisplay类的实例,并调用了display方法
Main类(Main.java)

public class Main {

    public static void main(String[] args) {
        //生成一个持有'H'的CharDisplay类的实例
        AbstractDisplay d1 = new CharDisplay('H');
        //生成一个持有"Hello,world"的StringDisplay类的实例
        AbstractDisplay d2 = new StringDisplay("Hello,world");
        AbstractDisplay d3 = new StringDisplay("你好,世界");
        d1.display();       //由于d1,d2和d3都是AbstractDisplay类的子类
        d2.display();       //可以调用继承的display方法
        d3.display();       //实际的程序行为取决于CharDisplay类和StringDisplay类的具体实现
    }
}

运行结果

"C:\Program Files\Java\jdk1.8.0_91\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar=51086:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar;D:\study\out\production\study" Main
<<HHHHH>>+-----------+|Hello,world|
|Hello,world|
|Hello,world|
|Hello,world|
|Hello,world|
+-----------++-------------+|你好,世界|
|你好,世界|
|你好,世界|
|你好,世界|
|你好,世界|
+-------------+
Process finished with exit code 0

TemplateMethod模式中的角色
在TemplateMethod模式中有以下登场角色
AbstractClass(抽象类)
AbstractClass角色不仅负责实现模板方法,还负责声明在模板方法中使用到的抽象方法.这些抽象方法由子类ConcreteClass角色负责实现.在示例程序中,由AbstractDisplay类扮演此角色
ConcreteClass(具体类)
该角色负责具体实现AbstractClass角色中定义的抽象方法.这里实现的方法将会在AbstractClass角色的模板方法中被调用.在示例程序中,由CharDisplay类和StringDisplay类扮演此角色
TemplateMethod模式的类图
在这里插入图片描述
可以使逻辑处理通用化
使用TemplateMethod模式究竟能带来什么好处呢?这里,它的优点是由于在父类的模板方法中编写了算法,因此无需在每个子类中再编写算法.
例如,我们没使用TemplateMethod模式,二货思使用根本编辑器的复制和粘贴功能编写了多个ConcreteClass角色.此时,会出现ConcreteClass1.ConcreteClass2.ConcreteClass3等很多类似的类.编写完成后立即发现了Bug还好,但如果是过一段时间才发现在ConcreteClass1中有Bug,该怎么办呢?这是,我们就必须将这个Bug的修改反应到所有的COncreteClass角色中才行.
关于这一点,如果是使用TemplateMethod模式进行编程,当我们在模板方法中发现bug时,只需要修改模板方法即可解决问题.
父类与子类之间的协作
在TemplateMethod模式中,父类和子类是紧密联系,共同工作的.因此,在子类中实现父类中声明的抽象方法时,必须要理解这些抽象方法被调用的时机.在看不到父类源代码的情况下,想要编写出子类是非常困难的
父类与子类里的一致性
在示例程序中k,bulun是CharDisplay的示例还是StringDisplay的示例,都是保存在AbstractDisplay类型的变量中,然后再来调用display方法的.
使用父类类型的变量保存子类实例的优点是,集市没有用instanceof等指定子类的种类,程序也能正常工作.
无论在父类类型的变量中保存那个子类的实例,程序都可以正常工作,这种原则称为里氏替换原则(The Liskov Substitution Principle, LSP).当然,LSP并非仅限于TemplateMethod模式,它是通用的继承原则

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值