前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript设计模式之模板方法模式

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

作者头像
一粒小麦
发布2019-11-09 17:10:49
6060
发布2019-11-09 17:10:49
举报
文章被收录于专栏:一Li小麦一Li小麦一Li小麦

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

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

再比如,我有女朋友了,我有男朋友了,我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. 业务组件可复用,可插拔

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模板方法的引入
  • 关于Admin
  • 使用场景
  • 生命周期钩子:个性化处理
  • 好莱坞原则
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档