设计模式(行为型)-模板方法

定义

        模板模式的定义简洁而有力:定义一个操作的算法骨架,将部分步骤延迟到子类中实现,从而让子类在不改变算法整体结构的前提下,重新定义某些特定步骤。这一模式的核心在于分离算法的通用流程与个性化实现,如同搭建一座房屋,先确定整体框架,再根据不同需求装修内部空间。​

        例如,在制作咖啡和茶的过程中,都有烧水、冲泡等相似步骤,但具体冲泡方式和添加配料各有不同。通过模板模式,我们可以将烧水、冲泡等通用步骤定义在父类的算法骨架中,而把添加咖啡粉、茶叶或特殊配料等个性化步骤交由子类实现,既保证了流程的一致性,又满足了多样化需求。

类图

角色 

  • 抽象类(Abstract Class)​:抽象类是模板模式的核心中枢,肩负着给出算法轮廓和骨架的重任。它由一个模板方法和若干基本方法构成。模板方法如同整个算法的指挥家,按照特定顺序调用各个基本方法,把控算法的整体流程;基本方法则是算法的具体组成部分,包括抽象方法、具体方法和钩子方法。​

  • 具体子类(Concrete Class)​:具体子类负责实现抽象类中定义的抽象方法和钩子方法,它们是算法顶级逻辑的具体组成步骤。通过实现这些方法,具体子类为算法注入了独特的个性,使其能够适应不同的业务场景和需求。

模板方法分类

  • 抽象方法:在抽象类中声明,必须由具体子类实现。这些方法代表了算法中需要根据具体情况进行个性化定制的部分,如在制作饮品的例子中,冲泡的具体方式就是抽象方法,咖啡和茶的冲泡方式各不相同。​

  • 具体方法:在抽象类中已经实现,子类可以继承使用,也可以根据需要重写。具体方法实现了算法中相对固定、通用的部分,为子类提供了基础功能。​

  • 钩子方法:在抽象类中同样有实现,分为两种类型。一种是用于判断的逻辑方法,如判断是否满足某个条件再执行后续操作;另一种是需要子类重写的空方法,为子类提供了扩展点,子类可以根据实际需求决定是否重写钩子方法来增强算法功能。​

  • 模板方法​:模板方法定义了算法的骨架,它将算法的各个步骤有序串联起来。在模板方法中,抽象类规定了算法执行的先后顺序,子类无需改变这个顺序,只需专注于实现抽象方法和钩子方法,从而确保算法的稳定性和一致性。​

优缺点

优点​

  • 封装不变,扩展可变:模板模式将算法中不变的部分封装在父类中,可变的部分交由子类实现。这种设计方式使得代码的稳定性和可扩展性得到了很好的平衡,父类的算法骨架可以被多个子类复用,子类则能根据具体需求灵活扩展,避免了重复开发。​

  • 提取公共代码,提升复用性:通过在父类中提取公共代码,模板模式有效地减少了代码冗余。多个子类可以共享父类的通用方法,不仅提高了代码的复用率,还降低了维护成本,当需要修改公共代码时,只需在父类中进行修改,所有子类都能自动受益。​

  • 遵循开闭原则,便于拓展:模板模式允许子类通过实现抽象方法和钩子方法来增加新的功能,而无需修改父类的算法骨架。这完全符合开闭原则(对扩展开放,对修改关闭),使得系统在面对需求变化时能够轻松应对,具有良好的可维护性和扩展性。​

缺点​

  • 子类数量增多,系统复杂度上升:由于每个不同的实现都需要定义一个子类,随着业务需求的增加,子类的数量会不断增长,导致系统变得庞大且复杂。这不仅增加了代码的管理难度,也使得开发者在理解和维护代码时需要花费更多精力。​

  • 反向控制结构,代码阅读困难:在模板模式中,父类的模板方法调用子类实现的抽象方法,子类的执行结果会影响父类的最终结果,形成一种反向的控制结构。这种结构与常规的代码执行逻辑有所不同,提高了代码阅读和理解的难度,尤其是对于初次接触模板模式的开发者来说,需要花费更多时间理清代码之间的调用关系。

应用场景

  • 算法步骤固定,部分易变:当算法的整体步骤相对固定,但其中某些步骤容易发生变化时,模板模式是理想的选择。通过将易变的步骤抽象出来,由子类实现,既保证了算法的稳定性,又能快速适应变化。例如,在电商系统中,订单处理流程通常包括创建订单、支付、发货等固定步骤,但不同支付方式(如支付宝、微信支付)和发货策略(普通快递、加急快递)存在差异,此时就可以使用模板模式来处理。​

  • 提取子类公共行为:当多个子类存在公共行为时,利用模板模式将这些公共行为提取到父类中,可以有效避免代码重复。通过分析子类的代码,识别出相同的操作,并将其封装为父类的方法,子类只需继承父类并实现个性化部分,就能实现代码的复用和优化。​

  • 控制子类扩展:如果需要对子类的扩展进行控制,模板模式的钩子方法可以发挥重要作用。通过在模板方法中特定点调用钩子方法,只允许在这些指定点进行拓展,确保子类的扩展不会破坏算法的整体结构和逻辑。

