设计模式学习之——模板方法模式

模板方法模式(Template Method Pattern)是一种行为型设计模式 ,它定义了一个算法的骨架 ,而将一些步骤延迟到子类中实现,也即子类为一个或多个步骤提供具体实现。这种模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

一、定义与原理

模板方法模式定义了一个操作的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法的某些特定步骤。这种设计模式基于面向对象的多态性和继承机制,通过定义一个抽象类(或接口),将算法的框架抽象出来,并通过抽象方法(或接口方法)将算法中可变的部分延迟到子类中实现。

二、结构

模板方法模式主要包含以下几个部分:

  1. 抽象类(Abstract Class):定义了算法的框架,即模板方法。它声明了抽象方法,这些方法是算法中可变的部分,由子类实现。同时,它还实现了算法中的固定部分,即具体方法。此外,抽象类还可以包含钩子方法(Hook Methods),它们是可选的、具有默认实现的方法,子类可以选择性地覆盖这些方法以改变算法的行为。
  2. 具体子类(Concrete Class):继承自抽象类,实现抽象类中声明的抽象方法,以完成算法中可变部分的具体实现。子类还可以选择性地覆盖钩子方法,以改变算法的默认行为。

三、特点

  1. 算法骨架固定,细节可变:在一些应用场景中,算法的整体步骤是固定的,但是其中的某些步骤在不同情况下有不同的实现。模板方法模式允许子类在不改变算法结构的前提下,重新定义这些特定步骤。
  2. 代码复用:通过定义模板方法,可以把通用的代码放到抽象类中,避免代码重复。
  3. 扩展性强:模板方法模式让子类可以有选择地覆盖父类的方法以扩展算法,而不是通过修改父类的代码来实现。
  4. 控制流程:子类需要调用父类的方法来完成整个算法的某些步骤,模板方法模式提供了这种控制流程的机制。

四、应用场景

模板方法模式适用于以下场景:

  1. 算法框架固定,但具体实现可变。
  2. 在多个类中存在重复的代码片段,且这些代码片段在算法中扮演相似的角色时,可以通过模板方法模式将这些代码抽象到父类中,以减少代码重复。
  3. 需要控制子类的扩展方式,确保子类按照特定的算法框架进行扩展时。

五、代码示例

以下是一些简单的模板方法模式示例。

示例一:制作饮料

在这个示例中,我们有一个抽象类Beverage,它定义了制作饮料的基本结构和流程。具体步骤如烧水(boilWater)、冲泡(brew)、倒入杯中(pourInCup)以及添加调料(addCondiments)等,其中冲泡和添加调料的方法由子类实现。此外,还有一个钩子方法customerWantsCondiments,用于判断是否需要添加调料,子类可以选择性地覆盖这个方法。

java 复制代码
abstract class Beverage {
    // 模板方法,定义了算法的基本结构和流程
    final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    // 具体步骤,由子类实现
    abstract void brew();
    abstract void addCondiments();

    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 钩子方法,由子类选择性地覆盖
    boolean customerWantsCondiments() {
        return true;
    }
}

class Coffee extends Beverage {
    void brew() {
        System.out.println("Brewing coffee");
    }

    void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

class Tea extends Beverage {
    void brew() {
        System.out.println("Steeping tea");
    }

    void addCondiments() {
        System.out.println("Adding lemon");
    }

    // 覆盖钩子方法
    boolean customerWantsCondiments() {
        return false;
    }
}
示例二:支付回调处理

在这个示例中,我们有一个抽象类AbstractPayCallbackTemplate,它定义了支付回调处理的算法骨架。具体步骤如验证签名(verifySignature)、写入日志(payLog)、执行异步服务(asyncService)等,其中验证签名和执行异步服务的方法由子类实现。此外,还有几个钩子方法用于返回成功或失败的结果。

java 复制代码
abstract class AbstractPayCallbackTemplate {
    // 模板方法,定义了支付回调处理的算法骨架
    public String asyncCallBack() {
        Map<String, String> verifySignatureMap = verifySignature();
        payLog(verifySignatureMap);
        String analysisCode = verifySignatureMap.get("analysisCode");
        if (!analysisCode.equals("200")) {
            return resultFail();
        }
        return asyncService(verifySignatureMap);
    }

