模板方法模式核心概念
- 模板方法模式:可以类比成:
- 你做手抓饼的步骤是固定的 (模板):和面→摊饼→加配料→出锅,但加的配料 (鸡蛋 / 培根 / 生菜) 可以自定义;
- 对应到编程中:定义一个算法的骨架 (模板方法),将算法中可变的步骤延迟到子类中实现,父类控制整体流程,子类定制具体细节;
- 类图:
- 实现代码:
/** * 抽象类:定义做饭的模板流程(对应类图AbstractCooking) */ abstract class AbstractCooking { // 模板方法:固定的做饭流程(对外暴露的核心方法) public cook(): void { this.prepareIngredients(); // 步骤1:准备食材(固定) this.cookMain(); // 步骤2:烹饪主食(可变,子类实现) this.addSeasoning(); // 步骤3:加调料(可变,子类实现) this.finish(); // 步骤4:装盘(固定) } // 具体方法:所有子类共享的固定步骤(protected,子类可访问但外部不可调用) protected prepareIngredients(): void { console.log("1. 清洗食材,准备厨具"); } // 抽象方法:子类必须实现的可变步骤 protected abstract cookMain(): void; protected abstract addSeasoning(): void; // 具体方法:所有子类共享的固定步骤 protected finish(): void { console.log("4. 装盘出锅,完成!"); console.log("------------------------"); } } /** * 具体子类1:煮面条(对应类图CookNoodles) */ class CookNoodles extends AbstractCooking { // 实现抽象方法:煮面条的核心逻辑 protected cookMain(): void { console.log("2. 烧水煮面条,煮3分钟至断生"); } // 实现抽象方法:加面条的调料 protected addSeasoning(): void { console.log("3. 加葱花、辣椒油、生抽调味"); } } /** * 具体子类2:煮米饭(对应类图CookRice) */ class CookRice extends AbstractCooking { // 实现抽象方法:煮米饭的核心逻辑 protected cookMain(): void { console.log("2. 米和水按1:1.2比例,电饭煲煮20分钟"); } // 实现抽象方法:加米饭的调料(比如蛋炒饭) protected addSeasoning(): void { console.log("3. 加鸡蛋、盐、酱油翻炒"); } } // 测试代码(生动演示) console.log("===== 煮面条 ====="); const noodleCook = new CookNoodles(); noodleCook.cook(); console.log("===== 煮米饭(蛋炒饭) ====="); const riceCook = new CookRice(); riceCook.cook();
常用使用场景
表单提交流程(最常用)
- 核心思路:抽象类定义 「验证 → 提交 → 处理响应 → 提示」 的固定流程,子类 (登录 / 注册表单) 仅需实现各自的验证规则和提交逻辑;
- 实现代码:
/** * 抽象类:表单提交模板(定义固定流程) */ abstract class BaseForm { // 模板方法:固定的表单提交流程 public submit(): void { console.log("===== 开始表单提交 ====="); // 步骤1:验证表单(可变,子类实现) const isValid = this.validate(); if (!isValid) { console.log("表单验证失败,终止提交"); return; } // 步骤2:提交数据(可变,子类实现) const data = this.getSubmitData(); this.sendRequest(data); // 步骤3:处理响应(可变,子类实现) this.handleResponse({ code: 200, msg: "提交成功" }); // 步骤4:提示结果(固定,所有表单共享) this.showTip(); console.log("===== 表单提交流程结束 =====\n"); } // 抽象方法:表单验证(子类实现) protected abstract validate(): boolean; // 抽象方法:获取提交数据(子类实现) protected abstract getSubmitData(): Record<string, any>; // 抽象方法:发送请求(子类实现) protected abstract sendRequest(data: Record<string, any>): void; // 抽象方法:处理响应(子类实现) protected abstract handleResponse(res: any): void; // 具体方法:固定的提示逻辑(所有子类共享) protected showTip(): void { console.log("✅ 操作完成,已给出用户反馈"); } } /** * 具体子类1:登录表单 */ class LoginForm extends BaseForm { private username: string; private password: string; constructor(username: string, password: string) { super(); this.username = username; this.password = password; } // 实现:登录表单验证规则 protected validate(): boolean { console.log("1. 验证登录表单:用户名/密码不能为空"); return !!this.username && !!this.password; } // 实现:获取登录提交数据 protected getSubmitData(): Record<string, any> { console.log("2. 组装登录请求数据"); return { username: this.username, password: this.password }; } // 实现:发送登录请求 protected sendRequest(data: Record<string, any>): void { console.log(`3. 发送登录请求:POST /api/login,数据:${JSON.stringify(data)}`); } // 实现:处理登录响应 protected handleResponse(res: any): void { console.log(`4. 处理登录响应:${res.msg},跳转到首页`); } } /** * 具体子类2:注册表单 */ class RegisterForm extends BaseForm { private phone: string; private code: string; constructor(phone: string, code: string) { super(); this.phone = phone; this.code = code; } // 实现:注册表单验证规则 protected validate(): boolean { console.log("1. 验证注册表单:手机号格式正确+验证码6位"); const phoneReg = /^1[3-9]\d{9}$/; const codeReg = /^\d{6}$/; return phoneReg.test(this.phone) && codeReg.test(this.code); } // 实现:获取注册提交数据 protected getSubmitData(): Record<string, any> { console.log("2. 组装注册请求数据"); return { phone: this.phone, code: this.code }; } // 实现:发送注册请求 protected sendRequest(data: Record<string, any>): void { console.log(`3. 发送注册请求:POST /api/register,数据:${JSON.stringify(data)}`); } // 实现:处理注册响应 protected handleResponse(res: any): void { console.log(`4. 处理注册响应:${res.msg},跳转到登录页`); } } // 测试:登录表单提交 const loginForm = new LoginForm("zhangsan", "123456"); loginForm.submit(); // 测试:注册表单提交 const registerForm = new RegisterForm("13800138000", "666666"); registerForm.submit();
组件生命周期封装(框架底层)
- 核心思路:模拟 Vue/React 的组件生命周期,抽象类定义 「初始化 → 挂载 → 更新 → 卸载」 的固定流程,子类 (业务组件) 仅需实现各自的生命周期钩子;
- 实现代码:
/** * 抽象类:组件生命周期模板(模拟框架底层) */ abstract class BaseComponent { // 模板方法:组件完整生命周期流程 public lifecycle(): void { console.log("===== 组件生命周期开始 ====="); this.init(); // 步骤1:初始化(固定) this.beforeMount(); // 步骤2:挂载前(可变) this.mounted(); // 步骤3:挂载(可变) this.updated(); // 步骤4:更新(可变) this.beforeUnmount(); // 步骤5:卸载前(可变) this.unmounted(); // 步骤6:卸载(可变) console.log("===== 组件生命周期结束 =====\n"); } // 具体方法:固定的初始化逻辑(框架内置) protected init(): void { console.log("1. [固定] 组件初始化:创建实例、绑定props"); } // 抽象方法:生命周期钩子(子类实现) protected abstract beforeMount(): void; protected abstract mounted(): void; protected abstract updated(): void; protected abstract beforeUnmount(): void; protected abstract unmounted(): void; } /** * 具体子类1:按钮组件(业务组件) */ class ButtonComponent extends BaseComponent { protected beforeMount(): void { console.log("2. [按钮组件] 挂载前:校验按钮类型(primary/default)"); } protected mounted(): void { console.log("3. [按钮组件] 挂载:渲染DOM、绑定点击事件"); } protected updated(): void { console.log("4. [按钮组件] 更新:更新按钮文案、样式"); } protected beforeUnmount(): void { console.log("5. [按钮组件] 卸载前:移除点击事件监听"); } protected unmounted(): void { console.log("6. [按钮组件] 卸载:删除DOM节点、释放内存"); } } /** * 具体子类2:列表组件(业务组件) */ class ListComponent extends BaseComponent { protected beforeMount(): void { console.log("2. [列表组件] 挂载前:校验列表数据源格式"); } protected mounted(): void { console.log("3. [列表组件] 挂载:渲染列表DOM、初始化分页"); } protected updated(): void { console.log("4. [列表组件] 更新:刷新列表数据、重置分页"); } protected beforeUnmount(): void { console.log("5. [列表组件] 卸载前:取消数据请求、清除定时器"); } protected unmounted(): void { console.log("6. [列表组件] 卸载:删除列表DOM、释放分页实例"); } } // 测试:按钮组件生命周期 const button = new ButtonComponent(); button.lifecycle(); // 测试:列表组件生命周期 const list = new ListComponent(); list.lifecycle();
页面渲染流程
- 核心思路:抽象类定义 「加载数据 → 渲染 DOM → 绑定事件 → 初始化子组件」 的固定流程,子类 (首页 / 列表页) 仅需实现各自的数据源和渲染逻辑;
- 实现代码:
/** * 抽象类:页面渲染模板(固定流程) */ abstract class BasePage { private container: HTMLElement; constructor(containerId: string) { this.container = document.getElementById(containerId)!; if (!this.container) throw new Error("容器不存在"); } // 模板方法:固定的页面渲染流程 public render(): void { console.log(`===== 渲染${this.getPageName()} =====`); // 步骤1:加载数据(可变) const data = this.loadData(); // 步骤2:渲染DOM(可变) this.renderDOM(data); // 步骤3:绑定事件(固定) this.bindEvents(); // 步骤4:初始化子组件(可变) this.initSubComponents(); console.log(`===== ${this.getPageName()}渲染完成 =====\n`); } // 抽象方法:获取页面名称(子类实现) protected abstract getPageName(): string; // 抽象方法:加载页面数据(子类实现) protected abstract loadData(): any; // 抽象方法:渲染DOM(子类实现) protected abstract renderDOM(data: any): void; // 具体方法:固定的事件绑定(所有页面共享) protected bindEvents(): void { console.log("3. [固定] 绑定通用事件:返回顶部、暗黑模式切换"); // 模拟绑定通用事件 this.container.addEventListener("click", (e) => { if ((e.target as HTMLElement).dataset.action === "backTop") { window.scrollTo(0, 0); } }); } // 抽象方法:初始化子组件(子类实现) protected abstract initSubComponents(): void; } /** * 具体子类1:首页 */ class HomePage extends BasePage { constructor(containerId: string) { super(containerId); } protected getPageName(): string { return "首页"; } // 实现:加载首页数据 protected loadData(): any { console.log("1. [首页] 加载数据:轮播图、推荐商品"); return { banners: ["banner1.jpg", "banner2.jpg"], recommendGoods: [{ id: 1, name: "推荐商品1" }, { id: 2, name: "推荐商品2" }], }; } // 实现:渲染首页DOM protected renderDOM(data: any): void { console.log("2. [首页] 渲染DOM:轮播图容器、推荐商品列表"); // 模拟渲染DOM this.container.innerHTML = ` <div class="banner">${data.banners.map((src: string) => `<img src="${src}">`).join("")}</div> <div class="recommend">${data.recommendGoods.map((g: any) => `<div>${g.name}</div>`).join("")}</div> `; } // 实现:初始化首页子组件 protected initSubComponents(): void { console.log("4. [首页] 初始化子组件:轮播图插件、商品卡片组件"); // 模拟初始化轮播图插件 console.log("✅ 轮播图插件初始化完成"); } } /** * 具体子类2:商品列表页 */ class GoodsListPage extends BasePage { private categoryId: number; constructor(containerId: string, categoryId: number) { super(containerId); this.categoryId = categoryId; } protected getPageName(): string { return "商品列表页"; } // 实现:加载列表页数据 protected loadData(): any { console.log(`1. [列表页] 加载数据:分类${this.categoryId}下的商品`); return { goodsList: [{ id: 101, name: "商品1", price: 99 }, { id: 102, name: "商品2", price: 199 }], pagination: { page: 1, size: 10, total: 20 }, }; } // 实现:渲染列表页DOM protected renderDOM(data: any): void { console.log("2. [列表页] 渲染DOM:商品列表、分页控件"); // 模拟渲染DOM this.container.innerHTML = ` <div class="goods-list">${data.goodsList.map((g: any) => `<div>${g.name} - ¥${g.price}</div>`).join("")}</div> <div class="pagination">第${data.pagination.page}页/共${data.pagination.total}条</div> `; } // 实现:初始化列表页子组件 protected initSubComponents(): void { console.log("4. [列表页] 初始化子组件:分页插件、筛选组件"); // 模拟初始化分页插件 console.log("✅ 分页插件初始化完成"); } } // 测试:渲染首页(需提前在HTML中添加 <div id="home"></div>) const homePage = new HomePage("home"); homePage.render(); // 测试:渲染商品列表页(需提前在HTML中添加 <div id="goodsList"></div>) const goodsListPage = new GoodsListPage("goodsList", 5); goodsListPage.render();
模板方法模式的优缺点
- 优点:
- 代码复用:将公共代码提取到父类;
- 封装不变部分,扩展可变部分:符合开闭原则;
- 控制反转:父类调用子类的方法;
- 提取公共行为:避免重复代码;
- 缺点:
- 继承限制:子类必须继承父类,可能导致类层次复杂;
- 调试困难:流程在父类,实现在子类,代码跳转多;
- 违反里氏替换原则:如果子类重写了太多方法;
最佳实践建议
- 尽量使用钩子方法,而不是强制子类实现抽象方法;
- 模板方法应声明为 final (通过注释约定);
- 尽量减少子类需要实现的方法数量;
- 钩子方法提供默认实现;
- 注意方法访问权限:模板方法 public,具体步骤 protected;
享元模式(结构型模式)
上一篇