模板方法模式核心概念

  1. 模板方法模式:可以类比成:
    1. 你做手抓饼的步骤是固定的 (模板):和面→摊饼→加配料→出锅,但加的配料 (鸡蛋 / 培根 / 生菜) 可以自定义;
    2. 对应到编程中:定义一个算法的骨架 (模板方法),将算法中可变的步骤延迟到子类中实现,父类控制整体流程,子类定制具体细节;
  2. 类图:
  3. 实现代码:
    /**
     * 抽象类:定义做饭的模板流程(对应类图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();
    

常用使用场景

表单提交流程(最常用)

  1. 核心思路:抽象类定义 「验证 → 提交 → 处理响应 → 提示」 的固定流程,子类 (登录 / 注册表单) 仅需实现各自的验证规则和提交逻辑;
  2. 实现代码:
    /**
     * 抽象类:表单提交模板(定义固定流程)
     */
    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();
    

组件生命周期封装(框架底层)

  1. 核心思路:模拟 Vue/React 的组件生命周期,抽象类定义 「初始化 → 挂载 → 更新 → 卸载」 的固定流程,子类 (业务组件) 仅需实现各自的生命周期钩子;
  2. 实现代码:
    /**
     * 抽象类:组件生命周期模板(模拟框架底层)
     */
    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();
    

页面渲染流程

  1. 核心思路:抽象类定义 「加载数据 → 渲染 DOM → 绑定事件 → 初始化子组件」 的固定流程,子类 (首页 / 列表页) 仅需实现各自的数据源和渲染逻辑;
  2. 实现代码:
    /**
     * 抽象类:页面渲染模板(固定流程)
     */
    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();
    

模板方法模式的优缺点

  1. 优点:
    1. 代码复用:将公共代码提取到父类;
    2. 封装不变部分,扩展可变部分:符合开闭原则;
    3. 控制反转:父类调用子类的方法;
    4. 提取公共行为:避免重复代码;
  2. 缺点:
    1. 继承限制:子类必须继承父类,可能导致类层次复杂;
    2. 调试困难:流程在父类,实现在子类,代码跳转多;
    3. 违反里氏替换原则:如果子类重写了太多方法;

最佳实践建议

  1. 尽量使用钩子方法,而不是强制子类实现抽象方法;
  2. 模板方法应声明为 final (通过注释约定)
  3. 尽量减少子类需要实现的方法数量;
  4. 钩子方法提供默认实现;
  5. 注意方法访问权限:模板方法 public,具体步骤 protected
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 模板方法模式核心概念
  2. 2. 常用使用场景
    1. 2.1. 表单提交流程(最常用)
    2. 2.2. 组件生命周期封装(框架底层)
    3. 2.3. 页面渲染流程
  3. 3. 模板方法模式的优缺点
  4. 4. 最佳实践建议