3

设计原则(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 = <div>
    <img src="avater.png" className="profile"/>
    <h3>{[user.firstName,user.lastName].join('')}</h3>
    </div>;
编译完之后:
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: '<div>I am async!</div>'
        })
    }, 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
<div id="example">
    <p>Original message:"{{message}}"</p>
    <p>Computed reversed message:"{{reversedMessage}}"</p>
</div>

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装饰器
@testDec
class Demo {
    //...
}

function testDec(target) {
    target.isDec = true;
}
alert(Demo.isDec);
装饰器原理
@decorator
class 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)
应用场景
  • 网页事件绑定

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

<button id="btn1">btn</button>

<script>
    $('#btn1').click(function () {
        console.log(1)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
</script>
  • 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').EventEmitter
const 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 emit

class 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') //读取文件的stream

var length = 0
readStream.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 = 0
rl.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.js
var 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.js
process.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 ( 
            <div>
                <header title = "登录" history = { this.props.history } ></header> 
            </div >
        )
    }
    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演示一下
<p>jQuery each</p>
<p>jQuery each</p>
<p>jQuery each</p>
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<length;i++){
    console.log(nodeList[i])
}

$p.each(function(key,p){
    console.log(key,p)
})

//顺序遍历有序集合
//使用者不必知道集合的内部结构
function each(data) {
    var $data = $(data) //生成迭代器
    $data.each(function(key, value) {
        console.log(key,value)
    })
}

each(arr)
each(nodeList)
each($p)

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')
    }
}


//基于原型创建x
var x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
console.log(x.getName())
x.say()

//基于原型创建y
var 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实现一个菜单文件夹管理,不算经典应用,与业务相关
<div id="div1" class="container">
    <p>123</p>
    <p>456</p>
</div> 
{
    tag: 'div',
    attr: {
        id: 'div1',
        className: 'container'
    },
    children: [{
        tag: 'p',
        attr: {},
        children: ['123']
    }, {
        tag: 'p',
        attr: {},
        children: ['456']
    }]
}
  • 整体和单个节点的操作是一致的
  • 整体和单个节点的数据结构也保持一致
  • 设计原则验证
  • 将整体和单个节点的操作抽象出来
  • 符合开放封闭原则

享元模式

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

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

演示
//无限下拉列表,将事件代理到高层次节点上
//如果都绑定到'<a>'标签,对内存开销大

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <!--无限下拉列表-->
</div>

< script >
    var div1 = document.getElementById('div1')
    div1.addEventListener('click', function(e) {
        var target = e.target
        if (e.nodeName === 'A' {
            alert(target.innerHtml)
        })
    }) 
</script>
设计原则验证
  • 将相同的部分抽象出来
  • 符合开放封闭原则

策略模式

  • 不同策略分开处理
  • 避免出现大量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)
设计原则验证
  • 将各关联对象通过中介者隔离
  • 符合开放封闭原则

访问者模式

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

解释器模式

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

综合应用

关于面试
  • 能说出课程重点讲解的设计模式即可
日常使用
  • 了解重点设计模式,要强制自己模仿、掌握
  • 非常用的设计模式,视业务场景选择性使用

kinshan
1.7k 声望1.9k 粉丝