浅谈设计模式-JS

前阵子看了《JavaScript设计模式与开发实践》,非常不错的一本书,整理一些最常用的设计模式,以后再补充一些其它的。

单例模式

保证一个类仅有一个实例,并且提供一个访问它的全局访问点。

实现单例模式

  1. 利用构造函数的实例属性(不透明)

    let CreateDiv = function (name) {
        this.name = name
        this.instance = null
    }
    CreateDiv.prototype.init = function () {
        let div = document.createElement( 'div' )
        // ....
    }
    CreateDiv.creatInstance = function (name) {
        if (!this.instance) {
            this.instance = new CreateDiv(name)
        }
        return this.instance
    }
    let lee1 = CreateDiv.creatInstance('lee1')
    let lee2 = CreateDiv.creatInstance('lee2')
    
    lee1 === lee2   // true
    
  2. 利用闭包 (不透明)

    let CreateDiv = function (name) {
        this.name = name
    }
    CreateDiv.prototype.init = function () {
        let div = document.createElement( 'div' )
        // ....
    }
    CreateDiv.creatInstance = (function () { // 立即执行函数
        let instance = null
        return function (name) {
            if (!instance) {
                instance = new CreateDiv(name)
            }
            return instance
        }
    })()
    let lee1 = CreateDiv.creatInstance('lee1')
    let lee2 = CreateDiv.creatInstance('lee2')
    
    lee1 === lee2   // true
    
  3. 单例类(透明)

    上面两种实现方式不透明,需要研究代码的实现才知道调creatInstance方法,这样不太好啊,我门用普通类来实现

    let Singleton = (function () {
        let instance = null
        let CreateDiv = function (name) {
            if (instance) return instance
            this.name = name
            this.init() // 创建一个div
            return instance = this // this指向新创建的实例对象
        }
        CreateDiv.prototype.init = function () {
            let div = document.createElement( 'div' )
            // ....
        }
        return CreateDiv
    })()
    let lee1 = new Singleton('lee1')
    let lee2 = new Singleton('lee2')
    
    lee1 === lee2   // true
    
  4. 代理类(代理模式 - 透明)

    // 有一我们要创建100个div怎么办 恐怕只能修改CreateDiv了吧
    // 代理类只负责单例模式,不影响原来的类,而且也很透明
    let CreateDiv = function (name) {
        this.name = name
        this.init()
    }
    CreateDiv.prototype.init = function () {
        let div = document.createElement( 'div' )
        // ....
    }
    <!--代理类 负责单例模式 不影响-->
    let ProxyCreatInstance = (function () {
        let instance = null
        return function (name) {
            if (!instance) {
                instance = new CreateDiv(name)
            }
            return instance   // 返回了实例
        }
    })()
    let lee1 = new ProxyCreatInstance('lee1') // 直接new 很透明,创建多个还用CreateDiv类
    let lee2 = new ProxyCreatInstance('lee2')
    
    lee1 === lee2   // true
    // 这里涉及到了new操作符原理,构造函数如果返回了引用类型,会覆盖掉产生的对象
    function mockNew (func, params) {
      let obj = {}
      obj.__proto__ = func.prototype
      let res = func.call(obj, params)
      return res instanceof Object ? res : obj
    }
    
  5. js惰性单例(上面的都混入了传统面向对象语言的特点,没有利用js的特点)

    // js嘛 函数是一等公民, 先写个通用的单例函数
    function getSingle (fn) {
        let instance = null
        return function () {
            return instance || (instance = fn.call(this, arguments)) // fn有返回 这样写更简洁
        }
    }
    let CreateDiv = function (name) {
        this.name = name
        let div = document.createElement( 'div' )
        div.setAttribute('id', name[0])
        return div
    }
    
    let singleton = getSingle(CreateDiv)
    
    let lee1 = singleton('lee1')
    let lee2 = singleton('lee2')
    
    lee1 === lee2   // true
    
    button.addEventListener('click', function () {
        let lee = singleton('lee')
    } , false);
    <!--惰性单例就是指只在需要的时候才创建 比如我点击按钮后创建-->
    
    

小结:不透明模式需要了解代码,不够友好,基于传统的面向对象,我们可以通过代理类来实现单例模式的透明化,创建对象和管理单例的职责被分布在两个不同的方法中。惰性单例技术就是指在合适的时候才创建对象。

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。当我们不方便直接访问对象本体的时候,通过代理去访问它。

image

比如我们上面单例模式中的代理类ProxyCreatInstance,我们如果不用代理,就像第三种单例类一样,代码耦合在一起,假如我们想创建100个div,只能去更改CreateDiv的内部,因为你new 100单例模式下个它也只有一个。用了代理模式后,当你需要用单例的时候走代理类就ok了,不影响原来的类。

小王要给小芳送花,他不善言辞,送花失败率高,他让小芳的朋友小明替他去送,小明挑了一个小芳开心的日子去送花,一下车替小王解决了终身大事。。。小明这个代理可以监听小芳的心情,在她心情好的时候再去送,这就是代理模式的作用。

