博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Javascript设计模式
阅读量:6264 次
发布时间:2019-06-22

本文共 26660 字,大约阅读时间需要 88 分钟。

设计原则(SOLID)

单一职责模式(S)

  • 一个程序只做好一件事
  • 如果功能过于复杂就拆分开,每个部分保持独立

里式替换原则(L)

  • 子类能覆盖父类
  • 父类能出现的地方子类就能出现
  • JS中使用较少(弱类型&继承使用较少)

开放封闭原则(O)

  • 对扩展开放对修改封闭
  • 增加需求时,扩展新代码,而非修改已有代码
  • 软件设计的终极目标

接口隔离原则(I)

  • 保持接口的单一独立,避免出现"胖接口"
  • JS中没有接口(typescript例外),使用较少
  • 类似于单一职责所在,这里更关注接口

依赖倒置原则(D)

  • 面向接口编程,依赖于抽象而不依赖于具体
  • 使用方只关注接口而不关注具体类的实现
  • JS中使用较少(没有接口&弱类型)

设计模式

工厂模式

  • 将new操作单独封装
  • 遇到new时,就要考虑是否该使用工厂模式了

示例

你去购买汉堡,直接点餐、取餐,不会自己亲手做

商店要“封装”做汉堡的工作,做好直接给买者

UML类图:

代码示例:

class Product {    constructor(name) {        this.name = name;    }    init() {        console.log('init')    }    fn1() {        console.log('fn1')    }    fn2() {        console.log('fn2')    }}class Creator {    create(name) {        return new Product(name)    }}let create = new Creator();let p = create.create('p')p.init()p.fn1()p.fn2()复制代码

应用场景

  • jQuery:

    $('div')new $('div')有何区别?

  • 第一:书写麻烦,jQuery的链式操作将成为噩梦

  • 第二:一旦jQuery名字变化,将是灾难性的

//仿jQuery代码class jQuery {    constructor(selector) {        let slice = Array.prototype.slice;        let dom = slice.call(document.querySelectorAll(selector))        let len = dom ? dom.length : 0        for (let i = 0; i < len; i++) {            this[i] = dom[i]        }        this.length = len        this.selector = selector || ''    }    append() {        console.log('append');    }    addClass() {        console.log('addClass')    }}window.$ = function(selector) {    return new jQuery(selector);}var $p = $('p')console.log($p)console.log($p.addClass)复制代码
  • React.crateElement:
var profile = 

{[user.firstName,user.lastName].join('')}

;复制代码

编译完之后:

var profile = React.createElement("div",null,    React.createElement("img",{src:"avater.png",className:"profile"}),    React.createElement("h3",null,[user.firstName,user.lastName].join(" ")));复制代码
//源码实现class vnode(tag, attrs, children) {    //...省略内部代码...}React.createElement = function(tag,attrs,children){    return new vnode(tag,attrs,children)}复制代码
  • Vue的异步组件:
