“浅尝”JavaScript设计模式

什么是设计模式?

设计模式:根据不一样场景建立不一样类型的对象的套路被称为设计模式。javascript

使用设计模式的主要缘由?

①可维护性:设计模式有助于下降模块间的耦合程度,这使对代码进行重构和换用不一样的模块变得更容易,也使得程序员在大型团队中的工做以及与其余程序员的合做变得更容易。
②沟通:设计模式为处理不一样类型的对象提供了一套通用的术语,程序员能够更简明地描述本身的系统的工做方式,你不用进行冗长的说明,每每一句话,我是用了什么设计模式,每一个模式有本身的名称,这意味着你能够在较高层面上进行讨论,而没必要涉足过多的细节
③性能:某些模式是起优化做用的模式,能够大幅度提升程序的运行速度,并减小须要传送到客户端的代码量前端

设计模式

实现设计模式比较容,懂得应该在何时使用什么模式比较困难,未搞懂设计模式的用途就盲目套用,是一种不安全的作法,你应该保证所选用的模式就是最恰当的那种,而且不要过分牺牲性能。vue

1、单例模式

<font size="4" color="red">确保单体对象只存在一个实例。</font>

业务场景:当咱们使用node启动一个服务链接数据库的时候咱们通常会建立一个链接数据库的实例(这个实例就是单例)。每一个请求对于数据的请求都是经过这个单例的,不会为没个请求去建立单独的实例,一个单例便于统一管理。java

var Single = (function () {
  var instance
  var createSingle = function (name) {
    if (instance) {
      return instance
    }
    this.name = name
    instance = this
    return instance
  }
  return createSingle
})();

var single1 = new Single('1')
var single2 = new Single('2')
console.log(single1.name) // '1'
console.log(single2.name) // '2'

2、工厂模式

<font size="4" color="red">工厂模式使用一个方法来决定究竟要实例化哪一个具体的类。</font>
  • 简单工厂模式

使用一个总的工厂来完成对于全部类的分发。<font color="red">情景再现:</font>一个<font color="red">宠物店</font>里面有着许多宠物,客人能够经过向宠物店<font color="red">传递消息</font> 因而次日咱们就能够到宠物店获取一只猫<font color="red">猫</font>
①工厂(宠物店) ②传参(传递消息) ③实例化对应类(猫)node

class Cat{
   constructor() {
       this.name = '猫'
   }
}
class Dog{
   constructor() {
       this.name = '狗'
   }
}
class Factory {
    constructor(role){
      return this.switchRole(role)
    }
    switchRole(role){
      switch(role){
        case '猫':
            return new Cat()
        case '狗':
            return new Dog()
        default:
            return {}
      }
    }
}
var dog = new Factory('狗') // {name:'猫'}
var cat = new Factory('猫') // {name:'狗'}

简单的工厂模式咱们已经实现了,这时候咱们须要又要发散咱们的小脑壳去好好揣摩这个模式,咱们发现若是每次宠物店又有了新的宠物能够出售,例现在天宠物店引进了乌龟、宠物猪,那咱们不只要先实现相对应的类,还要在<font color="red">Factory</font>中的<font color="red">switchRole()</font>补充条件。若是建立实例的方法的逻辑发生变化,工厂的类就会被屡次修改程序员

  • 复杂工厂模式

既然简单工厂模式,不能知足咱们所有的业务需求,那就只能进化变身了。<font color="red">《javascript设计模式》</font> 给了定义:真正的工厂模式与简单工厂模式区别在于,它不是另外使用一个类或对象建立实例,而是使用<font color="red">子类</font>工厂是一个将其成员对象的实例推送到子类中进行的类 也就是咱们在定义咱们看到真正的工厂模式,是提供一个工厂的<font color="red">父类抽象类</font>,对于根据传参实现实例化的过程是放在子类中实现。数据库

再思考一个问题:猫、狗、乌龟、宠物猪、这些类是否能够再进行细分出现了加菲猫、波斯猫、柴犬、阿拉斯加等子类。在购买宠物的时候是否须要特别的条件? 上述工厂的子类能够解决这个问题:出现了<font color="red">专卖店</font>专门卖猫的,卖狗的,卖宠物猪的,这些专卖店(<font color="red">工厂子类</font>)各自维护本身的类,当你须要新的专卖店你能够从新实现一个<font color="red">工厂子类</font>设计模式

