一、故事解说:外婆的菜谱如何统一做菜流程
假设你外婆有一本祖传菜谱:
-
固定流程:所有菜的烹饪流程都是:
- 准备食材 → 处理食材 → 下锅烹饪 → 调味 → 装盘;
-
变化步骤:
- 红烧肉:准备五花肉,切块焯水,红烧,加糖和酱油,装盘撒葱花;
- 清蒸鱼:准备鲈鱼,处理内脏,清蒸,加蒸鱼豉油,装盘撒香菜;
-
核心逻辑:外婆定义了做菜的通用流程(模板),具体步骤由不同菜品实现,但流程顺序固定不变。
模板方法模式核心:定义一个算法的骨架(如做菜流程),将一些步骤延迟到子类实现,让子类在不改变算法结构的情况下,重新定义某些步骤,就像外婆的菜谱一样。
二、模板方法模式核心结构(做菜案例)
java
// 1. 抽象类:定义做菜的模板方法和基本步骤
abstract class CookingRecipe {
// 模板方法:定义做菜的整体流程
public final void cook() {
prepareIngredients();
processIngredients();
cookInPot();
season();
装盘();
}
// 必须由子类实现的抽象方法
protected abstract void prepareIngredients();
protected abstract void processIngredients();
// 具体方法:通用实现
protected void cookInPot() {
System.out.println("下锅烹饪");
}
// 钩子方法:子类可选择性重写
protected void season() {
System.out.println("加盐和味精");
}
// 具体方法:默认实现
private void 装盘() {
System.out.println("装盘上桌");
}
}
// 2. 具体子类:红烧肉
class BraisedPork extends CookingRecipe {
@Override
protected void prepareIngredients() {
System.out.println("准备五花肉");
}
@Override
protected void processIngredients() {
System.out.println("五花肉切块,焯水去血沫");
}
@Override
protected void cookInPot() {
System.out.println("锅中倒油,放冰糖炒糖色,下肉翻炒,加酱油和水,小火慢炖1小时");
}
@Override
protected void season() {
System.out.println("加老抽、料酒、八角、桂皮调味");
}
}
// 3. 具体子类:清蒸鱼
class SteamedFish extends CookingRecipe {
@Override
protected void prepareIngredients() {
System.out.println("准备鲈鱼");
}
@Override
protected void processIngredients() {
System.out.println("处理鱼内脏,在鱼身上划几刀");
}
@Override
protected void cookInPot() {
System.out.println("锅中烧水,水开后放鱼蒸8分钟");
}
@Override
protected void season() {
System.out.println("倒掉蒸鱼的水,撒葱丝,淋热油,浇蒸鱼豉油");
}
}
// 4. 客户端:使用模板方法
public class Chef {
public static void main(String[] args) {
System.out.println("开始做红烧肉...");
CookingRecipe porkRecipe = new BraisedPork();
porkRecipe.cook();
System.out.println("\n开始做清蒸鱼...");
CookingRecipe fishRecipe = new SteamedFish();
fishRecipe.cook();
// 输出:
// 开始做红烧肉...
// 准备五花肉
// 五花肉切块,焯水去血沫
// 锅中倒油,放冰糖炒糖色,下肉翻炒,加酱油和水,小火慢炖1小时
// 加老抽、料酒、八角、桂皮调味
// 装盘上桌
//
// 开始做清蒸鱼...
// 准备鲈鱼
// 处理鱼内脏,在鱼身上划几刀
// 锅中烧水,水开后放鱼蒸8分钟
// 倒掉蒸鱼的水,撒葱丝,淋热油,浇蒸鱼豉油
// 装盘上桌
}
}
三、Android 常用模板方法模式案例与实现
案例 1:Activity 生命周期管理(基类 + 子类)
java
// 1. 抽象基类:BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
// 模板方法:Activity生命周期流程
@Override
protected final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 固定流程:设置布局 → 初始化视图 → 初始化数据 → 设置监听器
setContentView(getLayoutId());
initViews();
initData();
setupListeners();
}
// 必须由子类实现的抽象方法
protected abstract int getLayoutId();
protected abstract void initViews();
protected abstract void initData();
// 钩子方法:子类可选择性重写
protected void setupListeners() {
// 默认空实现,子类需要时重写
}
// 其他生命周期方法...
}
// 2. 具体子类:MainActivity
public class MainActivity extends BaseActivity {
private TextView textView;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initViews() {
textView = findViewById(R.id.textView);
}
@Override
protected void initData() {
textView.setText("欢迎来到主界面");
}
@Override
protected void setupListeners() {
textView.setOnClickListener(v -> {
// 处理点击事件
});
}
}
// 3. 具体子类:LoginActivity
public class LoginActivity extends BaseActivity {
private EditText usernameEditText;
private EditText passwordEditText;
private Button loginButton;
@Override
protected int getLayoutId() {
return R.layout.activity_login;
}
@Override
protected void initViews() {
usernameEditText = findViewById(R.id.username);
passwordEditText = findViewById(R.id.password);
loginButton = findViewById(R.id.loginButton);
}
@Override
protected void initData() {
// 初始化登录数据
}
@Override
protected void setupListeners() {
loginButton.setOnClickListener(v -> {
// 处理登录逻辑
});
}
}
优点:
-
统一流程:所有 Activity 遵循相同的初始化流程,减少代码重复;
-
提高可维护性:修改通用流程只需在基类中修改,无需修改所有子类;
-
强制规范:通过抽象方法确保子类实现必要的初始化步骤。
缺点:
- 灵活性降低:子类必须遵循基类定义的流程,难以改变整体结构;
- 过度抽象风险:基类可能变得过于庞大,包含过多方法;
- 子类依赖基类:子类行为依赖基类实现,基类修改可能影响所有子类。
案例 2:网络请求框架的基类(定义请求流程)
java
// 1. 抽象基类:定义网络请求的模板方法
public abstract class NetworkRequest {
// 模板方法:定义完整的网络请求流程
public final void execute() {
preRequest();
Response response = sendRequest();
postRequest(response);
processResponse(response);
}
// 前置处理:钩子方法,子类可选择性重写
protected void preRequest() {
System.out.println("准备请求:添加通用请求头");
}
// 必须由子类实现的抽象方法
protected abstract Response sendRequest();
// 后置处理:钩子方法
protected void postRequest(Response response) {
System.out.println("请求完成:记录请求耗时");
}
// 处理响应:抽象方法
protected abstract void processResponse(Response response);
}
// 2. 具体子类:GET请求
public class GetRequest extends NetworkRequest {
private String url;
public GetRequest(String url) {
this.url = url;
}
@Override
protected Response sendRequest() {
System.out.println("发送GET请求到:" + url);
// 实际发送GET请求的代码
return new Response(200, "{"data":"response data"}");
}
@Override
protected void processResponse(Response response) {
if (response.getStatusCode() == 200) {
System.out.println("处理成功响应:" + response.getBody());
} else {
System.out.println("处理失败响应:" + response.getStatusCode());
}
}
}
// 3. 具体子类:POST请求
public class PostRequest extends NetworkRequest {
private String url;
private String body;
public PostRequest(String url, String body) {
this.url = url;
this.body = body;
}
@Override
protected void preRequest() {
super.preRequest();
System.out.println("额外处理:设置POST请求体");
}
@Override
protected Response sendRequest() {
System.out.println("发送POST请求到:" + url + ",内容:" + body);
// 实际发送POST请求的代码
return new Response(201, "{"message":"created"}");
}
@Override
protected void processResponse(Response response) {
if (response.getStatusCode() == 201) {
System.out.println("资源创建成功:" + response.getBody());
} else {
System.out.println("处理失败响应:" + response.getStatusCode());
}
}
}
// 4. 客户端:使用网络请求模板
public class Client {
public static void main(String[] args) {
System.out.println("执行GET请求...");
NetworkRequest getRequest = new GetRequest("https://api.example.com/data");
getRequest.execute();
System.out.println("\n执行POST请求...");
NetworkRequest postRequest = new PostRequest("https://api.example.com/upload", "{"key":"value"}");
postRequest.execute();
// 输出:
// 执行GET请求...
// 准备请求:添加通用请求头
// 发送GET请求到:https://api.example.com/data
// 请求完成:记录请求耗时
// 处理成功响应:{"data":"response data"}
//
// 执行POST请求...
// 准备请求:添加通用请求头
// 额外处理:设置POST请求体
// 发送POST请求到:https://api.example.com/upload,内容:{"key":"value"}
// 请求完成:记录请求耗时
// 资源创建成功:{"message":"created"}
}
}
优点:
-
流程标准化:所有网络请求遵循相同流程,便于统一处理日志、错误等;
-
代码复用:通用逻辑(如添加请求头、记录耗时)只需在基类实现一次;
-
易于扩展:新增请求类型(如 PUT、DELETE)只需继承基类,实现必要方法。
缺点:
- 子类受限:子类必须遵循基类定义的流程,难以改变整体结构;
- 调试困难:多层继承和方法调用可能使调试变得复杂;
- 基类膨胀:基类可能包含越来越多的钩子方法,导致类变得庞大。
案例 3:数据库操作基类(定义 CRUD 流程)
java
// 1. 抽象基类:定义数据库操作的模板方法
public abstract class BaseDao<T> {
protected SQLiteDatabase db;
public BaseDao(SQLiteDatabase db) {
this.db = db;
}
// 模板方法:定义插入数据的流程
public final long insert(T entity) {
beginTransaction();
long rowId = doInsert(entity);
endTransaction();
afterInsert(rowId);
return rowId;
}
// 模板方法:定义查询数据的流程
public final List<T> query(String selection, String[] selectionArgs) {
beginTransaction();
List<T> result = doQuery(selection, selectionArgs);
endTransaction();
afterQuery(result);
return result;
}
// 事务开始:具体方法
private void beginTransaction() {
db.beginTransaction();
System.out.println("开始数据库事务");
}
// 事务结束:具体方法
private void endTransaction() {
db.setTransactionSuccessful();
db.endTransaction();
System.out.println("结束数据库事务");
}
// 必须由子类实现的抽象方法
protected abstract long doInsert(T entity);
protected abstract List<T> doQuery(String selection, String[] selectionArgs);
// 钩子方法:插入后处理
protected void afterInsert(long rowId) {
System.out.println("插入完成,行ID: " + rowId);
}
// 钩子方法:查询后处理
protected void afterQuery(List<T> result) {
System.out.println("查询完成,结果数量: " + (result != null ? result.size() : 0));
}
}
// 2. 具体子类:用户数据访问对象
public class UserDao extends BaseDao<User> {
private static final String TABLE_NAME = "users";
public UserDao(SQLiteDatabase db) {
super(db);
}
@Override
protected long doInsert(User user) {
ContentValues values = new ContentValues();
values.put("name", user.getName());
values.put("age", user.getAge());
return db.insert(TABLE_NAME, null, values);
}
@Override
protected List<User> doQuery(String selection, String[] selectionArgs) {
List<User> users = new ArrayList<>();
Cursor cursor = db.query(TABLE_NAME, null, selection, selectionArgs, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
int age = cursor.getInt(cursor.getColumnIndex("age"));
users.add(new User(name, age));
}
cursor.close();
}
return users;
}
@Override
protected void afterInsert(long rowId) {
super.afterInsert(rowId);
System.out.println("用户数据已插入,ID: " + rowId);
}
}
// 3. 客户端:使用数据库操作模板
public class DatabaseClient {
public static void main(String[] args) {
// 假设已创建SQLiteDatabase实例
SQLiteDatabase db = null; // 实际中初始化数据库
UserDao userDao = new UserDao(db);
// 插入用户
User user = new User("张三", 25);
long rowId = userDao.insert(user);
// 查询用户
List<User> users = userDao.query("age > ?", new String[]{"20"});
// 输出:
// 开始数据库事务
// 结束数据库事务
// 插入完成,行ID: 1
// 用户数据已插入,ID: 1
// 开始数据库事务
// 结束数据库事务
// 查询完成,结果数量: 1
}
}
优点:
-
事务一致性:所有数据库操作都遵循相同的事务管理流程,确保数据一致性;
-
减少重复代码:事务管理、日志记录等通用逻辑只需在基类实现一次;
-
便于维护:修改数据库操作流程(如添加缓存)只需在基类修改。
缺点:
- 灵活性受限:子类必须遵循基类定义的流程,难以实现特殊需求;
- 继承链脆弱:基类修改可能影响所有子类,需要谨慎设计;
- 过度抽象:对于简单的数据库操作,使用模板方法可能增加不必要的复杂度。
四、模板方法模式的适用场景与总结
适用场景:
- 流程固定:多个子类有共同的算法流程,但部分步骤实现不同(如 Activity 生命周期、网络请求流程);
- 代码复用:避免在多个子类中重复实现相同的代码(如事务管理、日志记录);
- 框架设计:框架定义通用流程,让开发者实现特定步骤(如 Android 的 AsyncTask);
- 强制规范:确保子类遵循统一的执行流程(如初始化顺序)。
核心优点:
- 代码复用:将通用代码放在基类,减少子类重复;
- 统一流程:确保所有子类遵循相同的算法结构;
- 易于维护:修改算法流程只需在基类修改,无需修改所有子类;
- 符合开闭原则:新增子类只需实现必要步骤,无需修改基类。
核心缺点:
- 灵活性降低:子类必须遵循基类定义的流程,难以改变整体结构;
- 类数量增加:每个不同实现都需要一个子类,可能导致类数量过多;
- 调试困难:多层继承和方法调用可能使调试变得复杂;
- 基类膨胀:基类可能包含越来越多的钩子方法,导致类变得庞大。
Android 中的最佳实践:
-
基类设计:在框架层使用模板方法定义通用流程(如 Activity 基类、网络请求基类);
-
钩子方法使用:合理使用钩子方法,让子类可选择性重写;
-
避免过度抽象:只在确实需要统一流程的地方使用模板方法,避免滥用;
-
结合其他模式:与工厂模式结合创建子类实例,与策略模式结合替代复杂的条件判断。
模板方法模式是 Android 开发中组织代码结构的重要工具,尤其适合处理有固定流程但部分步骤实现不同的场景,理解它有助于设计出更健壮、可维护的框架和库。