前端设计模式应用 | 青训营笔记

113 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

设计模式———在软件开发过程中,针对特定问题简洁而优雅的解决方案!

背景模式语言:城镇、建筑、建造;设计模式:可复用面向对象软件的基础。
本质:对类的封装性、继承性、多态性、关联关系、组合关系的面向对象设计原则的总结应用。
运用:浏览器,JavaScript,前端框架中很多地方都有设计模式的运用。

23种分类:创造型、结构型、行为型
创造型:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、单例模式
结构型:适配者模式、桥模式、组合模式、装饰器模式、外观模式、享元模式、代理模式
行为型:职责链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、访问者模式、策略模式、状态模式、模板方法模式、解释器模式

原则:SOLID

  • [S - Single-responsiblity Principle]单一职责原则
  • [O - Open-closed Principle]开放闭合原则,如Redux、Koa、Draft.js 库、插件、框架等
  • [L - Liskov Substitution Principle]里氏置换原则,继承复用的基础,派生类可替换基类、基类功能不受破坏,派生类可增加新的行为
  • [I - Interface Segregation Principle]接口独立原则,减少耦合
  • [D - Dependency Inversion Principle]依赖倒置原则,高层次模块不依赖低层次模块 其他原则:
  • [Composite Reuse Principle]组合复用原则,模块相互独立
  • [Demeter Principle]迪米特原则,如函数式编程
1. 单例模式
  • 定义:一个类一个实例,并提供访问它的全局访问点
  • 场景:dialog弹窗、Window、请求缓存
const getSingle = (fn) => {
    let result;
    return (...args) => {
        return result || result = fn.apply(this, args);
    }
}
let createDialogLayer = () => {
    let div = document.createElement('div');
    div.style.display = 'none';
    document.body.appendChild('div');
    return div;
} 
let singleDialog = getSingle(createDialogLayer);
2. 策略模式
  • 定义:定义一系列算法,把它们封装成策略类,算法被封装在策略内部的方法实现里,将算法的使用和算法的实现分离开来。
  • 场景:绩效考核、表单验证