class Cat{
   constructor() {
       this.name = '猫'
   }
}
class Garfield {
   constructor() {
       this.name = '加菲猫'
   }
}
class Persian {
   constructor() {
       this.name = '波斯猫'
   }
}
class Dog{
   constructor() {
       this.name = '狗'
   }
}
// 定义成为抽象类,工厂的父类,不接受任何修改
class Factory {
    constructor(role){
      return this.createModule(role)
    }
    createModule(role){
        return new Error('我是抽象类不要改我,也不要是实例化我')
    }
}
// 猫的专卖工厂,须要重写父类那里继承来的返回实例的方法。购买猫的逻辑能够放在找个类中实现。
class CatFactory extends Factory{
    constructor(role){
      super(role)
    }
    // 重写createFactory的方法
    createModule(role){
        switch(role){
            case '加菲猫':
                return new Garfield()
            case '波斯猫':
                return new Persian()
            default:
                return {}
          }
    }
}
.... 狗、宠物猪、乌龟的均可以从新继承父类Factory。
var catFac = new CatFactory('波斯猫')
console.log(catFac)

总结:<font color="red">复杂工厂模式</font>将原有的简单工厂模式下的工厂类变为<font color="red">抽象类</font>根据输出<font color="red">实例</font>的不一样来构建不一样的<font color="red">工厂子类</font>。这样既不会修改到工厂抽象类,符合设计原则,又提供了可拓展性。api

3、桥接模式

<font size="4" color="red">将抽象与其实现隔离开来,以便两者独立变化。</font>

你们看到上面的那句话会以为有点摸不清头脑看一下下面的代码:数组

//一个事件的监听,点击元素得到id,根据得到的id咱们发送请求查询对应id的猫
element.addEventListener('click',getCatById)
var getCatById = function(e){
   var id = this.id
   asyncRequst('Get',`cat.url?id=${id}`,function(resp){
       console.log('我已经获取了信息')
   })
}

你们看一下getCatById这个api函数咱们能够理解为<font color="red">抽象</font> 而点击这个过程是<font color="red">实现</font>的效果,可是咱们发现getCatById与实现逻辑并无彻底分割,<font color="red">getCatById</font>是一个只能工做在浏览器中的API,根据事件监听器函数的工做机制,事件对象天然会被做为第一个参数传递给这个函数,在本例中并无使用,可是咱们看到了<font color="red">var id = this.id</font>,若是你要对这个API作单元测试,或者命令环境中执行,那就只能祝你好运了,任何一个API都不该该<font color="red">把它与任何特定环境搅在一块儿</font>

// 改写getCatById 将抽象与现实彻底隔离,抽象彻底依赖传参,同时咱们在别的地方也能够引用,不受制与业务
var getCatById = function(id,callback){
   asyncRequst('Get',`cat.url?id=${id}`,function(resp){
       console.log('我已经获取了信息')
   })
}

这个时候咱们发现了如今抽象出来的getCatById已经不能直接做为事件函数的回调了,这时候咱们要隆重的请出咱们<font color="red">桥接模式</font> 此处应该有撒花。

element.addEventListener('click',getCatByIdBridge)
var getCatByIdBridge(e){ // getCatByIdBridge 桥接元素
    getCatById(this.id,function(cat){
        console.log('request cat')
    })
}

咱们能够看到<font color="red">getCatByIdBridge</font>这个就是桥接模式的产物。沟通抽象与现实的<font color="red">桥梁</font>。有了这层桥接,getCatById这个API的使用范围就大大的拓宽了,没有与事件对象捆绑在了一块儿。

总结:在实现API的时候,桥接模式很是有用,咱们用这个模式来<font color="red">弱化</font>API与使用他的类和对象之间的耦合,这种模式对于js中常见的<font color="red">事件驱动</font>有大的裨益。

4、策略模式

