设计模式 道 SOLID设计原则
单一功能原则(Single Responsibility Principle
开放封闭原则(Opened Closed Principle
里式替换原则(Liskov Substitution Principle)
接口隔离原则(Interface Segregation Principle)
依赖反转原则(Dependency Inversion Principle)
开放封闭原则:对拓展开放,对修改封闭。说得更准确点,软件实体(类、模块、函数)可以扩展,但是不可修改
设计模式核心思想-封装变化 将变与不变分离,确保变化的部分灵活、不变的部分稳定。
术
简单工厂模式 构造器模式 1 2 3 4 5 6 7 function User (name , age, career ) { this .name = name this .age = age this .career = career } const user = new User(name, age, career)
构造器模式可以很好的区分共性与个性即变与不变
简单工厂模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function User (name , age, career, work ) { this .name = name this .age = age this .career = career this .work = work } function Factory (name, age, career ) { let work switch (career) { case 'coder' : work = ['写代码' ,'写系分' , '修Bug' ] break case 'product manager' : work = ['订会议室' , '写PRD' , '催更' ] break case 'boss' : work = ['喝茶' , '看报' , '见客户' ] case 'xxx' : ... return new User(name, age, career, work) }
工厂模式其实就是将创建对象的过程单独封装 ,其目的就是为了实现无脑传参
小结 将创建对象的过程单独封装,这样的操作就是工厂模式 有构造函数的地方,我们就应该想到简单工厂 构造器解决的是多个对象实例的问题,简单工厂解决的是多个类的问题
抽象工厂模式 创建抽象工厂
1 2 3 4 5 6 7 8 9 10 class MobilePhoneFactory { createOS ( ) { throw new Error ("抽象工厂方法不允许直接调用,你需要将我重写!" ); } createHardWare ( ) { throw new Error ("抽象工厂方法不允许直接调用,你需要将我重写!" ); } }
抽象工厂不创建实例而是给具体工厂继承
具体工厂
1 2 3 4 5 6 7 8 9 10 11 class FakeStarFactory extends MobilePhoneFactory { createOS ( ) { return new AndroidOS() } createHardWare ( ) { return new QualcommHardWare() } }
抽象产品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class OS { controlHardWare ( ) { throw new Error ('抽象产品方法不允许直接调用,你需要将我重写!' ); } } class AndroidOS extends OS { controlHardWare ( ) { console .log('我会用安卓的方式去操作硬件' ) } } class AppleOS extends OS { controlHardWare ( ) { console .log('我会用🍎的方式去操作硬件' ) } } class HardWare { operateByOrder ( ) { throw new Error ('抽象产品方法不允许直接调用,你需要将我重写!' ); } } class QualcommHardWare extends HardWare { operateByOrder ( ) { console .log('我会用高通的方式去运转' ) } } class MiWare extends HardWare { operateByOrder ( ) { console .log('我会用小米的方式去运转' ) } }
当我们使用的时候只需要这样:
1 2 3 4 5 6 7 8 9 10 const myPhone = new FakeStarFactory()const myOS = myPhone.createOS()const myHardWare = myPhone.createHardWare()myOS.controlHardWare() myHardWare.operateByOrder()
如果不需要FakeStarFactory
,而是创建其他工厂就可以**不对抽象工厂MobilePhoneFactory
做任何修改,只需要拓展它的种类:
1 2 3 4 5 6 7 8 class newStarFactory extends MobilePhoneFactory { createOS ( ) { } createHardWare ( ) { } }
这样就符合对原有的系统不会造成任何潜在影响 的“对拓展开放,对修改封闭”的原则
总结
抽象工厂(抽象类,它不能被用于生成具体实例): 用于声明最终目标产品的共性。在一个系统里,抽象工厂可以有多个(大家可以想象我们的手机厂后来被一个更大的厂收购了,这个厂里除了手机抽象类,还有平板、游戏机抽象类等等),每一个抽象工厂对应的这一类的产品,被称为“产品族”。
具体工厂(用于生成产品族里的一个具体的产品): 继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体的产品的类。
抽象产品(抽象类,它不能被用于生成具体实例): 上面我们看到,具体工厂里实现的接口,会依赖一些类,这些类对应到各种各样的具体的细粒度产品(比如操作系统、硬件等),这些具体产品类的共性各自抽离,便对应到了各自的抽象产品类。
具体产品(用于生成产品族里的一个具体的产品所依赖的更细粒度的产品): 比如我们上文中具体的一种操作系统、或具体的一种硬件等。
单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点
单例模式的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class SingleDog { show ( ) { console .log('我是一个单例对象' ) } static getInstance ( ) { if (!SingleDog.instance) { SingleDog.instance = new SingleDog() } return SingleDog.instance } } const s1 = SingleDog.getInstance()const s2 = SingleDog.getInstance()s1 === s2
还可以用闭包实现
1 2 3 4 5 6 7 8 9 10 11 12 SingleDog.getInstance = (function ( ) { let instance = null return function ( ) { if (!instance) { instance = new SingleDog() } return instance } })()
Vuex中的单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let Vue ... export function install (_Vue ) { if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== 'production' ) { console .error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin(Vue) }
实现一个 Storage 实现Storage,使得该对象为单例,基于 localStorage 进行封装。实现方法 setItem(key,value) 和 getItem(key)。
静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Storage { static getInstance ( ) { if (!Storage.instance) { Storage.instance = new Storage() } return Storage.instance } getItem (key) { return localStorage .getItem(key) } setItem (key, value) { return localStorage .setItem(key, value) } } const storage1 = Storage.getInstance()const storage2 = Storage.getInstance()storage1.setItem('name' , '李雷' ) storage1.getItem('name' ) storage2.getItem('name' ) storage1 === storage2
闭包版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function StorageBase ( ) {}StorageBase.prototype.getItem = function (key ) { return localStorage .getItem(key) } StorageBase.prototype.setItem = function (key, value ) { return localStorage .setItem(key, value) } const Storage = (function ( ) { let instance = null return function ( ) { if (!instance) { instance = new StorageBase() } return instance } })() const storage1 = new Storage()const storage2 = new Storage()storage1.setItem('name' , '李雷' ) storage1.getItem('name' ) storage2.getItem('name' ) storage1 === storage2
实现一个全局的模拟框 实现一个全局唯一的Modal弹框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 单例模式弹框</title > </head > <style > #modal { height : 200px ; width : 200px ; line-height : 200px ; position : fixed; left : 50% ; top : 50% ; transform : translate (-50% , -50% ); border : 1px solid black; text-align : center; } </style > <body > <button id ='open' > 打开弹框</button > <button id ='close' > 关闭弹框</button > </body > <script > const Modal = (function ( ) { let modal = null return function ( ) { if (!modal) { modal = document .createElement('div' ) modal.innerHTML = '我是一个全局唯一的Modal' modal.id = 'modal' modal.style.display = 'none' document .body.appendChild(modal) } return modal } })() document .getElementById('open' ).addEventListener('click' , function ( ) { const modal = new Modal() modal.style.display = 'block' }) document .getElementById('close' ).addEventListener('click' , function ( ) { const modal = new Modal() if (modal) { modal.style.display = 'none' } }) </script > </html >
原型模式 原型模式不仅是一种设计模式,它还是一种编程范式(programming paradigm),是 JavaScript 面向对象系统实现的根基。
以类为中心的语言和以原型为中心的语言 JAVA中的类 JAVA 中,类才是它面向对象系统的根本。所以说在 JAVA 中,我们可以选择不使用原型模式 —— 这样一来,所有的实例都必须要从类中来,当我们希望创建两个一模一样的实例时,就只能这样做(假设实例从 Dog 类中来,必传参数为姓名、性别、年龄和品种):
1 2 3 Dog dog = new Dog('旺财' , 'male' , 3 , '柴犬' ) Dog dog_copy = new Dog('旺财' , 'male' , 3 , '柴犬' )
没错,我们不得不把一模一样的参数传两遍,非常麻烦。而原型模式允许我们通过调用克隆方法的方式达到同样的目的,比较方便,所以 Java 专门针对原型模式设计了一套接口和方法,在必要的场景下会通过原型方法来应用原型模式。当然,在更多的情况下,Java 仍以“实例化类”这种方式来创建对象。
JavaScript中的类 JS 的类基于prototype,ES6 的class为语法糖
ECMAScript 2015 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为 JavaScript 引入新的面向对象的继承模型。 ——MDN
原型范式 了解原型与原型链
需要了解深拷贝
深入了解深拷贝
装饰器模式 在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求
给点击按钮弹窗添加逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class OpenButton { onClick ( ) { const modal = new Modal() modal.style.display = 'block' } } class Decorator { constructor (open_button ) { this .open_button = open_button } onClick ( ) { this .open_button.onClick() this .changeButtonStatus() } changeButtonStatus ( ) { this .changeButtonText() this .disableButton() } disableButton ( ) { const btn = document .getElementById('open' ) btn.setAttribute("disabled" , true ) } changeButtonText ( ) { const btn = document .getElementById('open' ) btn.innerText = '快去登录' } } const openButton = new OpenButton()const decorator = new Decorator(openButton)document .getElementById('open' ).addEventListener('click' , function ( ) { decorator.onClick() })
我们把实例传给了Decorator
这样方便未来的拓展
单一职责原则 文本修改&按钮置灰这两个变化,被封装在了两个不同的方法里,这是一种单一职责的体现,在日常开发中要首先有尝试拆分 的敏感,其次要有该不该拆 的判断,如果逻辑颗粒度过小,盲目拆分会导致项目中有过多零碎的小方法。
ES7中的装饰器 ES7 中我们可以通过@语法糖给类添加装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function classDecorator (target ) { target.hasDecorator = true return target } @classDecorator class Button { } console .log('Button 是否被装饰了:' , Button.hasDecorator)
也可是使用它来装饰类里面的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function funcDecorator (target, name, descriptor ) { let originalMethod = descriptor.value descriptor.value = function ( ) { console .log('我是Func的装饰器逻辑' ) return originalMethod.apply(this , arguments ) } return descriptor } class Button { @funcDecorator onClick ( ) { console .log('我是Func的原有逻辑' ) } } const button = new Button()button.onClick()
装饰器语法糖背后的故事 函数传参 & 调用 1 2 3 4 5 6 7 8 9 10 function classDecorator (target ) { target.hasDecorator = true return target } @classDecorator class Button { }
给类添加装饰器时,target
是类本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function funcDecorator (target, name, descriptor ) { let originalMethod = descriptor.value descriptor.value = function ( ) { console .log('我是Func的装饰器逻辑' ) return originalMethod.apply(this , arguments ) } return descriptor } class Button { @funcDecorator onClick ( ) { console .log('我是Func的原有逻辑' ) } }
修饰方法时target
是Button.prototype
,因为onClick 方法总是要依附其实例存在的,修饰 onClik 其实是修饰它的实例。但我们的装饰器函数执行的时候,Button 实例还并不存在。为了确保实例生成后可以顺利调用被装饰好的方法,装饰器只能去修饰 Button 类的原型对象。
将“属性描述对象”交到你手里 1 2 3 4 5 6 7 8 function funcDecorator (target, name, descriptor ) { let originalMethod = descriptor.value descriptor.value = function ( ) { console .log('我是Func的装饰器逻辑' ) return originalMethod.apply(this , arguments ) } return descriptor }
target
是原型,name
是要修饰的属性,descriptor
就是Object.defineProperty(obj, prop, descriptor)
中的descriptor
生产实践 React中的装饰器:HOC
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React, { Component } from 'react' const BorderHoc = WrappedComponent => class extends Component { render ( ) { return <div style ={{ border: 'solid 1px red ' }}> <WrappedComponent /> </div > } } export default borderHocimport React, { Component } from 'react' import BorderHoc from './BorderHoc' @BorderHoc class TargetComponent extends React .Component { render ( ) { } } export default TargetComponent
使用装饰器改写 Redux connect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React, { Component } from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import action from './action.js' class App extends Component { render ( ) { } } function mapStateToProps (state ) { return state.app } function mapDispatchToProps (dispatch ) { return bindActionCreators(action, dispatch) } export default connect(mapStateToProps, mapDispatchToProps)(App)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import action from './action.js' function mapStateToProps (state ) { return state.app } function mapDispatchToProps (dispatch ) { return bindActionCreators(action, dispatch) } export default connect(mapStateToProps, mapDispatchToProps)import React, { Component } from 'react' import connect from './connect.js' @connect export default class App extends Component { render ( ) { } }
core-decorators
适配器模式 适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题。
axios中的适配器 在 axios 的核心逻辑 中,我们可以注意到实际上派发请求的是 dispatchRequest 方法 。该方法内部其实主要做了两件事:
数据转换,转换请求体/响应体,可以理解为数据层面的适配;
调用适配器。
调用如下适配器:
1 2 3 4 5 6 7 8 9 10 11 12 function getDefaultAdapter ( ) { var adapter; if (typeof process !== 'undefined' && Object .prototype.toString.call(process) === '[object process]' ) { adapter = require ('./adapters/http' ); } else if (typeof XMLHttpRequest !== 'undefined' ) { adapter = require ('./adapters/xhr' ); } return adapter; }
http适配器
1 2 3 4 5 module .exports = function httpAdapter (config ) { return new Promise (function dispatchHttpRequest (resolvePromise, rejectPromise ) { } }
xhr适配器
1 2 3 4 5 module .exports = function xhrAdapter (config ) { return new Promise (function dispatchXhrRequest (resolve, reject ) { } }
代理模式 ES6 的Proxy 1 const proxy = new Proxy (obj, handler)
每次访问obj都需要通过handler
getter拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const baseInfo = ['age' , 'career' ]const privateInfo = ['avatar' , 'phone' ]const user = { ...(一些必要的个人信息) isValidated : true , isVIP : false , } const JuejinLovers = new Proxy (girl, { get : function (girl, key ) { if (baseInfo.indexOf(key)!==-1 && !user.isValidated) { alert('您还没有完成验证哦' ) return } if (user.isValidated && privateInfo.indexOf(key) && !user.isVIP) { alert('只有VIP才可以查看该信息哦' ) return } } })
setter拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 const present = { type : '巧克力' , value : 60 , } const girl = { name : '小美' , aboutMe : '...' (大家自行脑补吧) age : 24 , career : 'teacher' , fakeAvatar : 'xxxx' (新垣结衣的图片地址) avatar : 'xxxx' (自己的照片地址), phone : 123456 , presents : [], bottomValue : 50 , lastPresent : present, } const JuejinLovers = new Proxy (girl, { get : function (girl, key ) { if (baseInfo.indexOf(key)!==-1 && !user.isValidated) { alert('您还没有完成验证哦' ) return } if (user.isValidated && privateInfo.indexOf(key)!==-1 && !user.isVIP) { alert('只有VIP才可以查看该信息哦' ) return } } set : function (girl, key, val ) { if (key === 'lastPresent' ) { if (val.value < girl.bottomValue) { alert('sorry,您的礼物被拒收了' ) return } girl.lastPresent = val girl.presents = [...girl.presents, val] } } })
常见四种代理模式 事件代理 1 2 3 4 5 6 7 8 9 10 11 12 const father = document .getElementById('father' )father.addEventListener('click' , function (e ) { if (e.target.tagName === 'A' ) { e.preventDefault() alert(`我是${e.target.innerText} ` ) } } )
虚拟代理 图片预加载
错误的做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class PreLoadImage { static LOADING_URL = 'xxxxxx' constructor (imgNode ) { this .imgNode = imgNode } setSrc (targetUrl ) { this .imgNode.src = PreLoadImage.LOADING_URL const image = new Image() image.onload = () => { this .imgNode.src = targetUrl } image.src = targetUrl } }
正确的做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class PreLoadImage { constructor (imgNode ) { this .imgNode = imgNode } setSrc (imgUrl ) { this .imgNode.src = imgUrl } } class ProxyImage { static LOADING_URL = 'xxxxxx' constructor (targetImage ) { this .targetImage = targetImage } setSrc (targetUrl ) { this .targetImage.setSrc(ProxyImage.LOADING_URL) const virtualImage = new Image() virtualImage.onload = () => { this .targetImage.setSrc(targetUrl) } virtualImage.src = targetUrl } }
ProxyImage 帮我们调度了预加载相关的工作,我们可以通过 ProxyImage 这个代理,实现对真实 img 节点的间接访问,并得到我们想要的效果。
在这个实例中,virtualImage 这个对象是一个“幕后英雄”,它始终存在于 JavaScript 世界中、代替真实 DOM 发起了图片加载请求、完成了图片加载工作,却从未在渲染层面抛头露面。因此这种模式被称为“虚拟代理”模式。
缓存代理 对计算结果缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const addAll = function ( ) { console .log('进行了一次新计算' ) let result = 0 const len = arguments .length for (let i = 0 ; i < len; i++) { result += arguments [i] } return result } const proxyAddAll = (function ( ) { const resultCache = {} return function ( ) { const args = Array .prototype.join.call(arguments , ',' ) if (args in resultCache) { return resultCache[args] } return resultCache[args] = addAll(...arguments) } })()
保护代理 上面拦截getter和setter就是一种保护代理
小结 代理模式十分多样,可以是为了加强控制、拓展功能、提高性能,也可以仅仅是为了优化我们的代码结构、实现功能的解耦。
策略模式 例子 错误做法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function askPrice (tag, originPrice ) { if (tag === 'pre' ) { if (originPrice >= 100 ) { return originPrice - 20 } return originPrice * 0.9 } if (tag === 'onSale' ) { if (originPrice >= 100 ) { return originPrice - 30 } return originPrice * 0.8 } if (tag === 'back' ) { if (originPrice >= 200 ) { return originPrice - 50 } return originPrice } if (tag === 'fresh' ) { return originPrice * 0.5 } if (tag === 'newUser' ) { if (originPrice >= 100 ) { return originPrice - 50 } return originPrice } }
单一功能改造 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 function prePrice (originPrice ) { if (originPrice >= 100 ) { return originPrice - 20 } return originPrice * 0.9 } function onSalePrice (originPrice ) { if (originPrice >= 100 ) { return originPrice - 30 } return originPrice * 0.8 } function backPrice (originPrice ) { if (originPrice >= 200 ) { return originPrice - 50 } return originPrice } function freshPrice (originPrice ) { return originPrice * 0.5 } function askPrice (tag, originPrice ) { if (tag === 'pre' ) { return prePrice(originPrice) } if (tag === 'onSale' ) { return onSalePrice(originPrice) } if (tag === 'back' ) { return backPrice(originPrice) } if (tag === 'fresh' ) { return freshPrice(originPrice) } }
对扩展开放,对修改封闭 对象映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const priceProcessor = { pre (originPrice ) { if (originPrice >= 100 ) { return originPrice - 20 ; } return originPrice * 0.9 ; }, onSale (originPrice ) { if (originPrice >= 100 ) { return originPrice - 30 ; } return originPrice * 0.8 ; }, back (originPrice ) { if (originPrice >= 200 ) { return originPrice - 50 ; } return originPrice; }, fresh (originPrice ) { return originPrice * 0.5 ; }, };
定义 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
状态模式 例子
美式咖啡态(american):只吐黑咖啡
普通拿铁态(latte):黑咖啡加点奶
香草拿铁态(vanillaLatte):黑咖啡加点奶再加香草糖浆
摩卡咖啡态(mocha):黑咖啡加点奶再加点巧克力错误写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CoffeeMaker { constructor ( ) { this .state = 'init' ; } changeState (state ) { this .state = state; if (state === 'american' ) { console .log('我只吐黑咖啡' ); } else if (state === 'latte' ) { console .log(`给黑咖啡加点奶` ); } else if (state === 'vanillaLatte' ) { console .log('黑咖啡加点奶再加香草糖浆' ); } else if (state === 'mocha' ) { console .log('黑咖啡加点奶再加点巧克力' ); } } }
职责分离 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class CoffeeMaker { constructor ( ) { this .state = 'init' ; } changeState (state ) { this .state = state; if (state === 'american' ) { this .americanProcess(); } else if (state === 'latte' ) { this .latteProcress(); } else if (state === 'vanillaLatte' ) { this .vanillaLatteProcress(); } else if (state === 'mocha' ) { this .mochaProcress(); } } americanProcess ( ) { console .log('我只吐黑咖啡' ); } latteProcress ( ) { this .americanProcess(); console .log('加点奶' ); } vanillaLatteProcress ( ) { this .latteProcress(); console .log('再加香草糖浆' ); } mochaProcress ( ) { this .latteProcress(); console .log('再加巧克力' ); } } const mk = new CoffeeMaker();mk.changeState('latte' );
开放封闭 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const stateToProcessor = { american ( ) { console .log('我只吐黑咖啡' ); }, latte ( ) { this .american(); console .log('加点奶' ); }, vanillaLatte ( ) { this .latte(); console .log('再加香草糖浆' ); }, mocha ( ) { this .latte(); console .log('再加巧克力' ); } } class CoffeeMaker { constructor ( ) { this .state = 'init' ; } changeState (state ) { this .state = state; if (!stateToProcessor[state]) { return ; } stateToProcessor[state](); } } const mk = new CoffeeMaker();mk.changeState('latte' );
这种方法仅仅是看上去完美无缺,其中却暗含一个非常重要的隐患——stateToProcessor 里的工序函数,感知不到咖啡机的内部状况。
进一步改造 把咖啡机和它的状态处理函数建立关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class CoffeeMaker { constructor ( ) { this .state = 'init' ; this .leftMilk = '500ml' ; } stateToProcessor = { that : this , american ( ) { console .log('咖啡机现在的牛奶存储量是:' , this .that.leftMilk) console .log('我只吐黑咖啡' ); }, latte ( ) { this .american() console .log('加点奶' ); }, vanillaLatte ( ) { this .latte(); console .log('再加香草糖浆' ); }, mocha ( ) { this .latte(); console .log('再加巧克力' ); } } changeState (state ) { this .state = state; if (!this .stateToProcessor[state]) { return ; } this .stateToProcessor[state](); } } const mk = new CoffeeMaker();mk.changeState('latte' );
策略与状态辨析 策略模式是对算法的封装。算法和状态对应的行为函数虽然本质上都是行为,但是算法的独立性可高。 状态模式需要对主体有感知,来判断接下来是否可执行。 总结: 策略模式中函数不依赖调用主体,互相平行。状态模式中的函数和状态主体关联,由状态主体将他们串联,所以不会特别割裂。
定义 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
观察者模式 定义 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
例子 发布者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Publisher { constructor ( ) { this .observers = [] console .log('Publisher created' ) } add (observer ) { console .log('Publisher.add invoked' ) this .observers.push(observer) } remove (observer ) { console .log('Publisher.remove invoked' ) this .observers.forEach((item, i ) => { if (item === observer) { this .observers.splice(i, 1 ) } }) } notify ( ) { console .log('Publisher.notify invoked' ) this .observers.forEach((observer ) => { observer.update(this ) }) } }
订阅者:被通知,去执行
1 2 3 4 5 6 7 8 9 10 class Observer { constructor ( ) { console .log('Observer created' ) } update ( ) { console .log('Observer.update invoked' ) } }
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class PrdPublisher extends Publisher { constructor ( ) { super () this .prdState = null this .observers = [] console .log('PrdPublisher created' ) } getState ( ) { console .log('PrdPublisher.getState invoked' ) return this .prdState } setState (state ) { console .log('PrdPublisher.setState invoked' ) this .prdState = state this .notify() } } class DeveloperObserver extends Observer { constructor ( ) { super () this .prdState = {} console .log('DeveloperObserver created' ) } update (publisher ) { console .log('DeveloperObserver.update invoked' ) this .prdState = publisher.getState() this .work() } work ( ) { const prd = this .prdState ... console .log('996 begins...' ) } } const liLei = new DeveloperObserver()const A = new DeveloperObserver()const B = new DeveloperObserver()const hanMeiMei = new PrdPublisher()const prd = { ... } hanMeiMei.add(liLei) hanMeiMei.add(A) hanMeiMei.add(B) hanMeiMei.setState(prd)
vue中的响应原理
observer(监听器):注意,此 observer 非彼 observer。在我们上节的解析中,observer 作为设计模式中的一个角色,代表“订阅者”。但在Vue数据双向绑定的角色结构里,所谓的 observer 不仅是一个数据监听器,它还需要对监听到的数据进行转发——也就是说它同时还是一个发布者。
watcher(订阅者):observer 把数据转发给了真正的订阅者——watcher对象。watcher 接收到新的数据后,会去更新视图。
compile(编译器):MVVM 框架特有的角色,负责对每个节点元素指令进行扫描和解析,指令的数据初始化、订阅者的创建这些“杂活”也归它管~
实现observer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function observe (target ) { if (target && typeof target === 'object' ) { Object .keys(target).forEach((key )=> { defineReactive(target, key, target[key]) }) } } function defineReactive (target, key, val ) { const dep = new Dep() observe(val) Object .defineProperty(target, key, { enumerable : true , configurable : false , get : function ( ) { return val; }, set : function (value ) { dep.notify(); } }); }
实现订阅者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Dep { constructor ( ) { this .subs = [] } addSub (sub ) { this .subs.push(sub) } notify ( ) { this .subs.forEach((sub )=> { sub.update() }) } }
实现一个Event Bus/ Event Emitter 全局事件总线,严格来说不能说是观察者模式,而是发布-订阅模式。
在Vue中使用Event Bus来实现组件间的通讯 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const EventBus = new Vue()export default EventBusimport bus from 'EventBus的文件路径' Vue.prototype.bus = bus this .bus.$on('someEvent' , func)this .bus.$emit('someEvent' , params)
整个调用过程中,没有出现具体的发布者和订阅者(比如上节的PrdPublisher和DeveloperObserver),全程只有bus这个东西一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的发布/订阅操作,必须经由事件中心,禁止一切“私下交易”!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class EventEmitter { constructor ( ) { this .handlers = {} } on (eventName, cb ) { if (!this .handlers[eventName]) { this .handlers[eventName] = [] } this .handlers[eventName].push(cb) } emit (eventName, ...args ) { if (this .handlers[eventName]) { this .handlers[eventName].forEach((callback ) => { callback(...args) }) } } off (eventName, cb ) { const callbacks = this .handlers[eventName] const index = callbacks.indexOf(cb) if (index !== -1 ) { callbacks.splice(index, 1 ) } } once (eventName, cb ) { const wrapper = (...args ) => { cb(...args) this .off(eventName, wrapper) } this .on(eventName, wrapper) } }
FaceBook推出的通用EventEmiiter库
观察者模式与发布-订阅模式的区别是什么 回到我们上文的例子里。韩梅梅把所有的开发者拉了一个群,直接把需求文档丢给每一位群成员,这种发布者直接触及到订阅者的操作,叫观察者模式。但如果韩梅梅没有拉群,而是把需求文档上传到了公司统一的需求平台上,需求平台感知到文件的变化、自动通知了每一位订阅了该文件的开发者,这种发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式。
观察者模式,解决的其实是模块间的耦合问题,有它在,即便是两个分离的、毫不相关的模块,也可以实现数据通信。但观察者模式仅仅是减少了耦合,并没有完全地解决耦合问题——被观察者必须去维护一套观察者的集合,这些观察者必须实现统一的方法供被观察者调用,两者之间还是有着说不清、道不明的关系。
而发布-订阅模式,则是快刀斩乱麻了——发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(事件总线)上。发布-订阅模式下,实现了完全地解耦。
但这并不意味着,发布-订阅模式就比观察者模式“高级”。在实际开发中,我们的模块解耦诉求并非总是需要它们完全解耦。如果两个模块之间本身存在关联,且这种关联是稳定的、必要的,那么我们使用观察者模式就足够了。而在模块与模块之间独立性较强、且没有必要单纯为了数据通信而强行为两者制造依赖的情况下,我们往往会倾向于使用发布-订阅模式。
迭代器模式 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
ES6对迭代器的实现 ES6约定,任何数据结构只要具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for…of…循环和迭代器的next方法遍历。 事实上,for…of…的背后正是对next方法的反复调用。
1 2 3 4 5 6 7 8 const arr = [1 , 2 , 3 ]const iterator = arr[Symbol .iterator]()iterator.next() iterator.next() iterator.next()
迭代器协议
实现迭代器生成函数 1 2 3 4 5 6 7 8 9 10 11 12 function *iteratorGenerator ( ) { yield '1号选手' yield '2号选手' yield '3号选手' } const iterator = iteratorGenerator()iterator.next() iterator.next() iterator.next()
实现这个语法糖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function iteratorGenerator (list ) { var idx = 0 var len = list.length return { next : function ( ) { var done = idx >= len var value = !done ? list[idx++] : undefined return { done : done, value : value } } } } var iterator = iteratorGenerator(['1号选手' , '2号选手' , '3号选手' ])iterator.next() iterator.next() iterator.next()