常见的设计模式及其JS实现(单例模式、观察者模式、发布者-订阅者、工厂等)

前言

设计模式是开发中很重要的内容,我更愿意把他理解为一种指导思想。按照这种指导思想去解决问题,代码可以更低的耦合更高的内聚并且更健壮和逻辑清晰。

单例模式

一个类只有一个实例,并提供一个访问它的全局访问点。单例模式顾名思义保证一个类仅有一个实例,并且提供一个访问它的全局访问点。单例的好处是可以减少不必要的开销例如页面需要展示一个弹框,那么这个弹框只在首次的时候会进行创建,之后在进行点击的时候使用的都是之前创建好的。例如登录框单例,vue中的store,router

单例模式实现

核心思想为:createDiv方法只负责创建div,扩展方法交给原型链上的init。创建单例时使用立即执行函数和闭包进行封装

var CreateDiv = function(html) {
    this.html = html
    this.init()
}

CreateDiv.prototype.init = function() {
    var div = document.createElement('div')
    div.innerHTML = this.html
    document.appendChild(div)
}
var ProxysingletonCreateDiv = (function() {
    var instance
    return function(html) {
        if (!instance) {
            instance = new CreateDiv(html)
        }
        return instance
    }
})()
var a = new ProxysingletonCreateDiv('test1')
var b = new ProxysingletonCreateDiv('test2')

alert(a === b) // true

适用于js的惰性单例

控制只有一个对象的操作抽象出来是个什么样子:

var obj 
if (!obj) {
    obj = xxx
}

于是就可以把这个操作的逻辑封装到一个getSingle函数中,然后把要执行的函数当作参数传入进去:

var getSingle = function(fn) {
    var result
    return function() {
        result || (result = fn.apply(this, arguments))
    }
}
var createLoginLayer = function() {
    var div = document.createElement('div')
    div.innerHTML = '我是登录弹窗'
    div.style.display = 'none'
    document.appendChild(div)
    return div
}

var createSingleLoginLayer = getsingle(createLoginLayer)

document.getElementById('loginBtn').onclick = function() {
    var loginLayer = createSingleLoginLayer()
    loginLayer.style.display = 'block'
}

观察者模式

定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己,当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。缺点过度使用会导致对象与对象之间的联系弱化,会导致程序难以跟踪维护和理解。

场景:promise中 then函数中的回调就订阅了resolve函数和reject函数;vue中生命周期钩子

我们解读一下代码:

  • 首先,主题对象 需要有状态(值)的get、set方法观察者的绑定解绑方法通知观察者的方法
  • get 方法直接返回状态即可,set方法设置状态后,调用通知方法
  • attach方法绑定观察者,其实就是加入数组;unattach找到对应的观察者移除即可
  • notifyAllObservers方法就是遍历数组调用update方法
  • 对于 观察者来说 具有自己的属性,要绑定哪个主题,以及update方法
  • new 观察者时,设置自己的属性,然后根据传入的主题进行好绑定
  • 接到通知后,调用update方法,再使用提供的get方法获取状态改变的值
  • 对于解绑来说,调用主体不同写法不同
// 主题 保存状态,状态变化之后触发所有观察者对象
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  attach(observer) {
    this.observers.push(observer)
  }
  unattach(observer) {
    this.observers.splice(this.observers.indexOf(observer),1)
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }

}

// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}

// 测试
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
s.setState(12)
// 两种方法都可以解除绑定
// s.unattach(o1) //以主题为主体
o1.subject.unattach(this) // 以观察者为主体
s.setState(13)

发布者-订阅者模式

其实仔细看看,EventEmitter就是一个典型的发布订阅模式,实现了事件调度中心。发布订阅模式中,包含发布者,事件调度中心,订阅者三个角色。我们刚刚实现的EventEmitter的一个实例ee就是一个事件调度中心,发布者和订阅者是松散耦合的,互不关心对方是否存在,他们关注的是事件本身。发布者借用事件调度中心提供的emit方法发布事件,而订阅者则通过on进行订阅。

下面解读一下代码:

  • 事件调度中心需要实现:维护订阅者,订阅者注册方法on,事件发布方法emit,移除某个订阅者或全部订阅者的方法off
  • on方法:如果没有该类事件,我们先创建一个数组(维护cb),然后再将回调加入
  • emit方法:发布事件,如果有人订阅该事件,则通知所有人执行回调。如果没有发布也就没人响应
  • off方法:在移除订阅者时,如果该事件有人订阅,则找到对应的人进行移除,移除后如果订阅者为空,就删除这个事件维护的cb数组
  • offAll方法:如果该事件有订阅者,直接移除事件维护的cb数组即可
class EventEmitter {
    constructor() {
        // 维护事件及监听者
        this.listeners = {}
    }
    /**
     * 注册事件监听者
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    on(type, cb) {
        if (!this.listeners[type]) {
            this.listeners[type] = []
        }
        this.listeners[type].push(cb)
    }
    /**
     * 发布事件
     * @param {String} type 事件类型
     * @param  {...any} args 参数列表,把emit传递的参数赋给回调函数
     */
    emit(type, ...args) {
        if (this.listeners[type]) {
            this.listeners[type].forEach(cb => {
                cb(...args)
            })
        }
    }
    /**
     * 移除某个事件的一个监听者
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    off(type, cb) {
        if (this.listeners[type]) {
            const targetIndex = this.listeners[type].findIndex(item => item === cb)
            if (targetIndex !== -1) {
                this.listeners[type].splice(targetIndex, 1)
            }
            if (this.listeners[type].length === 0) {
                delete this.listeners[type]
            }
        }
    }
    /**
     * 移除某个事件的所有监听者
     * @param {String} type 事件类型
     */
    offAll(type) {
        if (this.listeners[type]) {
            delete this.listeners[type]
        }
    }
}
// 创建事件管理器实例
const ee = new EventEmitter()
// 注册一个chifan事件监听者
ee.on('chifan', function() { console.log('吃饭了,我们走!') })
// 发布事件chifan
ee.emit('chifan')
// 也可以emit传递参数
ee.on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}${food}`) })
ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者

// 测试移除事件监听
const toBeRemovedListener = function() { console.log('我是一个可以被移除的监听者') }
ee.on('testoff', toBeRemovedListener)
ee.emit('testoff')
ee.off('testoff', toBeRemovedListener)
ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了

// 测试移除chifan的所有事件监听
ee.offAll('chifan')
console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了

工厂模式

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。例如我们axios中使用create创建就类似;曾经我们熟悉的JQuery的$()就是一个工厂函数,它根据传入参数的不同创建元素或者去寻找上下文中的元素

场景:将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式。

缺点:添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度。考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度

class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fun() {
        console.log('fun')
    }
}

class Factory {
    create(name) {
        return new Product(name)
    }
}

// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值