// 场景:根据超过10种不同的类型,有不同的详情数据,不同的表单UI进行验证,同时进行新增、编辑操作(以下例子只列举了五种,实际代码更长)
// index.vue 代码1
// 校验后新增、编辑
save() {
    if (type === '1') {
      let data = this.$refs[type].submit();
      if (!data) return;
      this.config1 = data;
    } else if (type === '2') {
      let data = this.$refs[type].submit();
      if (!data) return;
      this.config2 = data;
    } else if (type === '3') {
      let data = this.$refs[type].submit();
      if (!data) return;
      this.config3 = data;
    } else if (type === '4') {
      let data = this.$refs[type].submit();
      if (!data) return;
      this.config4 = data;
    } else if (type === '5') {
      ...
    }
    if (action === 'add') {
        if (type === '1') {
            this.addData = { id, name, config: this.config1 };
        } else if (type === '2') {
            this.addData = { id, name, config: this.config2 };
        } else if (type === '3') {
            this.addData = { id, name, config: this.config3 };
        } else if (type === '4') {
            this.addData = { id, name, config: this.config4 };
        } else if (type === '5') {
            ...
        }
    } else if (action === 'edit') {
        if (type === '1') {
            this.editData1 = { id, name, config: this.config1 };
        } else if (type === '2') {
            this.editData2 = { id, name, config: this.config2 };
        } else if (type === '3') {
            this.editData3 = { id, name, config: this.config3 };
        } else if (type === '4') {
            this.editData4 = { id, name, config: this.config4 };
        } else if (type === '5') {
            ...
        }  
    }
}
detail() {
    // 接口返回数据res.config;
    if (type === '1') {
        this.detailData1 = res.config;
    } else if (type === '2') {
        this.detailData2 = res.config;
    } else if (type === '3') {
        this.detailData3 = res.config;
    } else if (type === '4' {
        this.detailData4 = res.config;
    } else if (type === '5') {
        ...
    } 
}
// 使用策略模式
const strategies = {
    [type]: {
        handleAddData(formData) {},
        handleEditData(formData) {},
        handleDetailData(detailData) {},
        handleValidateData(formData) {},
        // 不同类型可以追加不同的方法
        ...
    }
}
save({ type, formData }) {
    const strategy  = strategies[type];
    this.config = strategy.handleValidateData(formData);
    if (!this.config) return;
    if (action === 'add') {
        this.addData = this.handleAddData(formData);
    } else {
        this.editData = this.handleEditData(formData);
    }
}
detail({ type, detailData }) {
    const strategy  = strategies[type];
    this.detailData = this.handleDetailData(detailData);
}

3. 代理模式
  • 定义:可自定义控制对原始对象的访问方式,允许监听属性更新前后做一些事情
  • 场景:监控、代理工具、框架如ES6的代理模式 | Proxy
const proxy = new Proxy(target, handle);
const origin = {}
const obj = new Proxy(origin, {
  get: function (target, propKey, receiver) {
		return '10'
  }
});
4. 发布订阅模式
  • 定义:订阅对象发生变化通知订阅者
  • 场景:DOM事件、消息通知、邮件订阅、上线订阅
type Notify = (user: User) => void
class User {
    name: String;
    status: 'offline' | 'online';
    followers: { user: User; notify: Notify }[]
    constructor(name: string) {
        this.name = name;
        this.status = 'offline';
        this.followers = [];
    }
    subscribe(user: User; notify: Notify) {
       user.followers.push({ user, notify });
    }
    online() {
       this.status = 'online';
       this.followers.forEach(({ notify }) => {
          notify(this);
       }) 
    }
}
const user1 = new User('user1');
const user2 = new User('user2');
const notifyUser1 = () => {
    console.log('user2 online');
}
user1.subscribe(user2, notifyUser1);
user2.online();

5. 命令模式
  • 定义:执行某些特定事情的指令,解决请求发送者和接收着耦合问题
  • 场景:IOT控制设备、游戏键盘按键执行动作
let gamePlayer = {
    jump: function(){},
    attack: function(){},
    defense: function(){}
}
// 创建命令
let makeCommand = (receiver, state) => {
  return () => receiver[state]
}
// 键盘码命令
const commands = {
    119: jump,
    115: attack,
    97: defense
}
// 保存命令堆栈
let commandStack = [];
document.onkeypress = function(e) {
    let command = makeCommand(gamePlayer, commands[e.keyCode]);
    if (command) {
        command();
        commandStack.push(command);
    }
}
document.getElementById('play').onclick = function() {
    let command;
    while(command = commandStack.shift()) {
        command();
    }
}
6. 装饰器模式
  • 定义:动态增加职责,即用即付
  • 场景:数据上报、插件式表单验证
// validate和formSubmit未分离
let formSubmit = function() {
    if (!validate()) return;
    ajax('http://xxx/login', { user, password });
}
// 改写
Function.prototype.before = function(beforeFn) {
  let _self = this;
  return function() {
      if (!beforeFn.apply(this, arguments)) return;
      return _self.apply(this, arguments);
  }
  let validate = function() {
      return user && password;
  }
  let formSubmit = function() {
    ajax('http://xxx/login', { user, password });
  }
  formSubmit = formSubmit.before(validate);
  formSubmit();
}
7. 组合模式
  • 定义:可多个对象组合也可单个对象独立使用
  • 场景DOMjQuery、前端组件、文件目录、部门
// https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch12s01.html
// Single elements 单个独立使用
$( "#singleItem" ).addClass( "active" ); 
$( "#container" ).addClass( "active" ); 
// Collections of elements // 多个组合使用
$( "div" ).addClass( "active" ); 
$( ".item" ).addClass( "active" ); 
$( "input" ).addClass( "active" );
const jQuery = {
    addClass: function( value ) {
    var classNames, i, l, elem,
      setClass, c, cl;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).addClass( value.call(this, j, this.className) );
      });
    }
    if ( value && typeof value === "string" ) {
      classNames = value.split( rspace );
      for ( i = 0, l = this.length; i < l; i++ ) {
        elem = this[ i ];
        if ( elem.nodeType === 1 ) {
          if ( !elem.className && classNames.length === 1 ) {
            elem.className = value;
          } else {
            setClass = " " + elem.className + " ";
            for ( c = 0, cl = classNames.length; c < cl; c++ ) {
              if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
                setClass += classNames[ c ] + " ";
              }
            }
            elem.className = jQuery.trim( setClass );
          }
        }
      }
    }

    return this;
  }
}
8. 适配器模式
  • 定义:解决两个接口不兼容的问题,不需要改变现有的接口,就能够使他们协同作用
  • 场景:接口不兼容问题、SDK
// 参考天气库适配案例
// [Adapter Pattern 介紹及 JavaScript 實作](https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/adapter-pattern-%E4%BB%8B%E7%B4%B9%E5%8F%8A-javascript-%E5%AF%A6%E4%BD%9C-c4f168f1cd26)
参考链接

s-o-l-i-d-the-first-5-priciples-of-object-oriented-design-with-javascript
betterprogramming-javascript-design-patterns
codesource-javascript-design-patterns
hackernoon-solid-principles-in-javascript
dofactory-design-patterns
一些软件设计的原则
JavaScript-Module-Pattern-In-Depth

书籍

addyosmani-essentialjsdesignpatterns
JavaScript设计模式与开发实战