设计模式:模板方法模式详解与实战应用
模板方法模式是一种基于继承的行为型设计模式,通过定义算法骨架并将部分步骤延迟到子类实现,从而在不改变算法结构的前提下实现代码复用。详细阐述了该模式的定义、UML 结构及核心角色,结合 Java ClassLoader 双亲委派模型、Android View 绘制流程及 AsyncTask 等实际源码案例,深入分析了其优缺点、与策略模式的区别及适用场景,帮助开发者理解如何在项目中合理运用该模式提升架构质量。

模板方法模式是一种基于继承的行为型设计模式,通过定义算法骨架并将部分步骤延迟到子类实现,从而在不改变算法结构的前提下实现代码复用。详细阐述了该模式的定义、UML 结构及核心角色,结合 Java ClassLoader 双亲委派模型、Android View 绘制流程及 AsyncTask 等实际源码案例,深入分析了其优缺点、与策略模式的区别及适用场景,帮助开发者理解如何在项目中合理运用该模式提升架构质量。

在软件开发中,我们经常会遇到一些业务流程相似但具体实现细节不同的场景。例如,制作咖啡和制作茶的过程非常相似:烧水、冲泡、加入配料等步骤基本一致,但具体的茶叶种类、咖啡粉类型以及添加的糖奶比例却各不相同。如果为每一种饮品都编写一套完整的流程代码,会导致大量的重复代码。此时,模板方法模式(Template Method Pattern) 便应运而生。
模板方法模式是一种基于继承的代码复用行为型模式。它通过定义一个操作中的算法框架,将某些步骤延迟到子类中实现,从而使得子类可以在不改变算法结构的前提下重定义特定步骤。本文将深入探讨该模式的定义、结构、经典案例以及在 Android 和 Java 核心类库中的应用。
模板方法模式(Template Method Pattern)定义了一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法模式主要包含以下两个核心角色:
抽象类(AbstractClass):
final 类型。具体子类(ConcreteClass):
(此处为模板方法模式 UML 类图示意)
为了更直观地理解,我们先看一个经典的非业务场景示例:冲饮制作。
// 抽象父类
abstract class Drink {
// 模板方法,定义算法骨架
public final void prepare() {
boilWater(); // 烧水
brew(); // 冲泡 (由子类实现)
pourInCup(); // 倒入杯中
addCondiments(); // 加调料 (由子类实现)
}
// 具体方法
private void boilWater() {
System.out.println("烧开水");
}
private void pourInCup() {
System.out.println("倒入杯中");
}
// 钩子方法,留给子类实现
protected abstract void brew();
protected abstract void addCondiments();
}
// 具体子类:茶
class Tea extends Drink {
@Override
protected void brew() {
System.out.println("用沸水泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加入柠檬片");
}
}
// 具体子类:咖啡
class Coffee extends Drink {
@Override
protected void brew() {
System.out.println("研磨并冲泡咖啡粉");
}
@Override
protected void addCondiments() {
System.out.println("加入牛奶和糖");
}
}
在这个例子中,prepare() 是模板方法,定义了固定的执行顺序。brew() 和 addCondiments() 是抽象方法,由子类根据具体需求实现。这样既保证了流程的一致性,又实现了行为的扩展性。
在 Java 核心类库中,ClassLoader 类就使用了模板方法模式来保证类加载过程中的唯一性和一致性。
public class ClassLoader {
// 这是一个重载方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// 父类算法的定义(模板方法)
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
// 这里留了一个方法给子类选择性覆盖
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
从上面的代码可以看出,findClass 这个方法并不是必须实现的,所以 ClassLoader 选择留给程序员们自己选择是否要覆盖。ClassLoader 中定义的算法顺序是:
findClass 方法加载。这是 ClassLoader 的双亲委派模型,即先从父类加载器加载,直到继承体系的顶层,否则才会采用当前的类加载器加载。这样做的目的是为了 JVM 中类的一致性。
无疑 ClassLoader 中就定义了模板方法,而 ClassLoader 的子类 BaseDexClassLoader 中就覆盖了 ClassLoader 的 findClass 方法:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
在 Android 源码中,View 中的 draw() 方法就是一个典型的'模板方法'。它定义了一系列'Draw'过程,主要包括这几个步骤:
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
其中第三步(Step 3)Draw view's content 函数:
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
第四步(Step 4)draw children:
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
从 View 的 draw() '模板方法'可以看出,当继承 View 子类中,如果要重写或者扩展这个方法时,整个方法流程和基本内容不能够修改,子类只能通过扩展 onDraw(Canvas canvas) 和 dispatchDraw(Canvas canvas) 两个函数,使子类自己的 View 显示效果和别的具体子类的不同。
我们可以看到,在 TextView 类中就重写了 onDraw 方法,处理文本渲染、背景绘制等逻辑。
Android 中的 AsyncTask 也是典型的模板方法模式。
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
继承 AsyncTask 的时候只需要根据需要重写上面几个方法就可以,它们就是 AsyncTask 类的可变部分,我们在子类中只需要实现可变部分就可以了,不变部分 AsyncTask 已经实现了,所以我们只需要根据这个模板进行使用就行。
虽然模板方法模式和策略模式都能解决算法变化的问题,但它们的侧重点不同:
在实际开发中,如果算法的流程是固定的,只是中间某些步骤有差异,优先使用模板方法模式;如果算法本身差异较大,且需要在运行时动态切换,优先使用策略模式。
模板方法模式是设计模式中非常基础且实用的一种模式。它通过定义算法骨架,有效地减少了代码冗余,提高了代码的可维护性。在 Android 开发、JVM 底层机制以及各种业务框架设计中,我们都能看到它的身影。掌握模板方法模式,有助于我们设计出更加清晰、稳定且易于扩展的软件架构。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online