<font size="4" color="red">定义一系列的规则,根据环境的不一样咱们执行不一样的规则,来避免大量重复的工做。</font>

策略模式根据上述的说法能够隐隐的猜到了,要实行策略模式咱们须要两个类:首先咱们须要一个定义了一系列规则的<font color="red">策略类</font>这个是整个策略模式的基石。以后有一个暴露对外方法的<font color="red">环境类</font><font color="red">经过传递不一样的参数给环境类,环境类从策类中选取不一样的方法执行,最终返回结果</font>

业务场景:做为一个前端不免跑不了一个表单验证。当你不使用库的时候不免须要手写一些正则校验、判空、判类型等方式。
这个时候你的代码就是以下的:

// vue下的表单校验
checkForm () {
  if (this.form.realName === '') {
    Toast.fail('真实姓名不能为空')
    return false
  }
  if (!/^[\u4e00-\u9fa5]{0,}$/.test(this.form.realName)) {
    Toast.fail('请输入中文')
    return false
  }
  if (!/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(this.form.idCardNum)) {
    Toast.fail('请输入正确的身份证格式')
    return false
  }
  return true
},

注意:这样写是没有问题的,可是咱们仔细看这个代码会发现,这个代码几乎<font color="red">不可复用</font>,即便在其余的地方咱们须要对字段断定中文,咱们就须要从新再用正则断定,其次当一个表单内部须要校验字段不少,那么上述方法将会过于<font color="red">冗余</font>

修改思路:将校验的具体的实现咱们放入策略类,这样对于可重复使用检验规则咱们使用一个单个方法统一维护,以后将暴露环境类给各个业务场景使用,传入所需的方法名、检测值而后调用策略返回结果。

// 策略类为表单校验正则及及自定义的规则
var rules = {
  // 是否中文
  isChinese: function (value) {
    if (/^[\u4e00-\u9fa5]{0,}$/.test(value)) {
      return true
    } else {
      return false
    }
  },
  //  是否不为空
  notNull: function (value) {
    if (value !== '') {
      return true
    } else {
      return false
    }
  },
.... // 不一样策略
}


// 环境类
var validate = function(rule,value) {
    return rules[rule](value);
};

//业务执行
const isChinese = validate('isChinese',value)
const notNull = validate('notNull',value)
const checkResult = isChinese||notNull
if(checkResult){
    .....
}

总结:一个简单的策略模式就已经实现了,将校验的抽象方法放在策略类,而后根据参数的不一样去调用不一样的策略,实现了策略的复用,也实现了代码的简化。可是这样的策略是你心中真正的爱么兄弟?

缺点:上述的代码已经知足了策略模式,可是对于具体业务的支持彷佛还有点小瑕疵,首先上述的业务执行,你会发现对于单个校验咱们返回的是boolean值,若是咱们须要有失败的回调函数的调用,那就仍是须要<font color="red">判断语句</font>的加入,真的是鱼和熊掌不可兼得,也就是说上述的代码,<font color="red">只能支持表单项所有检测完成后总的失败回调,对于当个表单项的失败没法支持,用户每每输入完成所有表单项后才被告知表单中有错误,并且还不知道具体是哪一个</font>。

优化:修改环境类,参数对象造成的数组或单个参数对象传入,参数对象传入时提供失败函数,对外提供一个检验方法,遍历检查参数对象数组,所有成功返回true,失败执行失败回调函数同时返回false。

class Validate {
  constructor () {
    this.cache = []
    if (Array.isArray(arguments[0])) {
      // 数组的单个元素{rule:string[规则的名称],value:any[校验的值],efn:fn[失败的回调]}
      this.cache = arguments[0]
    }
    // 传入参数为对象时
    this.cache.push(arguments[0])
  }
  // 执行校验,失败的话执行失败的回调,成功静默,全部的参数符合规则则返回true
  valid () {
    let i = 0
    for (const value of this.cache) {
      if (rules[value.rule] && rules[value.rule](value.value)) {
        i++
      } else {
        if (value.efn) value.efn()
        return false
      }
    }
    return i === this.cache.length
  }
}

