清淨
  • Java

    • Java 基础
  • 框架

    • Flyway
    • MapStruct
    • Spring Cloud
  • 中间件

    • Elasticsearch
    • Redis
    • RabbitMQ
    • Kafka
  • 三剑客

    • HTML
    • CSS
    • JavaScript
  • 进阶

    • jQuery
    • ECMAScript6
    • TypeScript
  • Vue

    • Vue2
    • Vue3
    • VuePress
  • Linux
  • Jenkins
  • Maven
  • MySQL
  • 插件工具
  • 代码集成
  • 其它

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • 设计模式
迁移日志
订阅号
微信订阅号
微信订阅号
GitHub
  • Java

    • Java 基础
  • 框架

    • Flyway
    • MapStruct
    • Spring Cloud
  • 中间件

    • Elasticsearch
    • Redis
    • RabbitMQ
    • Kafka
  • 三剑客

    • HTML
    • CSS
    • JavaScript
  • 进阶

    • jQuery
    • ECMAScript6
    • TypeScript
  • Vue

    • Vue2
    • Vue3
    • VuePress
  • Linux
  • Jenkins
  • Maven
  • MySQL
  • 插件工具
  • 代码集成
  • 其它

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • 设计模式
迁移日志
订阅号
微信订阅号
微信订阅号
GitHub
  • 设计模式

    • 简介
    • 软件设计原则
    • 创建型模式

      • 概述
      • 单例模式
      • 工厂模式
      • 原型模式
      • 建造者模式
    • 结构型模式

      • 概述
      • 代理模式
      • 适配器模式
      • 装饰器模式
    • 行为模式

      • 概述
      • 责任链模式
      • 观察者模式
      • 策略模式
      • 模板方法模式

模板方法模式

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

比如我有一个爬取网页的需求,看起来的代码如下:

public class ExtractTextStrategy {

    public ExtractTextVO extractText(String url) {
        ExtractTextVO extractTextVO = new ExtractTextVO();
        extractTextVO.setUrl(url);

        // 发送 http 请求获取网页的 DOM 树
        Document document = Jsoup.parse(HttpUtils.get(url, initHeaders(url)));
        if (document == null) {
            return extractTextVO;
        }

        // 获取网页的有效内容
        extractTextVO.setContent(document.body().text());

        return extractTextVO;
    }
}

有一天,你发现,有些网页比较特殊。比如,使用 http 请求无法获取网页的 DOM 树、网页的 DOM 树结构比较复杂,获取网页的有效内容需要使用不同的算法等等。然后开始修改代码:

public class ExtractTextStrategy {

    public ExtractTextVO extractText(String url) {
        ExtractTextVO extractTextVO = new ExtractTextVO();
        extractTextVO.setUrl(url);

        // 发送 http 请求获取网页的 DOM 树
        Document document = null;
        if (url.contains("xxx")) { 
            // 使用 selenium 获取网页的 DOM 树
            document = Jsoup.parse(getHtmlBySelenium(url));
        } else {
            document = Jsoup.parse(getHtmlByHttp(url));
        }
        if (document == null) {
            return extractTextVO;
        }

        // 获取网页的有效内容
        String text = null;
        if (url.contains("aaa")) { 
            text = getTextWithGNE(document);
        } else if (url.contains("bbb")) {
            text = getTextWithGoose(document);
        } else if (url.contains("ccc")) { 
            text = getTextWithReadability(document);
        } else if (url.contains("ddd")) { 
            text = getTextWithBoilerpipe(document);
        } else if (url.contains("eee")) { 
            text = getTextWithDiffbot(document);
        } else if (url.contains("xxx")) { 
            text = getTextWithLLM(document);
        } else {
            text = document.body().text();
        }
        extractTextVO.setContent(text);

        return extractTextVO;
    }
}

随着需求不断增加,条件分支越来越多,代码会越来越长,可读性会变得很差。这时,你可以使用模板方法模式,将获取网页有效内容的算法封装成模板方法或抽象方法,让子类来实现或重写。重构后的代码如下:

  • ExtractTextStrategy.java
interface ExtractTextStrategy {
    boolean support(String url);

    ExtractTextVO extractText(String url);
}
  • AbstractExtractTextStrategy.java
public abstract class AbstractExtractTextStrategy implements ExtractTextStrategy { 

    @Override
    public ExtractTextVO extractText(String url) { 
         ExtractTextVO extractTextVO = new ExtractTextVO();
        extractTextVO.setUrl(url);

        // 获取网页的 DOM 树
        Document document = this.getDocument(url);
        if (document == null) {
            return extractTextVO;
        }

        // 获取网页的有效内容
        extractTextVO.setContent(this.extractContent(document, url));

        return extractTextVO;
    }

    protected Document getDocument(String url) {
        return this.getDocument(url, false);
    }

    protected Document getDocument(String url, boolean isSelenium) {
        Document document = null;
        if (isSelenium) { 
            document = Jsoup.parse(getHtmlBySelenium(url));
        } else { 
            document = Jsoup.parse(getHtmlByHttp(url));
        }
        return document;
    }

    protected String extractContent(Document document, String url) {
        return document.body().text();
    }
}
  • XxxExtractTextStrategy.java
@Component
public class XxxExtractTextStrategy extends AbstractExtractTextStrategy { 

    @Override
    public boolean support(String url) { 
        return url.contains("xxx");
    }

    @Override
    protected Document getDocument(String url) { 
        return super.getDocument(url, true);
    }

    @Override
    protected String extractContent(Document document, String url) { 
        return getTextWithLLM(document);
    }
}
  • AaaExtractTextStrategy.java
@Component
public class AaaExtractTextStrategy extends AbstractExtractTextStrategy { 

    @Override
    public boolean support(String url) { 
        return url.contains("aaa");
    }

    @Override
    protected String extractContent(Document document, String url) { 
        return getTextWithGNE(document);
    }
}
  • ExtractTextService.java
@Service
public class ExtractTextService { 

    @Autowired
    private List<ExtractTextStrategy> extractTextStrategies;

    public ExtractTextVO extractText(String url) { 
        for (ExtractTextStrategy extractTextStrategy : extractTextStrategies) { 
            if (extractTextStrategy.support(url)) { 
                return extractTextStrategy.extractText(url);
            }
        }
    }
}

可以发现,模板方法模式通常会结合策略模式一起使用。

想要编辑此页?
上次更新: 2025/10/18 20:33
贡献者: Junfeng Dai
←策略模式
[ 纸上得来终觉浅绝知此事要躬行 ]
Copyright © 2021-present Junfeng Dai
蜀ICP备2021009537号