专栏首页一Li小麦JavaScript设计模式之模板方法模式

JavaScript设计模式之模板方法模式

泡茶和泡咖啡,整个过程都是类似的。我们可以抽离其中的概念:泡东西=>煮水->收集浸泡材料->泡->等。

玩一个抽象概念的文字游戏:我去停宝马,我去停奥迪,我去停旋风冲锋,我去停三轮=>你都可以说:我去停车了。

再比如,我有女朋友了,我有男朋友了,我new了一个Object->我有对象了。

这很明显就是继承,万物皆对象的角度来说,如果对象的定义足够广泛,女朋友什么的都可以包含在其中。而JavaScript真正用到继承的地方其实不多。如果你想让一个对象拥有一个对象的属性,常用的方法就是mixin。

const mixin=(obj1,obj2)=>{    Object.keys(obj2).forEach((key)=>{        obj1[key]=obj2[key];    });    return obj1;}

这样obj1就拥有了obj2的属性。但对于继承类来说,mixin后this的指针可能是不正确的。

你也可以通过prototype来实现继承:

class A{  getB:new B();}

而模板方法就是基于继承的一种设计模式。它至少由两个类组成,一个是抽象的父类,一个是负责业务场景的具体类。在父类中通常包括一些子类用到的公共方法,也包括执行顺序算法。

假如抽象父类有n个子类,各自都有自己的执行逻辑,那么可以把相同的行为进行提炼封装到父类中。不同的地方留待子类来实现,就是所谓的"模板方法模式"。

模板方法的引入

不举那些比较虚的例子,直接拿工作中的实践来说,一个管理系统的页面,大致都有这些要素:

请求数据->加载列表->渲染条件查询控件->渲染操作按钮

但是业务实在是太多了。基本上所有页面都不会是那么标准,而是它的变种。以两个页面比如教师管理和学生管理来说,二者的的流程可能是这样的:

教师管理:请求教师数据->加载列表->渲染查询控件(工号)->(不渲染操作按钮)学生管理:请求学生数据->加载列表->渲染查询控件(学号)->渲染增删改查按钮

我们思考这里面的流程,流程是相似的。因此可以实现一个Admin抽象类:

class Admin{  constructor(info){    this.info=info  }  // 获取api前缀  getApi(operate){    return `${this.info.api}/${operate}`;  }
  // 查  async getData(params){    let api=this.getApi('list')    return await http.post(api,params);  }  // 增  async addData(params){    let api=this.getApi('add')    return await http.post(api,params);  }  // 改  async updateData(params){    let api=this.getApi('update')    return await http.post(api,params);  }  // 删  async deletData(params){    let api=this.getApi('delete')    return await http.post(api,params);  }
  // 加载数据  loadData(data){    var list='';    data.list.forEach((x)=>{      list+=`<li>${x.name}-${x.phoneNumber}</li>`    })    document.querySelectorAll('#root').innerHTML=`<h1>${this.info.title}</h2>`;    document.querySelectorAll('#list').innerHTML=list;  }
  // 操作按钮绑事件  btnBindEvent(params){    let operates=['add','delete','update','get'];    operates.forEach((x)=>{      document.querySelector(`#${x}`).addEventListener('click',async (e)=>{        const data=await this[`${x}Data`](params);        this.loadData(data);      })    })  }  // 获取查询条件  getPrams(){    //... 子类自己实现  }
  // 初始渲染  async render(data){    const data=await this.getData();    const params=this.getParams();    this.loadData(data);    this.btnBindEvent(params);  }}
class Person{
}
Person.prototype=new Admin({  title:'人员管理',  api:'/person'});// 复写缺失的方法Person.prototype.getParams=function(){  // ...}
// Person 可以修改loadData等等的方法。

这样你new一个Admin类,做好配置,一整套页面模板就出来了。

关于Admin

模板方法模式是一种严重依赖抽象类的设计模式。

抽象方法被声明在抽象类中,抽象方法并没有具体的实现过程,是一些“哑”方法,或者叫做插槽方法。比如Admin类中的getParams、loadData都被声明为抽象方法。当子类继承了这个抽象类时,必须重写父类的抽象方法。如果Person没有相应的方法,那么100%会得不到预期的结果。

除了抽象方法之外,如果每个子类中都有一些同样的具体实现方法,那这些方法也可以选择放在抽象类中,这可以节省代码以达到复用的效果,这些方法叫作具体方法。当代码需要改变时,我们只需要改动抽象类里的具体方法就可以了。

当我们在JavaScript中使用原型继承来模拟传统的类式继承时,并没有编译器帮助我们进行任何形式的检查,我们也没有办法保证子类会重写父类中的“抽象方法”。

一个解决方法是"内置"一套解决方案。比如getParam可以检索指定数据模型的值(在react或vue中是检索当前指定节点的状态,在jQuery中则是指定区域下的表单域),但是内置的成本比较大,你得跟后端,跟自己做很多很多的约定。写出的程序刻板而缺乏灵活性。在变通之下也缺乏良好的应对。

另一个方案,就是抛异常。