实战案例

        以常见的日志记录系统为例,日志记录通常包括获取日志信息、格式化日志、写入日志文件等步骤。我们可以使用模板模式构建日志记录框架:​

  • 抽象日志记录类:定义模板方法,包含获取日志信息、格式化日志、写入日志文件等步骤,并将格式化日志和写入日志文件定义为抽象方法,交由子类实现。​

  • 具体日志记录子类:如文本日志记录类、数据库日志记录类等,分别实现抽象类中的抽象方法,实现不同格式和存储方式的日志记录。例如,文本日志记录类将日志格式化为文本格式并写入文件,数据库日志记录类将日志信息存储到数据库中。

代码示例

  • 抽象日志记录类(AbstractLogger

    • 定义了一个模板方法 log(),该方法按照获取日志信息、格式化日志、写入日志文件的顺序调用相应的方法,形成了日志记录的算法骨架。

    • getLogInfo() 方法提供了获取日志信息的默认实现,实际使用时可根据需求重写。

    • formatLog() 和 writeLog() 是抽象方法,需要具体子类实现。

// 抽象日志记录类
abstract class AbstractLogger {
    // 模板方法,定义日志记录的算法骨架
    public final void log() {
        String logInfo = getLogInfo();
        String formattedLog = formatLog(logInfo);
        writeLog(formattedLog);
    }

    // 获取日志信息,这里简单实现返回一个固定信息,实际可根据需求修改
    protected String getLogInfo() {
        return "This is a log message.";
    }

    // 抽象方法:格式化日志,由子类实现
    protected abstract String formatLog(String logInfo);

    // 抽象方法:写入日志,由子类实现
    protected abstract void writeLog(String formattedLog);
}    
  • 文本日志记录类(TextLogger

    • 继承自 AbstractLogger,实现了 formatLog() 方法,将日志信息格式化为文本日志格式。

    • 实现了 writeLog() 方法,将格式化后的日志信息写入 text_log.txt 文件。

import java.io.FileWriter;
import java.io.IOException;

// 文本日志记录类
class TextLogger extends AbstractLogger {
    @Override
    protected String formatLog(String logInfo) {
        return "Text Log: " + logInfo;
    }

    @Override
    protected void writeLog(String formattedLog) {
        try (FileWriter writer = new FileWriter("text_log.txt", true)) {
            writer.write(formattedLog + "\n");
            System.out.println("Text log written to text_log.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}    
  • 数据库日志记录类(DatabaseLogger

    • 同样继承自 AbstractLogger,实现了 formatLog() 方法,将日志信息格式化为数据库日志格式。

    • 实现了 writeLog() 方法,将格式化后的日志信息插入到数据库的 logs 表中。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

// 数据库日志记录类
class DatabaseLogger extends AbstractLogger {
    @Override
    protected String formatLog(String logInfo) {
        return "Database Log: " + logInfo;
    }

    @Override
    protected void writeLog(String formattedLog) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String username = "your_username";
        String password = "your_password";
        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            String sql = "INSERT INTO logs (log_message) VALUES (?)";
            try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                preparedStatement.setString(1, formattedLog);
                preparedStatement.executeUpdate();
                System.out.println("Database log written to database");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}    
  • 测试(Main:创建了 TextLogger 和 DatabaseLogger 的实例,并调用它们的 log() 方法进行日志记录测试。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

// 数据库日志记录类
class DatabaseLogger extends AbstractLogger {
    @Override
    protected String formatLog(String logInfo) {
        return "Database Log: " + logInfo;
    }

    @Override
    protected void writeLog(String formattedLog) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String username = "your_username";
        String password = "your_password";
        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            String sql = "INSERT INTO logs (log_message) VALUES (?)";
            try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                preparedStatement.setString(1, formattedLog);
                preparedStatement.executeUpdate();
                System.out.println("Database log written to database");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}    

        通过这种方式,使用模板模式实现了日志记录框架,不同的日志记录方式可以通过具体子类灵活扩展,而日志记录的整体流程保持不变。

总结

        模板模式凭借其独特的设计理念和强大的功能,在软件开发中发挥着重要作用。它帮助开发者构建出稳定、可复用且易于扩展的算法框架,尽管存在一些缺点,但在合适的应用场景下,其优势远远超过劣势。掌握模板模式,将为开发者在面对复杂算法设计时提供有力的武器,提升代码的质量和开发效率。        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

找了一圈尾巴

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值