模板方法模式
模板方法模式在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
比如我有一个爬取网页的需求,看起来的代码如下:
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);
}
}
}
}
可以发现,模板方法模式通常会结合策略模式一起使用。