  getPrams(){    throw new Error('找不到实现方法')  }

使用场景

从大的方面来讲,模板方法模式常被架构师用于搭建项目的框架,架构师定好了框架的骨架,程序员继承框架的结构之后,负责往里面填空。笔者工作场景就是基于这种模板模式。

往小了说,我们可以问一个问题:

如何设计一个ui组件?

答案很简单:

指定容器->请求数据->绘制界面->通知渲染完毕。

你把上述过程封装起来就可以了。

在react中的componentDidAmount等生命周期其实就是模板方法的实现。

生命周期钩子:个性化处理

在react或Vue中,会详尽地设计了多个生命周期钩子,其实就是模板方法的实现。

设想我们的Admin类已经适用了大多数场景,但业务的内容是无穷无尽的。比方说我有的页面需要做前端权限拦截。如果不符合某种条件就不会渲染这个页面,直接不渲染或跳转别的界面。

class Admin{  //...  // 默认  beforeRender(){    return true;  }
  // 初始渲染  async render(){    const before= this.beforeRender()    if(before){      const data=await this.getData();        const params=this.getParams();        this.loadData(data);        this.btnBindEvent(params);    }else{      alert('无权限查看!')    }  }}Person.prototype=new Admin({  title:'人员管理',  api:'/person'});
Person.prototype.beforeRender=function(condiction){   // ...CONDICTION}

正常情况下,你不写beforeRender也没事。假如要控制访问权限,就可以复写beforeRender方法。

好莱坞原则

“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。

梦想进入好莱坞的人们,你们不要找好莱坞。那怎么办呢?答案是,让好莱坞来找你!

这就是非常著名的好莱坞原则。

模板方法模式充分的体现了“好莱坞”原则。IOC是Inversion of Control的简称,IOC的原理就是基于好莱坞原则,所有的组件都是被动的(Passive),所有的组件初始化和调用都由容器负责。

所有的framework都是遵循好莱坞原则设计的,否则就不叫framework。framework使用IoC的目的:

  1. 对基于接口编程的支持
  2. 减少单件和抽象工厂的依赖
  3. 降低业务和框架的耦合
  4. 业务组件可复用,可插拔

在这一原则的指导下,我们允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。

模板方法模式是好莱坞原则的一个典型使用场景,它与好莱坞原则的联系非常明显,当我们用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类,只负责提供一些设计上的细节。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 组件设计基础(2)

    早期的react设计了许多的生命周期钩子。它们严格定义了组件的生命周期,一般说,生命周期可能会经历如下三个过程:

    一粒小麦
  • 《javascript数据结构和算法》读书笔记(3):链表

    储存多个元素,数组是最常用的。无论何种语言,都实现了数组。但是大多数语言中,数组的长度是固定的。数组修改操作的成本非常高。

    一粒小麦
  • react核心api

    react从16年12月开始,已经学了有2年多了。react引导了作者找到了第一份比较专职的前端工作。react 2014年横空出世,以其革命性的写法,带动了前...

    一粒小麦
  • C#项目代码规范

       小菜就是小菜,几个人搞出来的项目,让公司大牛稍微看了下,最后送出了惨不忍睹四个字。命名各种各样,五花八门,大写英文、小写英文、大写拼音、小写拼音、英文和拼...

    aehyok
  • 解决DropDownList常见问题三则

    1,给SelectedValue赋值时,如果Items中没有该项,则报XXX异常; 2,在绑定时,如果数据源返回null,它将不做任何动作,而我们一般习惯清空;...

    大石头
  • 泰迪熊智能玩具泄露数百万音频信息和用户密码

    正打算给自家小孩买可联网玩具或智能玩具的家长请注意,你可能需要三思而后行了,这些诡异的玩具会给小朋友们带来一些潜在的隐私和数据安全风险。

    FB客服
  • Supervisor重新加载配置启动新的进程

    (adsbygoogle = window.adsbygoogle || []).push({});

    双面人
  • TypeScript设计模式之中介者、观察者

    看看用TypeScript怎样实现常见的设计模式,顺便复习一下。 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到...

    用户1147588
  • 详解Python类定义中的各种方法

    首先应该明确,在面向对象程序设计中,函数和方法这两个概念是有本质区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数传递过去,...

    Python小屋屋主
  • 绘图(四)小练习[画心]

    动态的绘制图形,需要使用到invalidate和postInvalidate函数,本次我们使用的是:

    李小白是一只喵

扫码关注云+社区

领取腾讯云代金券