JS 常见的设计模式与使用场景

58 阅读4分钟

设计模式的目的

是为了更好的代码复用、可读、可靠、可维护

单例模式

  • 一个类只有一个实例,并提供一个访问他的全局访问点
  • 一个标准的单例模式是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
  • 代码demo
/**
 * 单例模式
 * @param {*} name 
 */
function Singleton ( name ) {
    this.name = name
}
/**
 * 获取实例
 */
Singleton.getInstance = (() => {
    let instance = null;
    return (name) => {
        if (!instance) {
            instance = new Singleton(name)
        }
        return instance
    }
})()

const a = Singleton.getInstance('张三')
const b = Singleton.getInstance('里斯')
console.log(a === b) // true
  • 使用场景
    • 变动频繁的提示框
    • VueX的全局store就是一个典型应用
  • 优点
    • 节约资源,保证访问的一致性。
  • 缺点
    • 扩展性不友好,因为单例模式一般自行实例化,没有接口。

工厂模式

  • 根据不同的参数,返回不同类的实例
  • 代码demo
/**
 * 工厂模式
 */
class Factory {
    constructor (name) {
        this.name = name;
    }
    getName () {
        return this.name
    }
}

const factory = new Factory('张三')
const factory2 = new Factory('里斯')
console.log(factory.getName(), factory2.getName())
  • 使用场景
    • vue3中的h函数创建虚拟 DOM 节点 (vnode)。
    • vue-Router 源码中的工厂模式
export default class VueRouter{
    constructotr(options){
        const mode = options.mode || "hash";
        switch (mode) {
            case 'history':
                this.history = new HTML5History(this, options.base)
                break
            case 'hash':
                this.history = new HashHistory(this, options.base, this.fallback)
                break
            case 'abstract':
                this.history = new AbstractHistory(this, options.base)
                break
            default:
                if (process.env.NODE_ENV !== 'production') {
                  assert(false, `invalid mode: ${mode}`)
                }
        }
    }
}

  • 优点
    • 良好的封装,访问者无需了解创建过程,代码结构清晰
    • 扩展性良好,通过工厂方法隔离了用户和创建流程,符合开闭原则
    • 解耦了高层逻辑和底层产品类,符合最少知识原则,不需要的就不要去交流
  • 缺点
    • 给系统增加了抽象性,带来了额外的系统复杂度

策略模式

  • 定义好需要的策略,然后根据传入的策略名去执行对应的策略
const memberMap = {
    // 青铜九折
    bronze: (price) => {
        return price * 0.9;
    },
    // 白银七折
    silver: (price) => {
        return price * 0.7;
    },
    // 黄金五折
    gold: (price) => {
        return price * 0.5;
    }
}
  • 优点
    • 策略相互独立,可以互相切换。提高了灵活性以及复用性
    • 扩展性好
  • 缺点
    • 策略相互独立,一些复杂的算法逻辑无法共享,造成资源浪费

观察者模式

  • 观察者模式:一个对象(称为subject被观察者)维持一系列依赖于它的对象(称为observer观察者),将有关状态的任何变更自动通知给它们(观察者)
  • 代码demo
const observerKey = 0;
/**
 * 被观察者
 */
class Subject {
    constructor () {
        this.observers = []
    }
    /**
     * 添加观察者
     * @param {*} observer 
     */
    addObserver (observer) {
        this.observers.push(observer)
    }
    /**
     * 删除观察者
     * @param {*} observer 
     */
    removeObserver (observer) {
        this.observers = this.observers.filter(obs => obs.key !== observer.key)
    }
    /**
     * 通知所有观察者
     */
    notify () {
        this.observers.forEach(obs => {
            obs.update(this)
        })
    }
}
/**
 * 观察者
 */
class Observer {
    constructor () {
        this.key = observerKey++
    }
    /**
     * 更新观察者
     * @param {*} subject 
     */
    update (subject) {

    }
}
  • 使用场景
    • 防疫通知群众接种疫苗
  • 优点
    • 目标变化就会通知观察者,这是观察者模式最大的优点。
  • 缺点
    • 不灵活。目标和观察者是耦合在一起的,要实现观察者模式,必须同时引入被观察者和观察者才能达到响应式的效果

发布-订阅模式

发布-订阅模式其实是1对多的关系,订阅者先把事件订阅到调度中心,然后发布者需要执行哪个事件,就把该事件的名称发到调度中心,调度中心则调用该事件对应的函数。 vue2.0的内部很多实现就是利用这个模式

  • 代码demo
class PublishSubscription {
    constructor () {
        // 调度中心, 记录事件,订阅者
        this.subObj = {}
    }
    /**
     * 订阅者
     * @param {*} type 事件类型
     * @param {*} fn  事件回调函数
     */
    on (type, fn) {
        if (!this.subObj[type]) {
            this.subObj[type] = [fn]
        } else {
            this.subObj[type].push(fn)
        } 
    }
    /**
     * 取消订阅
     * @param {*} type 事件类型
     * @param {*} fn  事件回调函数
     */
    off (type, fn) {
        if (this.subObj[type]) {
            if (fn) {
                const fns = this.subObj[type]
                this.subObj[type] = fns.filter(item => item !== fn)
            }
        } else {
           delete this.subObj[type]
        }
    }
    /**
     * 发布事件
     * @param {*} type 事件类型
     * @param {*} data 数据
     */
    emit (type, data) {
        if (!this.subObj[type]) return;
        this.subObj[type].forEach(fn => {
            fn(data)
        });
    }
}
// 测试
const publishSubscription = new PublishSubscription()
const fn1 = () => {
    console.log('f1')
}
const fn2 = () => {
    console.log('f2')
}
const type = 'aa'
publishSubscription.on(type, fn1) 
publishSubscription.on(type, fn2)
publishSubscription.emit(type)
publishSubscription.off(type, fn1)
publishSubscription.emit(type)
  • 使用场景
    • 我们微信会关注很多公众号,公众号有新文章发布时,就会有消息及时通知我们文章更新了
    • Vue 双向绑定中的发布订阅模式
  • 优点
    • 时间解耦:注册的订阅行为由发布者决定何时调用,订阅者无需持续关注,由发布者负责通知。
      1. 对象解耦:发布者无需知道消息的接受者,只需遍历订阅该消息类型的订阅者发送消息,解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象。
  • 缺点
    • 资源消耗:创建订阅者需要一定的时间和内存。
    • 增加复杂度:弱化了联系,难以维护调用关系,增加了理解成本。