23种设计模式-行为型模式-模板方法

文章目录

简介

模板方法是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

场景

假如你正在开发一款分析文档的数据挖掘程序。用户需要向程序输入各种格式(PDF、DOC 或 CSV)的文档,程序会试图从这些文件中提取有意义的数据,再以统一的格式返回给用户。

程序的首个版本只支持 DOC 文件。下个版本,程序需要支持 CSV 文件。一个月后,程序又需要从 PDF 文件中抽取数据。

一段时间后,你发现这三个类中包含许多相似代码。尽管读取不同格式数据的代码完全不同,但数据处理和分析的代码却几乎完全一样。怎么在保持算法结构完整的情况下去除重复代码?这是第一个问题。

还有另一个与使用这些类的客户端代码相关的问题:客户端代码中包含许多条件语句,因为它要根据不同的文档类型选择合适的处理过程。 如果所有处理数据的类都有相同的接口或基类,那你就可以去除客户端代码里的条件语句,转而使用多态机制来调用处理对象的方法。

解决

模板方法模式建议把算法分解为一系列步骤,然后将这些步骤改写为方法,最后在"模板方法"中依次调用这些方法。步骤可以是抽象的,也可以有一些默认的实现。为了能够使用算法,客户端需要自己提供子类,并且实现所有的抽象步骤。可能还需重写一些步骤(但这一步中不包括模板方法自身)。

让我们想想怎么在数据挖掘程序里运用这样的方案。我们可为三个解析算法创建一个基类,这个类定义了一个模板方法-mine方法,这个方法会在内部调用不同的文档处理步骤。

首先,我们将所有步骤声明为抽象类型,强制要求子类自行实现这些方法。在我们的例子里,子类已经实现了必须的方法,因此我们只需调整这些方法的名称,让他跟超类的方法匹配就行了。

现在,让我们看看怎么去除子类里的重复代码。对于不同的文件格式,打开和关闭文件以及读取解析数据的代码都不同,所以不需要修改这些方法。但分析原始数据和生成报告等其他步骤的实现方式非常相似,所以可以把他们提取到基类里,让子类共享这些代码。

所以我们有两种类型的步骤:

  1. 抽象步骤必须由各个子类来实现
  2. 可选步骤已经有一些默认实现,但你仍然可以在需要时进行重写
    还有另一种名为钩子 hook 的步骤。钩子是内容为空的可选的步骤。即使子类不重写钩子,模板方法也能工作。钩子通常放置在算法重要步骤的前后,为子类提供额外的算法扩展点。

代码

java 复制代码
// 抽象类定义模板方法
abstract class DataMiner {
    // 模板方法(不可被覆盖)
    public final void processDocument() {  
        openDocument();
        extractRawData();
        parseData();           // 抽象步骤
        analyzeData();         // 通用实现
        generateReport();      // Hook方法
    }

    // 文档打开基础实现
    protected void openDocument() {  
        System.out.println("打开文档...");
    }
    
    // 数据抽取基础实现
    protected void extractRawData() {  
        System.out.println("抽取原始数据...");
    }
    
    // 解析算法必须子类实现
    protected abstract void parseData();  

    // 通用分析实现
    protected void analyzeData() {  
        System.out.println("执行数据聚类分析...");
    }
    
    /* 钩子方法(可选覆盖) */
    protected void generateReport() {  
        System.out.println("生成基础统计报表");
    }
}

// 具体子类实现
class PDFDataMiner extends DataMiner {
    @Override
    protected void parseData() {  // 实现特定解析逻辑
        System.out.println("解析PDF版式结构");
        System.out.println("提取PDF文本流");
    }
}

class CSVDataMiner extends DataMiner {
    @Override
    protected void parseData() {  
        System.out.println("识别CSV分隔符");
        System.out.println("映射CSV字段");
    }
    
    @Override
    protected void generateReport() {  // 自定义Hook实现
        super.generateReport();
        System.out.println("追加CSV格式验证结果");
    }
}

// 客户端调用示例
class Client {
    public static void main(String[] args) {
        System.out.println("处理PDF文档:");
        DataMiner pdfProcessor = new PDFDataMiner();
        pdfProcessor.processDocument();  // 执行完整流程

        System.out.println("\n处理CSV文档:");
        DataMiner csvProcessor = new CSVDataMiner();
        csvProcessor.processDocument();
    }
}

关键优化点

  1. 固定流程:不可重写的processDocument()确保流程一致
  2. 职责分离:各子类仅需实现格式相关解析逻辑
  3. 扩展能力:通过钩子方法实现可选扩展(如CSV格式验证)

总结

  1. 抽象类(AbstractClass)会声明算法步骤的方法(step1,step2...),以及依次调用它们的模板方法(templateMethod)。算法步骤可以被声明为抽象类型,也可以提供一些默认实现。
  2. 具体类(ConcreteClass)可以重写所有步骤,但不能重写模板方法自身。
相关推荐
vker4 小时前
第 1 天:单例模式(Singleton Pattern)—— 创建型模式
java·设计模式
晨米酱1 天前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机1 天前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript