浅谈前端设计模式2:前端常用设计模式(工厂、单例、适配器、装饰器、代理、外观)

314 阅读6分钟

23种设计模式不是所有都适合前端的,因为前端语言的限制只有部分设计模式在在前端使用软多。

23种设计模式

  • 创建型 工厂模式(工厂方法模式、抽象工厂模式、建造者模式)、单例模式、原型模式

  • 组合型 适配器、装饰器、代理、外观、桥接、组合、享元

  • 行为型 策略、模板方法、观察者、迭代器、职责链、命令、备忘录、状态、访问者、中介者、解释器


前端常用设计模式

1、工厂模式

工厂模式:将new操作进行单独封装;遇到new时,就可以考虑用工厂模式。

(创建对象过程中)把对象的具体实现或接口与用户使用分开,用户只用调一下工厂函数的creact()就能生成该对象,不用使用new,不用处理相关的具体的细节。

UML类图

image.png

经典使用场景及代码演示

  • jQuery中的$('div')
  • React.createElement
  • Vue异步组件

1、jQuery中的$('div')

image.png

最后三行就是经典的工厂模式。

2、React.createElement

react常规语法 image.png

底层实际是使用的React.createElement创建了一个虚拟DOM节点 image.png

底层源码示意。也是new了一个虚拟DOM节点的实例返回出来。这块就是工厂模式。 image.png

3、Vue异步组件

image.png

设计原则验证

构造函数和创建者分离。符合开放封闭原则。

2、单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式。

数据的共享,在程序的所有地方该对象都只有一个实例。通过一个函数来判断是否已创建该对象实例,是则反回实例,没有才创建对象实例。通过调用这个函数来创建这个对象的实例。

UML类图

传统UML类图(强类型语言)

image.png

如上UML图:前面是-的属性是private。前端是+的属性是public。

  • 单例模式需要用到强类型语言面向对象中的private特性。
  • ES6中没有private(typescript除外),可以使用闭包来实现类似功能。

代码演示

java代码使用单例模式(使用private实现)

image.png

如上图,只能在类的内部new实例,而且只能new一次。

image.png

如上图,不能使用new再去创建实例。

js中使用单例模式(Es6中class静态方法版本)

class SingleObject {
    login() {
        console.log('login...')
    }
    static getInstance() { 
        // 判断是否已经new过1个实例 
        if (!SingleObject.instance) { 
            // 若这个唯一的实例不存在,那么先创建它 
            SingleObject.instance = new SingleObject() 
        } 
        // 如果这个唯一的实例已经存在,则直接返回 
        return SingleObject.instance 
    }
}

const s1 = SingleObject.getInstance() 
const s2 = SingleObject.getInstance() 

s1 === s2 // true 

js中使用单例模式(闭包版本)

class SingleObject {
    login() {
        console.log('login...')
    }
}
// 定义一个静态方法,使用闭包每次调用返回的都是同一个实例
SingleObject.getInstance = (function () {
    let instance
    return function () {
        if (!instance) {
            instance = new SingleObject()
        }
        return instance
    }
})()

// 注意:这里只能使用静态方法getInstance,不能new SingleObject()
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()

console.log('obj1 === obj2', obj1 === obj2) // true

--------------特别说明--------------
// js中使用new初始化一个实例也是能使用的,但是这时的实例已经是一个新实例了。并不符合单例模式了。

let obj3 = new SingleObject()
obj3.login()
console.log('obj1 === obj3', obj1 === obj3) // false
  • 在一个静态方法中使用闭包来实现返回唯一的实例。
  • js只能使用文档的形式来说明,使用错误也不会报错,无法完全实现单例模式。

场景

  1. jQuery,虽然不是严格的单例模式,但是也是单例模式的思想

image.png

  1. 模拟登录框

image.png

  1. 其它
  • 购物车(和登录框类似)
  • vuex和redux中的store

设计原则验证

  • 符合单一职责原则,只实例化唯一的对象。
  • 没法具体体现开放封闭原则,但是绝对不违反开放封闭原则。

3、适配器模式

旧接口格式和使用者不兼容,中间加一个适配器转换接口。

UML类图

传统UML类图

image.png

简化后的UML类图

image.png

使用场景

  1. 旧接口的封装

假如某个老项目中之前使用的都是jQuery中的ajax。而现在想要使用一个新ajax。如果批量赵替换风险很大。

image.png

这个时间就可以使用适配器模式,对新ajax进行一层封装,以适配之前的jQuery的写法。

image.png

  1. axios使用适配器同时兼容浏览器和node环境

axios在浏览器模式下使用的是ajax发送网络请求。在node环境下使用的是http模块发送网络请求。这也是使用的适配器模式在不同环境下进行适配。

设计原则验证

将旧接口和使用进行分离,符合开放封闭原则。


4、装饰器模式

为对象添加新功能,不改变其原有的结构和功能。

UML类图

传统UML类图

image.png

简化后的UML类图

image.png

使用场景(ES7装饰器)

1. 装饰类

装饰器原理

@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A

装饰类demo

/*
* 一个见简单的demo
*/
function testDec(target) {
    target.isDec = true
}

@testDec
class Demo {
    // code...
}

alert(Demo.isDec) // true


/*
* 带参数的装饰器
*/
function testDec(isDec) {
    return function(target) {
        target.isDec = isDec
    }
}
@testDec(true)
class Demo {
    // code...
}

alert(Demo.isDec) // true

混入示例

function mixins(...list) {
    return function(target) {
        Object.assign(target.prototype, ...list)
    }
}

const Foo = {
    foo() {
        alter('foo')
    }
}

@mixins(Foo) 
class MyClass {

}

let obj = new MyClass()
obj.foo()

2. 装饰方法

  1. 属性只读

image.png

image.png

  1. 给方法添加日志输出功能

image.png

image.png

3. 装饰器库core-decorators的使用

安装

npm i core-decorators --save

使用只读装饰器

image.png

废弃提示装饰器(适用于对外开放的类库,提高专业性)

image.png

设计原则验证

将现有对象和装饰器进行分离,两者独立存在。符合开放封闭原则。


5. 代理模式

使用者无权访问目标对象。中间加代理,通过代理做授权和控制。

UML类图

传统UML类图

image.png

简化后的UML类图

image.png

使用场景

1. js的事件代理

image.png

2.jQuery的proxy

常规用法,使用_this变量来传递this image.png

$.proxy方式 image.png

3.ES6的Proxy(使用明星和经纪人的例子讲解)

image.png

设计原则验证

代理类和目标类分离,隔离开目标类和使用者。符合开放封闭原则。

代理模式与适配器模式、装饰器模式的对比

1. 代理模式 VS 适配器模式

适配器模式: 提供一个不同的接口(如不同版本的插头) 代理模式:提供一模一样的接口

2. 代理模式 VS 装饰器模式

装饰器模式: 扩展新功能,原有功能不变且可直接使用 代理模式:可使用原有功能,但是是经过限制或者阉割后的(第三者代理的)

6.外观模式

为子系统中的一组接口提供一个高层接口,使用者只能使用高层接口。

image.png

UML类图

image.png

使用场景

兼容多个功能的接口

image.png

另外,我认为后端的网关的功能和微前端框架也符合外观模式。

设计原则验证

不符合单一职责原则和开和封闭原则,因此谨慎使用,不可滥用。