总结:策略模式能够应用<font color="red">使用判断语句多的时候,判断内容为同种模式</font>的状况下。策略模式中的策略类能够进行复用,从而避免不少地方的复制粘贴,独立的策略类易于理解、切换、拓展。

5、中介者模式

<font size="4" color="red">中介者模式的做用就是解除对象与对象之间的紧耦合关系。对象与对象中的通讯以中介者为媒介触发。中介者使各对象之间耦合松散,并且能够独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。</font>

场景分析:设定两个角色<font color="red">房东</font>、<font color="red">租客</font>。<font color="red">房东</font>与<font color="red">租客</font>组成的是多对多的网格关系,一个<font color="red">房东</font>能够与多名<font color="red">租客</font>接触,询问是否有租房意愿,同理<font color="red">租客</font>能够向多名<font color="red">房东</font>打探是否有房源出售。

前置条件咱们实例化两个角色<font color="red">房东</font>、<font color="red">租客</font>,咱们使用<font color="red">简单工厂模式</font>。

// 房东类
class Owener{
    constructor(name){
        this.name=name
    }
    // 想要出租
    sell(renters=[]){
        renters.map(renter=>{
            renter.onMessage(`${this.name}想要出租房子`)
        })
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
// 租客类
class Renter{
    constructor(name){
        this.name=name
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Factory{
    constructor(role,name){  
        this.name = name
        return this.switchRole(role,name)
    }
    switchRole(role,name){
        switch (role) {
            case 'owner':
                return new Owener(name)
            case 'renter':
                return new Renter(name)
            default:
                return new Error('无当前角色')
        }
    }
}
var owner1 = new Factory('owner','房东一')
var owner2 = new Factory('owner','房东二')
var renter1 = new Factory('renter','租客一')
var renter2 = new Factory('renter','租客二')
var renter3 = new Factory('renter','租客三')
owner.sell([renter1,renter2,renter3])

上述代码完成了<font color="red">房东一</font> 发布出租房子的意愿,三名<font color="red">租客</font>接受到了消息。反向同理也能够实现。租客发布消息,房东收到,可是咱们发现<font color="red">房东</font> 与<font color="red">租客</font>之间。仍是存在紧密的耦合,<font color="red">房东</font>与<font color="red">租客</font>之间不一样的关系须要自行不一样的方法,那整个房东类会变得及其臃肿,反之亦然。因而咱们引入<font color="red">中介者模式</font>

<font color="red">中介者模式</font>在上述例子中理解为<font color="red">中介公司</font>,扮演了维护<font color="red">房东</font> 和<font color="red">租客</font>关系的桥梁,<font color="red">租客</font>和<font color="red">房东</font>类只要考虑各自的行为,不须要考虑行为会给那些关系对象带来影响。这些任务交给咱们的
<font color="red">中介者</font>

优化:生成一个<font color="red">中介者</font>来处理<font color="red">房东</font>与<font color="red">租客</font>的相互调用,以及肯定相互关系,<font color="red">中介者</font>提供一个双向的方法,供<font color="red">房东</font>和<font color="red">租客</font>调用,<font color="red">而后实现对相关对象的分发</font>。

class Owener{
    constructor(name){
        this.name=name
    }
    // 想要出租
    sell(mediator){
        mediator.sendMessage('owner',`${this.name}想要出租房子`)
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Renter{
    constructor(name){
        this.name=name
    }
    // 想要租房
    rent(mediator){
        mediator.sendMessage('renter',`${this.name}想要租房子`)
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Factory{
    constructor(role,name){  
        this.name = name
        return this.switchRole(role,name)
    }
    switchRole(role,name){
        switch (role) {
            case 'owner':
                return new Owener(name)
                break;
            case 'renter':
                return new Renter(name)
                break;
            default:
                return new Error('无当前角色')
                break;
        }
    }
}
class Mediator{
    constructor(owner,renter){
      // 房东集合
      this.owner = owner
      // 房客集合
      this.renter = renter
    }
    sendMessage(role,msg){
       if(role === 'owner'){
           for(const value of this.renter){
               value.onMessage(msg)
           }
       }
       if(role === 'renter'){
           for(const value of this.owner){
               value.onMessage(msg)
           }
       }
    }
}
var owner1 = new Factory('owner','房东一')
var owner2 = new Factory('owner','房东二')
var renter1 = new Factory('renter','租客一')
var renter2 = new Factory('renter','租客二')
var renter3 = new Factory('renter','租客三')
var mediator = new Mediator([owner1,owner2],[renter1,renter2,renter3])
owner1.sell(mediator)
租客一知道了房东一想要出租房子
租客二知道了房东一想要出租房子
租客三知道了房东一想要出租房子
renter1.rent(mediator)
房东一知道了租客一想要租房子
房东二知道了租客一想要租房子

总结:<font color="red">房东</font>、<font color="red">租客</font>各自维护本身的行为,通知调用中介者。<font color="red">中介者</font>维护对象关系以及对象方法的调用。<font color="red">优势</font>:对象的关系在中介者中能够自由定义、一目了然。减小了两个类的臃肿。去除了两个对象之间的紧耦合。<font color="red">缺点</font>:两个关系类的不臃肿换来了<font color="red">中介者</font>类的臃肿。

6、装饰者模式

<font size="4" color="red">为对象增添特性的技术,它并不使用建立新子类这种手段</font>

场景分析:看了别的文章都是自行车的场景那我也来呗,嘻嘻~,自行车商店:出售A、B、C、D四种牌子的自行车,这时候能够选择配件车灯、前置篮。若是按照正常创造子类的方法,咱们首先定义 A、B、C、D四个牌子的自行车父类,而后再根据配件经过继承父类来实现子类。

// A自行车父类
class Bicycle{
    constructor(){
        this.type = 'A'
    }
    getBicycle(){
        console.log('A自行车的售价100')
    }
}
// 拥有车灯的A类自行车类
class BicycleDeng extends Bicycle{
    constructor(){
        super()
    }
    setDeng(){
        console.log('我安装上了大灯')
    }
}

上述代码咱们能够发现:当咱们须要穷尽场景中可能出现的自行车的时候:A类有车灯、A类有前置栏、B类有车灯....一共要4*2一共八个类。这个时候咱们使用的是<font color="red">建立新子类</font>的手段将全部状况穷举到<font color="red">实体类</font>上面,想要实例化一个对象只要找到对应实体类就行了。

优化:若是自行车的类型增多,配件增多就会出现实体类几何增长,大家应该不会想把本身的余生花在维护实体类上吧,来吧出来吧咱们的<font color="red">装饰者模式</font>。首先回归业务场景:A、B、C、D咱们能够暂时称为<font color="red">组件</font>。而咱们的配件就是<font color="red">装饰者</font>。<font color="red">主要思路</font>:替换建立新子类,每一个装饰者维护单个装饰者类,将<font color="red">组件做为实例传入装饰者类中进行‘装饰’</font>能够重写原有方法、也能够新增方法。 这样咱们只要维护组件类加装饰者类 4+2一个6个类

class Bicycle{
    constructor(){
        this.type = 'A'
        // 自行车售价
        this.price = 5000
    }
    getBicycle(){
        console.log(`A自行车的售价${this.price}`)
    }
}
class BicycleDecorator{
    constructor(bicycle){
        // 传入的组件
        this.bicycle = bicycle
        // 大灯售价
        this.dengPrice = 200
    }
    // 新增方法
    openDeng(){
        console.log('我打开了灯')
    }
    // 重写方法
    getBicycle(){
        console.log(`A自行车的售价${this.bicycle.price + this.dengPrice}`)
    }
}
// 先建立类型自行车
var a = new Bicycle()
// 装饰后替换原有
a = new BicycleDecorator(a)
a.getBicycle() // A自行车的售价5200

总结:<font color="red">装饰者模式</font>为对象增添特性提供了新的思路,去除了建立新的子类对应最小单位实体类,经过传递一个父类来进行对于父类增长新特性的方法是,保留了父类原有方法也具备延展性。真香~~

结束语

本文提供了部分的设计模式以及本身的理解,理解可能存在误差欢迎讨论,本文参考的是《javascript设计模式》。以后若有设计模式的补充还会继续更新。

相关文章
相关标签/搜索