function Flower () {}

let xiaoWang = {
    sendFlower: function (target) {
        let flower = new Flower()
        target.receiveFlower(flower)
    }
}
let xiaoMing =  {
    receiveFlower: function (flower) {
        xiaoFang.listenGoodMood(function () {
            xiaoFang.receiveFlower(flower)   
        }
    })
}
let xiaoFang =  {
    receiveFlower: function (flower) {
        console.log('收到花')
    },
    listenGoodMood: function (fn) {
        setTimeout(fn, 10000)
    }
}

xiaoWang.sendFlower(xiaoMing)

策略模式

定义一系列的算法,将不变的部分和变化的部分隔开,实际就是将算法的使用和实现分离出来,算法的使用方式是不变的

举个栗子

<!--年终核算绩效-->
A级 奖金为工资*3
B级 奖金为工资*2
C级 奖金为工资*1
D级 奖金为0

function calculateBonus ( performanceLevel, salary ){
    if ( performanceLevel === 'A' ){
        return salary * 3;
    }
    if ( performanceLevel === 'B' ){
        return salary * 2;
    }
    if ( performanceLevel === 'C' ){
        return salary * 1;
    }
};
calculateBonus( 'A', 10000 );
calculateBonus( 'B', 5000 );

---------------------------------------------
<!--这是我们的策略-->
const strategies = {
    'A': function (salary) {
        return salary * 3;
    },
    'B': function (salary) {
        return salary * 2
    },
    'C': function (salary) {
        return salary * 1
    },
    'D': function (salary) {
        return salary * 0
    }
}
<!--使用方法是不变的-->
const calculateBonus = function (performanceLevel, salary) {
    return strategies[performanceLevel](salary)
}

calculateBonus( 'A', 10000 );
calculateBonus( 'B', 5000 );

发布订阅模式

它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。例如你订阅了一些情报,情报中心刚才发布了一些新消息,如果里面有你订阅的情报时,就会通知到你。发布订阅的好处是发布和订阅是完全解耦的,它们通过消息类型关联。

像js中的addEventListener、vue中的bus通信都是用的发布订阅模式。

button.addEventListener("click", function () {}, false);
// html
    <button onclick="handleOn('m1')">订阅-放学啦</button>
    <button onclick="handleOn('m2')">订阅-老师来了</button>
    <button onclick="handleOn('m3')">订阅-打一把王者</button>

    <button onclick="handleSub('m1')">发布-放学啦</button>
    <button onclick="handleSub('m2')">发布-老师来了</button>
    <button onclick="handleSub('m3')">发布-打一把王者</button>
// js
  /**
   * 发布订阅模式 发布订阅互不影响,通过消息类型关联
   */
  var Observe = function () {
    // 定义消息队列
    this._msgQueue = {}
  }
  Observe.prototype = {
    // 消息订阅
    subscribe: function (type, fn, msg) {
      if (this._msgQueue[type] === void (0)) {
        this._msgQueue[type] = [fn]
      } else {
        this._msgQueue[type].push(fn)
      }
      alert(msg)
    },
    // 发布
    publish: function (type, args) {
      console.log(this._msgQueue[type])
      if (!this._msgQueue[type]) return
      let params = {
        type: type,
        args: args || {}
      }
      let i = 0, len = this._msgQueue[type].length;
      for (; i < len; i++) {
        this._msgQueue[type][i].call(this, params)
      }
    },
    // 移除消息订阅
    off: function (type, fn) {
      if (this._msgQueue[type] instanceof Array) {
        let i = 0, len = this._msgQueue[type].length;
        for (; i < len; i++) {
          if (this._msgQueue[type][i] === fn) {
            this._msgQueue[type].splice(i, 1)
            return
          }
        }
      }
    }
  }
  // -----------------------------------
  var Ming = new Observe()
  function handleOn(type) {
    console.log('type', type);
    switch (type) {
      case 'm1':
        console.log('m1');
        let fun1 = function (params) {
          alert(params.args)
        }
        Ming.subscribe(type, fun1, '放学了-订阅成功')
        break;
      case 'm2':
        let fun2 = function (params) {
          alert(params.args)
        }
        Ming.subscribe(type, fun2, '老师来了-订阅成功')
        break;
      case 'm3':
        let fun3 = function (params) {
          alert(params.args)
        }
        Ming.subscribe(type, fun3, '打一把王者-订阅成功')
        break;
    }
  }
  function handleSub(type) {
    switch (type) {
      case 'm1':
        Ming.publish(type, '放学了,快回家找妈妈')
        break;
      case 'm2':
        let fun2 = function (params) {
          alert(params.args)
        }
        Ming.publish(type, '老师来了-快把手机藏起来')
        break;
      case 'm3':
        let fun3 = function (params) {
          alert(params.args)
        }
        Ming.publish(type, '同学们请注意,这堂课我们一起玩王者农药')
        break;
    }
  }

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可

image

缺点:系统中会新增一个中介者对象,增加了对象之间交互的复杂性,中介者对象经常是巨大的,有时候难以维护。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355