实际项目中的设计模式总结

349 阅读4分钟

单例模式(singleton)

保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

使用场景

  1. 弹窗:无论点击多少次页面上的按钮,都只创建一个弹窗
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>单例模式(singleton)</title>
</head>
<body>
<button id="btn">按钮一</button>
<script>
  function createDialog (text) {
    let dialog = document.createElement('div');
    dialog.innerHTML = text;
    document.body.appendChild(dialog);
    return dialog;
  }

  function createSingleton (fn) {
    let singleton; // 这行代码只有在第一次被调用时,会执行,因为函数 return 了
    return function () {
      return singleton || (singleton = fn.apply(this, arguments)) // singleton 就是 createDialog 返回的 dialog
    }
  }

  let createSingletonDialog = createSingleton(createDialog)

  document.querySelector('#btn').onclick = function () {
    createSingletonDialog('弹窗文本')
  }
</script>
</body>
</html>

订阅-发布模式

基于一个Event Channel(事件调度中心) ,希望接收通知的对象 Subscriber(订阅者) 通过自定义事件订阅主题,被激活事件的对象 Publisher(发布者) 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。

使用场景

  1. 事件总线 EventBus

实际项目场景:修改购物车商品数量功能

  • 事件调度中心:EventBus
  • 订阅者:各个页面,通过 EventBus 的 on 方法,订阅了 'updateCount' 方法,每个页面都自定义了收到购物车数量更新通知后的处理事件
  • 发布者:购物车修改数量,通过 EventBus 的 emit 方法,发布 'updateCount'
// bridge.js 
var bridge = {};

/**
 * 添加事件队列
 * @param {String} key 事件名
 * @param {Function} func 事件处理函数
 */
function on(key, func) {
  if(!bridge[key]){
    // 事件名不存在(未添加),初始化事件队列数组,并塞入第一个事件
    bridge[key] = [func];
  }else{
    // 事件名已存在(已添加),push 新的事件进事件队列数组
    bridge[key].push(func);
  }
 }

/**
 * 触发事件
 * @param {String} key 事件名 
 * @param {*} params 事件处理函数接收的参数
 */
function emit(key, params) {
  // 事件名不存在,不处理
  if(!bridge[key]) return;
  // 遍历事件名对应的事件队列数组,依次执行所有事件处理函数
  for(let v of bridge[key]){
    v(params)
  };
}

/**
 * 移除事件名
 * @param {String} key 事件名
 */
function remove(key) {
  bridge[key] && delete bridge[key];
}

module.exports = {
  on,
  emit,
  remove
}

页面中使用(比如,在项目中的任意页面操作购物车相关数据):

let bridge = require('../../utils/bridge.js');
// 注册事件
bridge.on('modifyShoppingCart', data => {
})
// 触发事件
bridge.emit('modifyShoppingCart', data => {
})
// 移除事件
bridge.remove('modifyShoppingCart', data => {
})

观察者模式

当对象之间存在一对多的依赖关系时,当这个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。 观察者模式只有两个主体:

  1. 目标对象(Subject)
  2. 观察者(Observer)
// 定义目标对象
class Subject {
    constructor () {
        // 声明观察者列表
        this.observerList = []
    }

    // 添加观察者
    addObserver (observer) {
        this.observerList.push(observer)
    }

    // 通知观察者更新
    notify (task) {
        this.observerList.forEach(item => item.update(task))
    }
}

// 定义观察者类
class Observer {
    constructor (name, handler) {
        this.name = name
        this.handler = handler
    }

    update({ taskName, taskHandler }) {
        // 可以根据 taskName 做一些通用处理
        this.handler && this.handler()
    }
}

let subject = new Subject()
let observer1 = new Observer('obs1', function () {
    // 观察者1,收到通知后,执行自己的自定义行为
    console.log(`${this.name} 去执行自己的任务啦`)
})
let observer2 = new Observer('obs2', function () {
    // 观察者2,收到通知后,执行自己的自定义行为
    console.log(`${this.name} 啥都不想干`)
})
subject.addObserver(observer1)
subject.addObserver(observer2)

subject.notify({ taskName: '任务1'})

控制台输出:

image.png

观察者模式 与 订阅-发布模式 的对比

表格来源于: juejin.cn/post/705544…

设计模式观察者模式发布订阅模式
主体Observer观察者、Subject目标对象Publisher发布者、Event Channel事件中心、Subscriber订阅者
主体关系Subject中通过observerList记录ObServerPublisher和Subscribe不想不知道对方,通过中介联系
优点角色明确,Subject和Observer要遵循约定的成员方法松散耦合,灵活度高,通常应用在异步编程中
缺点紧耦合当事件类型变多时,会增加维护成本
使用案例双向数据绑定事件总线EventBus

策略模式(Strategy)

将定义的一组算法封装起来,使其相互之间可以替换。封装的算法具有一定的独立性,不会随客户端变化而变化。

使用场景

  1. 表单验证
const FormStrategy = function () {
    // 将算法封装在一个对象里
    const strategy = {
        // 是否为空
        isNull: function (value) {
            return /\s+/.test(value) ? '请输入内容' : '';
        },
        // 是否是一个数字
        isNumber: function (value) {
            return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '' : '请输入数字';
        },
        // 是否是手机号码
        isPhone: function (value) {
            return /^1[3456789]\d{9}$/.test(value) ? '' : '请输入正确的手机号';
        }
        // 是否是邮箱
        isEmail: function (value) {
            return /^\s+@\s+$/.test(value) ? '' : '请输入正确的邮箱';
        }
    }
    // 只对外暴露调用接口
    return {
        check: function (type, value) {
            // 去除首尾空白符
            value = value.replace(/^\s+|\s+$/g, '');
            return strategy[type] ? strategy[type](value) : '没有该类型的检测方法';
        },
        // 添加策略
        addStrategy: function (type, fn) {
            strategy[type] = fn;
        }
    }
}

使用:

let username = document.querySelector('.username').value;
let message = FormStrategy.check('isNull', username);
message ? window.alert(message) : '';

参考文章/书籍

  1. 《JavaScript 设计模式》

  2. juejin.cn/post/705544…