    // 具体步骤,由子类实现
    protected abstract Map<String, String> verifySignature();
    protected abstract String asyncService(Map<String, String> verifySignatureMap);

    // 钩子方法,由子类实现
    protected abstract String resultSuccess();
    protected abstract String resultFail();

    // 使用多线程异步写入日志
    @Async
    void payLog(Map<String, String> verifySignatureMap) {
        // 写入日志的逻辑
    }
}

class AliPayCallbackTemplate extends AbstractPayCallbackTemplate {
    @Override
    protected Map<String, String> verifySignature() {
        // 验证签名的逻辑
        Map<String, String> verifySignature = new HashMap<>();
        // ...
        return verifySignature;
    }

    @Override
    protected String asyncService(Map<String, String> verifySignatureMap) {
        // 执行异步服务的逻辑
        // ...
        return resultSuccess();
    }

    @Override
    protected String resultSuccess() {
        return "ok";
    }

    @Override
    protected String resultFail() {
        return "fail";
    }
}
示例三:爬虫应用

在这个示例中,我们有一个抽象类AbstractCrawlNewsService,它定义了爬取网站资讯的算法骨架。具体步骤如爬取页面(crawlPage)、校验是否已经抓取过(isCrawled)、保存资讯(saveArticle)等,其中爬取页面的方法由子类实现。校验和保存的逻辑则在父类中实现。

java 复制代码
abstract class AbstractCrawlNewsService {
    // 模板方法,定义了爬取资讯的算法骨架
    protected void doTask(String url) {
        int pageNum = 1;
        while (true) {
            List<Object> newsList = crawlPage(pageNum++);
            if (newsList.isEmpty()) {
                break;
            }
            Map<String, Boolean> crawledMap = isCrawled(newsList);
            for (Object news : newsList) {
                if (!crawledMap.getOrDefault(news.getTitle(), false)) {
                    saveArticle(news);
                }
            }
            // 可以考虑请求后休眠一下,因为太频繁IP容易被封
            // Thread.sleep(2000);
        }
    }

    // 具体步骤,由子类实现
    protected abstract List<Object> crawlPage(int pageNum) throws IOException;

    // 校验是否已经抓取过,由父类实现
    protected final Map<String, Boolean> isCrawled(List<Object> checkParams) {
        // 数据库校验的逻辑
        return new HashMap<>();
    }

    // 保存资讯,由父类实现
    protected final void saveArticle(Object object) {
        // 保存数据库的逻辑
    }
}

class CrawlerHuoXingServiceImpl extends AbstractCrawlNewsService {
    @Override
    protected List<Object> crawlPage(int pageNum) throws IOException {
        // 爬取火星网新闻的逻辑
        // ...
        return new ArrayList<>();
    }
}
示例四:文件处理示例

在文件处理的场景中,我们经常需要对不同类型的文件进行读写操作,而这些操作通常包含一些固定的步骤,如打开文件、读取/写入数据以及关闭文件等。同时,不同类型的文件在处理数据时又会有一些特定的逻辑。这时,我们可以使用模板方法模式来定义一个文件处理的算法骨架,并将特定于文件类型的处理逻辑延迟到子类中实现。

java 复制代码
// 首先,我们定义一个抽象模板类FileProcessor,它包含了文件处理的算法骨架:
abstract class FileProcessor {
    // 模板方法,定义了文件处理的算法骨架
    public final void processFile(String filePath) {
        openFile(filePath);
        processData();
        closeFile();
    }
 
    // 打开文件,由子类根据需要实现(例如,文本文件、二进制文件等)
    protected abstract void openFile(String filePath);
 
    // 处理数据,由子类实现具体的处理逻辑
    protected abstract void processData();
 
    // 关闭文件,可以是一个通用的实现
    protected void closeFile() {
        System.out.println("File closed.");
    }
}

// 然后,我们为不同类型的文件创建具体的模板类,例如文本文件处理器TextFileProcessor和二进制文件处理器

class TextFileProcessor extends FileProcessor {
    @Override
    protected void openFile(String filePath) {
        System.out.println("Opening text file: " + filePath);
        // 打开文本文件的逻辑(例如,使用BufferedReader)
    }

