模板方法模式

395 阅读6分钟

模板方法模式

Reference

[1] refactoringguru.cn/design-patt…, 本文主要借鉴该文章,如有侵权,请联系删除

[2] c.biancheng.net/view/1376.h…

什么是模板方法

模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤,简单来讲,它就是一个模板,大题的几个步骤固定不变,作为模板提供暴露给子类,子类可以进行重写,以满足不同的微小需求

场景

假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式 (PDF、 DOC 或 CSV) 的文档, 程序则会试图从这些文件中抽取有意义的数据, 并以统一的格式将其返回给用户。该程序的首个版本仅支持 DOC 文件。 在接下来的一个版本中, 程序能够支持 CSV 文件。 一个月后, 程序又需要支持从 PDF 文件中抽取数据。

image-20211212161531024

这三个类中的代码有许多相似,尽管处理的数据格式不同,但是处理步骤几乎一致。客户端代码中也需要根据不同的数据类型选择不同的处理过程,就会增加许多的条件语句,所以,如果所有处理数据的类都拥有相同的接口或基类,转而使用多态机制来在处理对象上调用函数,就可以减少许多的条件语句。

模板方法改造

分析这个过程,这个数据挖掘程序的基本步骤为

  1. 根据 path 打开文件 file
  2. 根据 file 提取数据 rawdata
  3. 根据 rawdata 解析成 data
  4. 将 data 进行分析处理为 analysis
  5. 再根据 analysis 发送分析报告
  6. 最后关闭 file

很明显,这个例子中,源数据格式不同,需要不同的处理方式,但是处理的大体步骤是一致的,很贴切模板方法

image-20211212162302410

我们可以将所有步骤都声明为抽象方法,定义在一个抽象类中,mine(path) 则为模板方法,其中指定了其他抽象方法的调用顺序。

让子类去继承该超类,强制它们自己实现这些方法。

对于分析数据 analysis 和发送报告 sendReport 这两个步骤的实现方式非常相似,则它们可以在抽象类中默认实现。

再举一个例子

就好比建房子,大题所需的东西是固定的,每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同,这就是模板方法的原理

image-20211212162738434

模板方法的结构

  • 抽象类:

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

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

      • 抽象方法:在抽象类中声明,由具体子类实现。
      • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  • 具体实现

image-20211212163125277

具体例子

分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,那么可以设计成以下形式

image-20211212163241429

public class StudyAbroadProcess {
    public static void main(String[] args) {
        StudyAbroad tm = new StudyInAmerica();
        tm.TemplateMethod();
    }
}
​
//抽象类: 出国留学
abstract class StudyAbroad {
    public void TemplateMethod() //模板方法
    {
        LookingForSchool(); //索取学校资料
        ApplyForEnrol();    //入学申请
        ApplyForPassport(); //办理因私出国护照、出境卡和公证
        ApplyForVisa();     //申请签证
        ReadyGoAbroad();    //体检、订机票、准备行装
        Arriving();         //抵达
    }
​
    public void ApplyForPassport() {
        System.out.println("三.办理因私出国护照、出境卡和公证:");
        System.out.println("  1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");
        System.out.println("  2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");
    }
​
    public void ApplyForVisa() {
        System.out.println("四.申请签证:");
        System.out.println("  1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");
        System.out.println("  2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");
    }
​
    public void ReadyGoAbroad() {
        System.out.println("五.体检、订机票、准备行装:");
        System.out.println("  1)进行身体检查、免疫检查和接种传染病疫苗;");
        System.out.println("  2)确定机票时间、航班和转机地点。");
    }
​
    public abstract void LookingForSchool();//索取学校资料
​
    public abstract void ApplyForEnrol();   //入学申请
​
    public abstract void Arriving();        //抵达
}
​
//具体子类: 美国留学
class StudyInAmerica extends StudyAbroad {
    @Override
    public void LookingForSchool() {
        System.out.println("一.索取学校以下资料:");
        System.out.println("  1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");
        System.out.println("  2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");
        System.out.println("  3)了解该学校的住宿、交通、医疗保险情况如何;");
        System.out.println("  4)该学校在中国是否有授权代理招生的留学中介公司?");
        System.out.println("  5)掌握留学签证情况;");
        System.out.println("  6)该国政府是否允许留学生合法打工?");
        System.out.println("  8)毕业之后可否移民?");
        System.out.println("  9)文凭是否受到我国认可?");
    }
​
    @Override
    public void ApplyForEnrol() {
        System.out.println("二.入学申请:");
        System.out.println("  1)填写报名表;");
        System.out.println("  2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");
        System.out.println("  3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");
    }
​
    @Override
    public void Arriving() {
        System.out.println("六.抵达目标学校:");
        System.out.println("  1)安排住宿;");
        System.out.println("  2)了解校园及周边环境。");
    }
}

进阶阅读

如果您想深入了解模板方法模式,可猛击阅读以下文章。