Vue.component('async-example', funciton(resolve, reject) {    setTimeout(function() => {        resolve({            template: '
I am async!
' }) }, 1000);})复制代码

设计原则验证:

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

单例模式

  • 系统中被唯一使用
  • 一个类中只有一个实例

 

实例:

登录框、购物车

传统UML图

说明

  • 单例模式需要用到java的特性(private)
  • ES6中没有(typescript除外)
  • 只能用java代码来演示UML图的内容(最后用js变相实现)

代码演示

java版的单例模式演示

public class SingleObject{     //注意:私有化构造函数,外部不能new,只能内部new!!!!     private SingleObject(){}     //唯一被new出来的对象     private SingleObject getInstance(){         if(instance == null){             //只new一次             instance = new SingleObject();         }         return instance;     }     //对象方法     public void login(username,password){         System.out.println("login...")     } }   public class SingletonPatternDemo{     public static void main(String[] args){        //不合法的构造函数        //编译时报错:构造函数 SingleObject()是不可见的!!!        //SingleObject object = new SingleObject();        //获取唯一可用的对象        SingleObject object = SingleObject.getInstance();     }     }复制代码

Javascript版的单例模式演示

class SingleObject {    login() {        console.log('login...')    }}//静态方法SingleObject.getInstance = (function() {    let instance    return function() {        if (!instance) {            instance = new SingleObject();        }        return instance;    }})()var login = SingleObject.getInstance().login();复制代码

javascript的单例模式缺点:

如果强制new也不会报错:

var loginnew = new SingleObject();loginnew.login()复制代码

测试

//注意这里只能用静态函数getInstance,不能new SingleObject()!!!let obj1 = SingleObject.getInstance()obj1.login()let obj2 = SingleObject.getInstance()obj2.login()console.log(obj1 === obj2); //两者必须完全相同复制代码

只有通过模块化完整实现

场景

  • jQuery 只有一个'$'
if(window.jQuery != null){    return window.jQuery}else{    //初始化...}//引用多少次都只有一个'$'复制代码
  • vuex 和 redux中的store

  • 购物车、登录框

class LoginForm {    constructor() {        this.state = 'hide'    }    show() {        if (this.state === 'show') {            alert('已经显示了');            return        }        this.state = 'show'        console.log('登录框显示成功')    }    hide() {        if (this.state === 'hide') {            alert('已经隐藏')            return        }        this.state = 'hide'        console.log('登录框隐藏成功')    }}LoginForm.getInstance = (function() {    let instance    return function() {        if (!instance) {            instance = new LoginForm()        }        return instance    }})()let login1 = LoginForm.getInstance()login1.show()let login2 = LoginForm.getInstance()//lgoin2.show() //登录框已经显示login2.hide()console.log(login1 === login2)复制代码

设计原则验证

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

适配器模式

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

示例

macbookpro适配器转换 电源插座国家不统一需要转换头

UML图

演示

class Adaptee {    specificRequest() {        return '德国标准插头'    }}class Target {    constructor() {        this.Adaptee = new Adaptee()    }    request() {        let info = this.Adaptee.specificRequest()        return `${info} - 转换器 - 中国标准插头`    }}let target = new Target()let res = target.request()console.log(res)复制代码

应用场景

  • 封装旧接口
//自己封装的ajax,使用方式如下:ajax({    url:'/getDate',    type:'Post',    dataType:'json',    data:{        id:123    }}).done(function(){})复制代码
//但因为历史原因,代码中全都是://$.ajax({...})复制代码

解决办法:

//做一层适配器var $ = {    ajax:function(options){        return ajax(options)    }}复制代码
  • vue computed

Original message:"{

{message}}"

Computed reversed message:"{

{reversedMessage}}"

var vm = new Vue({ el:"#example", data:{ mesage:'Hello' }, computed:{ //计算属性的getter reversedMessage:function(){ //'this'指向vm实例 return this.message.split('').reverse().join('') } }})复制代码

设计原则验证

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

装饰器模式

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

示例:

手机壳

UML类图

代码演示

class Circle {    draw() {        console.log('画一个圆形')    }}class Decorator {    constructor(circle) {        this.circle = circle    }    draw() {        this.circle.draw()        this.setRedBorder(circle)    }    setRedBorder(circle) {        console.log('设置红色边框')    }}let circle = new Circle();circle.draw()let decorator = new Decorator(circle)decorator.draw()复制代码

使用场景

  • ES7装饰器
@testDecclass Demo {    //...}function testDec(target) {    target.isDec = true;}alert(Demo.isDec);复制代码

装饰器原理

@decoratorclass A {}//等同于class A{}A = decorator(A)||A;复制代码

可以加参数

function testDec(isDec){    return function(target){        target.isDec = isDec;    }}@testDec(true)class Demo{    //....}alert(Demo.isDec) //true复制代码
function mixin(...list) {    return function(target) {        Object.assign(target.prototype, ...list)    }}const Foo = {    foo() { alert('foo') }}@mixin(Foo)class myClass() {}let obj = new myClass();obj.foo() //'foo'复制代码

装饰方法-例1

class Person {    constructor() {            this.first = 'A'            this.last = 'B'        }        //装饰方法    @readonly    name() {        return `${this.first} ${this.last}`    }}var p = new Person()console.log(p.name())   //p.name=function(){} //这里会报错,因为name是只读属性function readonly(target, name, descriptor) {    //descriptor 属性描述对象(Object.defineProperty中会用到),原来的值如下    //{    //  value:specifiedFunction,    //  enumerable:false,    //  configurable:true,    //  writable:true       //}    descriptor.writable = false;    return descriptor;}复制代码

装饰方法-例2

class Math{    //装饰方法    @log    add(a,b){        return a + b;    }}const math = new Math();const result = math.add(2,4);  //执行add时,会自动打印日志,因为有@log装饰器console.log('result',result)function log(target, name, descriptor) {    var oldvalue = descriptor.value;    descriptor.value = function() {        console.log(`calling ${name} with`, arguments);        return oldvalue.apply(this, arguments)    }    return descriptor;}复制代码
  • core-decorators
  • 第三方开源lib
  • 提供常用的装饰器
//首先安装npm i core-decorators --save//开始编码import { readonly } from 'core-decorators'class Person {    @readonly    name() {        return 'zhang'    }}let p = new Person()alert(p.name())    //p.name = function(){/*...*/} 此处会报错复制代码
import { deprecate } from 'core-decorators'class Person {    @deprecate    name() {        return 'zhang'    }}let p = new Person()alert(p.name())    //this funciton will be removed in future Vue.version    //也可以自己定义@deprecate("即将废用")    //也可以自己定义@deprecate("即将废用",{url:"www.imooc.com"})复制代码

设计原则验证

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

代理模式

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

示例:

  • 科学上网
  • 明星经纪人

UML

代码演示

class RealImg {    constructor(fileName) {        this.fileName = fileName;        this.loadFromDisk() //初始化即从硬盘中加载,模拟    }    display() {        console.log('display...' + this.fileName)    }    loadFromDisk() {        console.log('loading...' + this.fileName)    }}class ProxyImg {    constructor(fileName) {        this.realImg = new RealImg(fileName)    }    display() {        this.realImg.display()    }}let proxyImg = new ProxyImg('1.png')proxyImg.display()复制代码

场景

  • 网页事件代理
var div1 = document.getElementById('div1') div1.addEventListener('click', funtion(e) {    console.log(e)    var target = e.target    if (target.nodeName === "A") {        alert(target.innerHtml)    }})复制代码
  • jQuery $.proxy
$('#div1').click(function() {    //this符合期望    $(this).addClass('red')})$('#div1').click(function() {    setTimeout(function() {        //this不符合期望        $(this).addClass('red')    }, 1000);})复制代码
//可以用如下方式解决$('#div1').click(function() {    var _this = this    setTimeout(funciton() {        //_this符合期望        $(_this).addClass('red')    }, 1000)})复制代码

或者用$.proxy

//但推荐用$.proxy解决,这样就少定义一个变量$('#div1').click(function() {    setTimeout($.proxy(function() {        //this符合期望        $(this).addClass('red')    },this), 1000)})复制代码
  • ES6 Proxy
//明星let star = {    name: "zhangxx",    age: 25,    phone: '13910733521',}//经纪人let agent = new Proxy(star, {    get: function(target, key) {        if (key === 'phone') {            //返回经纪人自己的手机号            return '13838383838'        }        if (key === "price") {            //明星不报价,经纪人报价            return 120000        }        return target[key]    },    set: function(target, key, val) {        if (key === 'customPrice') {            if (val < 100000) {                throw new Error("价格太低")            } else {                target[key] = val                return true            }        }    }})console.log(agent.name)console.log(agent.phone)console.log(agent.age)console.log(agent.price)agent.customPrice = 150000;console.log('agent.customPrice', agent.customPrice)复制代码

设计原则验证

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

代理模式VS适配器模式

  • 适配器模式:提供一个不同的接口(如不同版本的插头,无法使用)
  • 代理模式:提供一模一样的接口(无权使用)

代理模式VS装饰器模式

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

外观模式

  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用这个高层接口

示例:

去医院看病,接待员去挂号、门诊、划价、取药

UML类图

代码演示

function bindEvent(elem,type,selector,fn){    if(fn == null){        fn = selector        selector = null    }}//调用bindEvent(elem,'click','#div1',fn)bindEvent(elem,'click',fn)复制代码

设计原则验证

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

观察者模式

  • 发布&订阅
  • 一对多(N)

示例

  • 点咖啡,点好之后坐等被叫

UML类图

前端设计最重要的一种模式

代码演示

//保存状态,状态变化之后触发所有观察者class Subject {    constructor() {        this.state = 0        this.observers = []    }    getState() {        return this.state    }    setState(state) {        this.state = state        this.notifyAllObervers()    }    notifyAllObervers() {        this.observers.forEach(observer => {            observer.update()        })    }    attach(observer) {        this.observers.push(observer)    }}//观察者class Observer {    constructor(name, subject) {        this.name = name        this.subject = subject        this.subject.attach(this)    }    update() {        console.log(`${this.name} update,state:${this.subject.getState()}`)    }}let subject = new Subject();let obs1 = new Observer('o1', subject);let obs2 = new Observer('o2', subject);let obs3 = new Observer('o3', subject);subject.setState(1)subject.setState(2)复制代码

应用场景

  • 网页事件绑定

所有的事件监听用的都是观察者模式

复制代码
  • Promise
function loadImg(src) {    var promise = new Promise(function(resolve, reject) {        var img = document.createElement('img')        img.onload = function() {            resolve(img)        }        img.onerror = function() {            reject('图片加载失败')        }        img.src = src    })    return promise}var src = "https://www.xxx.com/img/dafdafdfdafdsafd.png"var result = loadImg()result.then(function(img){    console.log('width',img.width)}).then(function(img){    console.log('width',img.height)})复制代码
  • jQuery callbacks
var callbacks = $.Callbacks() //注意大小写callbacks.add(function() {    console.log('fn1', info)})callbacks.add(function() {    console.log('fn2', info)})callbacks.add(function() {    console.log('fn3', info)})callbacks.fire('gogoogogo')callbacks.fire('fire')复制代码
  • nodejs自定义事件
cosnt EventEmitter = require('events').EventEmitterconst emitter1 = new EventEmitter()emitter1.on('some', () => {    //监听some事件    console.log('some events is occured 1')})emitter1.on('some', () => {        //监听some事件        console.log('some events is occured 2')    })    //触发some事件emitter1.emit('some')复制代码
const EventEmitter = require('events').EventEmitter//任何构造函数都可以继承 EventEmitter的方法on emitclass Dog extends EventEmitter {    constructor(name) {        super()        this.name = name    }}var simon = new Dog('simon')simon.on('bark', function() {    console.log(this.name, 'barked')})setInterval(() => {    simon.emit('bark')}, 500)复制代码
//Stream 用到了自定义事件var fs = require('fs')var readStream = fs.createReadStream('./data/file1.txt') //读取文件的streamvar length = 0readStream.on('data', function(chunk) {    length += chunk.toString().length})readStream.on('read', function() {    console.log(length)})复制代码
//readline用到了自定义事件var readline = require('readline')var fs = require('fs')var rl = readline.createInterface({    input: fs.createReadStream('./data/file1.txt')});var lineNum = 0rl.on('line', function(line) {    lineNum++})rl.on('close', function() {    console.log('lineNum', lineNum)})复制代码
  • nodejs中:处理http请求;多进程通讯
function serverCallback(req, res) {    var method = req.method.toLowerCase() //获取请求方法    if (method === 'get') {        //省略3行,上文代码示例中处理GET请求的代码    }    if (method === 'post') {        //接受post请求的内容        var data = ''        req.on('data', function() {            //"一点一点"接收内容            data += chunk.toString()        })        req.on('end', function() {            //接收完毕,将内容输出            res.writeHead(200, { 'Content-type': 'text/html' })            res.write(data)            res.end()        })    }}复制代码
//parent.jsvar cp = require('child_process')var n = cp.fork('./sub.js')n.on('message', function(m) {    console.log('PARENT got message:' + m)})n.send({ hello: 'workd' })//sub.jsprocess.on('message', function(m) {    console.log('CHILD got message:' + m)})process.send({ foo: 'bar' })复制代码
  • vue和React组件生命周期触发
class login extends React.component {    constructor(prop, context) {        super(props, context)        this.shouldComponentUpate = PureRenderMixin.shouldComponentUpate.bind(this);        this.state = {            checking: true        }    }    render() {        return (             
) } componentDidMount() { //判断是否已经登录 this.doCheck() }}复制代码
  • vue watch
var vm = new Vue({    el: "#demo",    data: {        firstName: 'Foo',        lastName: 'Bar',        fullName: 'Foo Bar'    },    watch: {        firstName: function(val) {            this.fullName = val + '' + this.lastName        },        lastName: function(val) {            this.fullName = this.firstName + '' + val        }    }})复制代码

设计原则验证

  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦
  • 符合开放封闭原则

迭代器模式

  • 顺序访问一个集合
  • 使用者无需知道集合的内部结构(封装)

示例

  • 没有合适的示例,jQuery演示一下

jQuery each

jQuery each

jQuery each

复制代码
var arr = [1,2,3]var nodeList = document.getElementsByTagName('p')var $p = $('p')//要对这三个对象进行遍历,要写三个遍历方法arr.forEach(function(item){    console.log(item)})//nodeList不是纯数组var i,length = nodeList.length;for(i;i

UML类图

代码演示

class Iterator {    constructor(container) {        this.list = container.list;        this.index = 0;    }    next() {        if (this.hasNext()) {            return this.list[this.index++]        }    }    hasNext() {        if (this.index >= this.list.length) {            return false;        }        return true;    }}class Container {    constructor(list) {            this.list = list        }        //生成遍历器    getIterator() {        return new Iterator(this)    }}let arr = [1, 2, 3, 4, 5, 6]let container = new Container(arr)let iterator = container.getIterator()while (iterator.hasNext()) {    console.log(iterator.next())}复制代码

应用场景

  • jQuery each
function each(data){    var $data = $(data)   //生成迭代器    $data.each(function(key,p){        console.log(key,p)    })}复制代码
  • ES6 Iterator
  • ES6语法中,有序集合的数据类型已经有很多
  • ArrayMapSetStringTypedArrayargumentNodeList
  • 以上数据类型都有[Symbol.Iterator]属性
  • 属性值是函数,执行函数返回一个迭代器
  • 这个迭代器就有next方法可顺序迭代子元素
  • 可运行Array.prototype[Symbol.iterator]来测试
  • for...of 消费 iterator
  • ES6 Iterator与Generator
  • iterator的价值不限于尚书几个类型的遍历,还有Generator函数的使用
  • 即只要返回的数据符合Iterator接口的要求
  • 即可使用Iterator语法,这就是迭代器模式

设计原则验证

  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象隔离开
  • 符合开放封闭原则

状态模式

  • 一个对象有状态变化
  • 每次状态变化都会触发一个逻辑
  • 不能总是用if...else来控制

示例

交通信号灯不同颜色的变化

UML类图

代码演示

//状态class State {    constructor(state) {        this.state = state    }    getState() {        return this.state    }    handle(context) {        console.log(`turn to ${this.state} light`)        context.setState(this)    }}//主体class Context {    constructor() {        this.state = null    }    getState() {        return this.state    }    setState(state) {        this.state = state    }}let context = new Context()let green = new State('green')let yellow = new State('yellow')let red = new State('red')green.handle(context);console.log(context.getState())yellow.handle(context);console.log(context.getState())red.handle(context);console.log(context.getState())复制代码

应用场景

  • 有限状态机
  • 有限个状态,以及在这些状态之间的变化,如交通信号灯
  • 使用开源lib:javascript-state-machine
//状态机模型import StateMachine from 'javascript-state-machine'var fsm = new StateMachine({    init: '收藏', //初始状态,待收藏    transitions: [{            name: 'doStore',            from: '收藏',            to: '取消收藏'        },        {            name: 'deleteStore',            from: '取消收藏',            to: '收藏'        }    ],    method: {        //执行收藏        onDoStore: function() {            alert('收藏成功')            updateText()        },        onDeleteStore: function() {            alert('取消收藏')            updateText()        }    }})var $btn = $('#btn');    //点击事件$btn.click(function() {    if (fsm.is('收藏')) {        fsm.doStore()    } else {        fsm.deleteStore()    }})//更新文案function updateText() {    $btn.text(fsm.state)}//初始化文案updateText()复制代码
  • 写一个简单的Promise
  • Promise是一个一个有限状态机
  • Promise有三种状态:pending、fullfilled、rejected
  • pending -> fullfilled 或者 pending -> rejected,不可逆向变化
class MyPromise {    constructor(fn) {        this.successList = []        this.failList = []        fn(() => {            //resolve函数            fsm.resolve(this)        }, () => {            //reject函数            fsm.reject(this)        })    }    then(successFn, failFn) {        this.successList.push(successFn)        this.failList.push(failFn)    }}//模型var fsm = new StateMachine({    init: 'pending',    transitions: [{        name: 'resolve',        from: 'pending',        to: 'fullfilled'    }, {        name: 'reject',        from: 'pending',        to: 'rejected'    }],    methods: {        onResolve: function(state, data) {            //参数state - 当前状态示例;data - fsm,resolve(xxx)执行时传递过来的参数            data.successList.forEach(fn => fn());        },        onReject: function(satte, data) {            //参数state - 当前状态示例;data-fsm.reject(xxx)执行时传递过来的参数            data.failList.forEach(fn => fn())        }    }})function loadImg(src) {    const promise = new MyPromise(function(resolve, reject) {        let img = document.createElement('img');        img.onload = function() {            resolve(img)        }        img.onerror = function() {            reject(img)        }        img.src = src    })    return promise}let src = "https://www.xxxx.com/dsadfa/dafdafd.png";let result = loadImg(src);result.then(function() {    console.log('ok1')}, function() {    console.log('fail1')})result.then(function() {    console.log('ok2')}, function() {    console.log('fail2')})复制代码

设计模式验证

  • 将状态对象和主题对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则

其他设计模式

  • 不常用
  • 对应不到经典场景

创建型:

  • 原型模式

结构型:

  • 桥接模式
  • 组合模式
  • 享元模式

行为型

  • 策略模式
  • 模板方法模式
  • 职责链模式
  • 命令模式
  • 备忘录模式
  • 中介者模式
  • 访问者模式
  • 解释器模式

原型模式

  • clone自己,生成新对象(new开销比较大)
  • java默认有clone接口,不用自己实现

使用场景

Object.create

  • Object.create用到了原型模式的思想(虽然不是java中的clone)
//基于一个原型创建一个对象const prototype = {    getName: function() {        return this.first + '' + this.last;    },    say: function() {        console.log('hello')    }}//基于原型创建xvar x = Object.create(prototype)x.first = 'A'x.last = 'B'console.log(x.getName())x.say()//基于原型创建yvar y = Object.create(prototype)y.first = 'C'y.last = 'D'console.log(y.getName())y.say()复制代码

对比JS中的原型prototype

  • prototype 可以理解为ES6 class的一种底层原理
  • class是实现面向对象的基础,并不是服务于某个模式

桥接模式

  • 用于把抽象化与实现化解耦
  • 使得两者可以独立变化
  • 在一些业务中比较常用

应用场景

//普通实现class ColorShape {    yellowCircle() {        //...画黄圆    }    redCircle() {        //...画红圆    }    yellowTriangle() {        //...画黄三角形    }    redTriangle() {        //...画红三角形    }}//测试let cs = new ColorShape()cs.yellowCircle()cs.redCircle()cs.yellowTriangle()cs.redTriangle()复制代码

//桥接模式class Color {    constructor(color) {        this.color = color;    }}class Shape {    constructor(name, color) {        this.name = name;        this.color = color;    }    draw() {        //画图...    }}//测试代码let red = new Color("red")let yellow = new Color("yellow")let circle = new Shape('circle', red)circle.draw()let triangle = new Shape('triangle', yellow)triangle.draw()复制代码

颜色和图形自由组合,复杂性少很多,后面增加图形也很好处理

设计原则验证

  • 抽象和实现分离,解耦
  • 符合开放封闭原则

组合模式

  • 生成树形结构,表示“整体-部分”关系
  • 让整体和部分都具有一致的操作方式

示例:

应用场景

  • 虚拟DOM中的vnode是这种形式,但数据类型简单
  • 用JS实现一个菜单文件夹管理,不算经典应用,与业务相关

123

456

复制代码
{    tag: 'div',    attr: {        id: 'div1',        className: 'container'    },    children: [{        tag: 'p',        attr: {},        children: ['123']    }, {        tag: 'p',        attr: {},        children: ['456']    }]}复制代码
  • 整体和单个节点的操作是一致的
  • 整体和单个节点的数据结构也保持一致
  • 设计原则验证
  • 将整体和单个节点的操作抽象出来
  • 符合开放封闭原则

享元模式

  • 共享内存(主要考虑内存,而非效率)
  • 相同的数据,共享使用

JS中不用太多考虑内存开销

演示

//无限下拉列表,将事件代理到高层次节点上//如果都绑定到''标签,对内存开销大
< script > var div1 = document.getElementById('div1') div1.addEventListener('click', function(e) { var target = e.target if (e.nodeName === 'A' { alert(target.innerHtml) }) }) 复制代码

设计原则验证

  • 将相同的部分抽象出来
  • 符合开放封闭原则

策略模式

  • 不同策略分开处理
  • 避免出现大量if...else或者switch...case

演示

class User {    constructor(type) {        this.type = type    }    buy() {        if (this.type === 'oridinary') {            console.log('普通用户购买')        } else if (this.type === 'member') {            console.log('会员用户购买')        } else if (this.type === 'vip') {            console.log('vip用户购买')        }    }}//测试代码var u1 = new User('oridinary')u1.buy()var u2 = new User('member')u2.buy()var u3 = new User('vip')u3.buy()复制代码

改成下面这种形式:

class OrdinaryUser {    buy() {        console.log('普通用户购买')    }}class MemberUser {    buy() {        console.log('会员用户购买')    }}class VipUser {    buy() {        console.log('vip用户购买')    }}var u1 = new OrdinaryUser()u1.buy()var u2 = new MemberUser()u2.buy()var u3 = new VipUser()u3.buy()复制代码

设计原则验证

  • 不同策略,分开处理,而不是混合在一起
  • 符合开放封闭原则

模板方法模式和职责链模式

模板方法模式:

class Action {    handle() {        handle1();        handle2();        handle3();    }    handle1() {        console.log('1')    }    handle2() {        console.log('2')    }    handle3() {        console.log('3')    }}复制代码

职责链模式

  • 一步操作可能分为多个职责角色来完成
  • 把这些角色都分开,然后用一个链串起来
  • 将发起者和各个处理者进行隔离
演示:
//请假审批,需要组长审批、经理审批、最后总监审批class Action {    constructor(name) {        this.name = name;        this.nextAction = null    }    setNextAction(action) {        this.nextAction = action    }    handle() {        console.log(`${this.name} 审批`)        if (this.nextAction != null) {            this.nextAction.handle()        }    }}let a1 = new Action('组长')let a2 = new Action('经理')let a3 = new Action('总监')a1.setNextAction(a2)a2.setNextAction(a3)a1.handle()复制代码

应用场景

JS中的链式操作

  • 职责链和业务结合较多,JS中能联想到链式操作
  • jQuery的链式操作,Promise.then的链式操作

设计原则验证

  • 发起者和各个处理者进行隔离
  • 符合开放封闭原则

命令模式

  • 执行命令时,发布者和执行者分开
  • 中间加入命令对象,作为中转站

class Receiver {    exec() {        console.log('执行')    }}class Command {    constructor(receiver) {        this.receiver = receiver    }    cmd() {        console.log('触发命令')        this.receiver.exec()    }}class Invoke {    constructor(command) {        this.command = command;    }    invoke() {        console.log('开始')        this.command.cmd();    }}let soldier = new Receiver()let trumpeter = new Command(soldier)let general = new Invoke(trumpeter)general.invoke()复制代码

应用场景

  • 网页富文本编辑器操作,浏览器封装了一个命令对象
  • document.execCommand("bold")
  • document.execCommand("undo")

设计原则验证

  • 命令对象与执行对象分开,解耦
  • 符合开放封闭原则

备忘录模式

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销功能)

演示

一个编辑器

//备忘类class Memento {    constructor(content) {        this.content = content;    }    getContent() {        return this.content;    }}//备忘列表class CareTaker {    constructor() {        this.list = [];    }    add(memento) {        this.list.push(memento)    }    get(index) {        return this.list[index]    }}//编辑器class Editor {    constructor() {        this.content = null    }    setContent(content) {        this.content = content    }    getContent(content) {        return this.content    }    saveContentToMemento() {        return new Memento(this.content)    }    getContentFromMenmeto(memento) {        this.content = memento.getContent()    }}复制代码
//测试代码let editor = new Editor()let careTaker = new CareTaker()editor.setContent('111')editor.setContent('222')careTaker.add(editor.saveContentToMemento()) //存储备忘录editor.setContent('333')careTaker.add(editor.saveContentToMemento()) //存储备忘录editor.setContent('444')复制代码
console.log(editor.getContent())editor.getContentFromMenmeto(careTaker.get(1)) //撤销console.log(editor.getContent())editor.getContentFromMenmeto(careTaker.get(0)) //撤销console.log(editor.getContent())复制代码

设计原则验证

  • 状态对象与使用者分开,解耦
  • 符合开放封闭原则

中介者模式

演示

class Mediator {    constructor(a, b) {        this.a = a;        this.b = b;    }    setA() {        let number = this.b.number;        this.a.setNumber(number * 100);    }    setB() {        let number = this.a.number;        this.b.setNumber(number / 100);    }}class A {    constructor() {        this.number = 0;    }    setNumber(num, m) {        this.number = num;        if (m) {            m.setB()        }    }}class B {    constructor() {        this.number = 0;    }    setNumber(num, m) {        this.number = num;        if (m) {            m.setA();        }    }}let a = new A();let b = new B();let m = new Mediator(a, b);a.setNumber(100, m);console.log(a.number, b.number);b.setNumber(300, m);console.log(a.number, b.number)复制代码

设计原则验证

  • 将各关联对象通过中介者隔离
  • 符合开放封闭原则

访问者模式

  • 将数据操作和数据结构分离
  • 使用场景不多

解释器模式

  • 描述语言语法如何定义,如何解释和编译
  • 用于专业场景

综合应用

关于面试

  • 能说出课程重点讲解的设计模式即可

日常使用

  • 了解重点设计模式,要强制自己模仿、掌握
  • 非常用的设计模式,视业务场景选择性使用

转载地址:http://afkpa.baihongyu.com/

你可能感兴趣的文章
dplyr 数据操作 常用函数(4)
查看>>
A股实时股票行情,指数行情web API 使用方法
查看>>
大整数算法[04] 位操作
查看>>
C# Parsing 类实现的 PDF 文件分析器
查看>>
汇编学习(1)
查看>>
Google招聘 Lead Software Engineer
查看>>
Bzoj1026 windy数
查看>>
Java07
查看>>
mongodb基础知识_4
查看>>
ROP
查看>>
Windows常用网络命令技巧汇总 [转]
查看>>
【noi 2.6_8787】数的划分(DP){附【转】整数划分的解题方法}
查看>>
Win8 app判断网络连接状态
查看>>
CentOS 6.7下nginx SSL证书部署的方法分享
查看>>
菜鸟学SQLServer--数据文件和日志文件
查看>>
分享我积攒的测试相关的资料收集awesome-test
查看>>
1.2、solidworks入门1(零件建模、装配设计、工程图设计)
查看>>
SpringBoot Docker Mysql安装,Docker安装Mysql
查看>>
td中使用overflow:hidden; 无效解决方案
查看>>
Apache Flume 1.7.0 自定义输入输出
查看>>