模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,而将一些步骤延迟到子类中实现,也即子类为一个或多个步骤提供具体实现。这种模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
一、定义与原理
模板方法模式定义了一个操作的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法的某些特定步骤。这种设计模式基于面向对象的多态性和继承机制,通过定义一个抽象类(或接口),将算法的框架抽象出来,并通过抽象方法(或接口方法)将算法中可变的部分延迟到子类中实现。
二、结构
模板方法模式主要包含以下几个部分:
- 抽象类(Abstract Class):定义了算法的框架,即模板方法。它声明了抽象方法,这些方法是算法中可变的部分,由子类实现。同时,它还实现了算法中的固定部分,即具体方法。此外,抽象类还可以包含钩子方法(Hook Methods),它们是可选的、具有默认实现的方法,子类可以选择性地覆盖这些方法以改变算法的行为。
- 具体子类(Concrete Class):继承自抽象类,实现抽象类中声明的抽象方法,以完成算法中可变部分的具体实现。子类还可以选择性地覆盖钩子方法,以改变算法的默认行为。
三、特点
- 算法骨架固定,细节可变:在一些应用场景中,算法的整体步骤是固定的,但是其中的某些步骤在不同情况下有不同的实现。模板方法模式允许子类在不改变算法结构的前提下,重新定义这些特定步骤。
- 代码复用:通过定义模板方法,可以把通用的代码放到抽象类中,避免代码重复。
- 扩展性强:模板方法模式让子类可以有选择地覆盖父类的方法以扩展算法,而不是通过修改父类的代码来实现。
- 控制流程:子类需要调用父类的方法来完成整个算法的某些步骤,模板方法模式提供了这种控制流程的机制。
四、应用场景
模板方法模式适用于以下场景:
- 算法框架固定,但具体实现可变。
- 在多个类中存在重复的代码片段,且这些代码片段在算法中扮演相似的角色时,可以通过模板方法模式将这些代码抽象到父类中,以减少代码重复。
- 需要控制子类的扩展方式,确保子类按照特定的算法框架进行扩展时。
五、代码示例
以下是一些简单的模板方法模式示例。
示例一:制作饮料
在这个示例中,我们有一个抽象类Beverage
,它定义了制作饮料的基本结构和流程。具体步骤如烧水(boilWater
)、冲泡(brew
)、倒入杯中(pourInCup
)以及添加调料(addCondiments
)等,其中冲泡和添加调料的方法由子类实现。此外,还有一个钩子方法customerWantsCondiments
,用于判断是否需要添加调料,子类可以选择性地覆盖这个方法。
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
)等,其中验证签名和执行异步服务的方法由子类实现。此外,还有几个钩子方法用于返回成功或失败的结果。
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
)等,其中爬取页面的方法由子类实现。校验和保存的逻辑则在父类中实现。
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<>();
}
}
示例四:文件处理示例
在文件处理的场景中,我们经常需要对不同类型的文件进行读写操作,而这些操作通常包含一些固定的步骤,如打开文件、读取/写入数据以及关闭文件等。同时,不同类型的文件在处理数据时又会有一些特定的逻辑。这时,我们可以使用模板方法模式来定义一个文件处理的算法骨架,并将特定于文件类型的处理逻辑延迟到子类中实现。
// 首先,我们定义一个抽象模板类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)来改变算法的行为,从而提供灵活性。
- 符合开闭原则:
- 模板方法模式使得系统对扩展开放,对修改关闭。可以通过增加新的子类来扩展功能,而不需要修改现有的代码。
缺点
- 增加了代码的复杂性:
- 由于引入了父类和子类之间的继承关系,代码结构变得相对复杂,增加了理解和维护的难度。
- 子类之间的耦合性:
- 如果子类之间存在过多的依赖关系,可能会增加代码的耦合性,使得修改一个子类时可能需要同时修改其他子类。
- 抽象层次过多:
- 如果抽象层次过多,可能会导致系统变得过于复杂,难以理解和维护。
- 限制灵活性:
- 虽然模板方法模式提供了算法骨架的复用,但它也限制了子类对算法的完全自定义能力。子类只能重写或扩展特定的方法,而不能完全改变算法的结构。
- 性能问题:
- 在某些情况下,由于使用了继承和多态性,可能会导致性能上的开销,尤其是在大量使用动态绑定的情况下。
综上所述,模板方法模式是一种非常有用的设计模式,它允许在不改变算法结构的前提下,通过子类重新定义算法的某些特定步骤。这种设计模式在软件开发中具有广泛的应用场景和重要的价值。