    @Override
    protected void processData() {
        System.out.println("Processing text data...");
        // 处理文本数据的逻辑(例如,读取每一行并处理)
    }
}

class BinaryFileProcessor extends FileProcessor {
    @Override
    protected void openFile(String filePath) {
        System.out.println("Opening binary file: " + filePath);
        // 打开二进制文件的逻辑(例如,使用FileInputStream)
    }

    @Override
    protected void processData() {
        System.out.println("Processing binary data...");
        // 处理二进制数据的逻辑(例如,读取字节并处理)
    }
}

// 客户端代码
public class FileProcessorClient {
    public static void main(String[] args) {
        FileProcessor textProcessor = new TextFileProcessor();
        textProcessor.processFile("example.txt");

        FileProcessor binaryProcessor = new BinaryFileProcessor();
        binaryProcessor.processFile("example.bin");
    }
}

这些示例展示了模板方法模式在不同场景中的应用,通过定义算法骨架和延迟某些步骤到子类中实现,提高了代码的复用性和灵活性。

六、优缺点

优点
  • 代码复用
    • 模板方法定义了算法的骨架,使得相同算法的不同部分可以在子类中复用。
  • 扩展性
    • 新的算法可以通过增加新的子类来实现,而不必修改现有的代码。
  • 封装性
    • 模板方法将算法的步骤封装在父类中,使得子类不需要关心算法的总体结构,只需关注自己的特定实现。
  • 灵活性
    • 子类可以通过重写父类中的抽象方法或钩子方法(hook method)来改变算法的行为,从而提供灵活性。
  • 符合开闭原则
    • 模板方法模式使得系统对扩展开放,对修改关闭。可以通过增加新的子类来扩展功能,而不需要修改现有的代码。
缺点
  • 增加了代码的复杂性
    • 由于引入了父类和子类之间的继承关系,代码结构变得相对复杂,增加了理解和维护的难度。
  • 子类之间的耦合性
    • 如果子类之间存在过多的依赖关系,可能会增加代码的耦合性,使得修改一个子类时可能需要同时修改其他子类。
  • 抽象层次过多
    • 如果抽象层次过多,可能会导致系统变得过于复杂,难以理解和维护。
  • 限制灵活性
    • 虽然模板方法模式提供了算法骨架的复用,但它也限制了子类对算法的完全自定义能力。子类只能重写或扩展特定的方法,而不能完全改变算法的结构。
  • 性能问题
    • 在某些情况下,由于使用了继承和多态性,可能会导致性能上的开销,尤其是在大量使用动态绑定的情况下。

综上所述,模板方法模式是一种非常有用的设计模式,它允许在不改变算法结构的前提下,通过子类重新定义算法的某些特定步骤。这种设计模式在软件开发中具有广泛的应用场景和重要的价值。

相关推荐
重生之绝世牛码2 小时前
Java设计模式 —— 【创建型模式】工厂模式(简单工厂、工厂方法模式、抽象工厂)详解
java·大数据·开发语言·设计模式·工厂方法模式·设计原则·工厂模式
five小点心3 小时前
尚硅谷学习笔记——Java设计模式(一)设计模式七大原则
java·开发语言·设计模式
夏旭泽3 小时前
设计模式-桥接模式
设计模式·桥接模式
山猪打不过家猪3 小时前
C#设计模式——抽象工厂模式(重点)
设计模式·c#·抽象工厂模式
萨达大5 小时前
23种设计模式-工厂方法(Factory Method)设计模式
c++·设计模式·工厂方法模式·软考·创建型设计模式·软件设计师
IsToRestart6 小时前
String的设计,用到了哪些设计模式?
java·jvm·设计模式
Theodore_10227 小时前
10 设计模式之装饰模式
java·开发语言·算法·设计模式·java-ee·装饰模式
机器视觉知识推荐、就业指导7 小时前
C++设计模式:装饰器模式 (Decorator) (咖啡订单系统)
开发语言·c++·设计模式·装饰器模式
萨达大8 小时前
23种设计模式-单例(Singleton)设计模式
java·c++·单例模式·设计模式·软考·创建型设计模式·软件设计师