Javascript的设计模式之从设计到模式(重要设计模式)

一、五大设计原则

  • S - 单一职责原则(做好一件事)
  • O - 开放封闭原则(可扩展,不修改)
  • L - 李氏置换原则(子类覆盖父类)
  • I - 接口独立原则(单一独立)
  • D - 依赖倒置原则(依赖抽象)

二、23种设计模式

  • 创建型
  1. 工厂模式 - 工厂方法模式(创建和生成)√
  2. 工厂模式 - 抽象工厂模式(创建和生成)√
  3. 工厂模式 - 建造者模式(创建和生成)√
  4. 单例模式(只能出现一个对象)√
  5. 原型模式 (通过拷贝一个现有对象生成新的对象)
  • 组合型
  1. 适配器模式 √
  2. 装饰器模式 √
  3. 代理模式 √
  4. 外观模式 √
  5. 桥接模式
  6. 组合模式
  7. 享元模式
  • 行为型
  1. 策略模式
  2. 模板方法模式
  3. 观察者模式 √
  4. 迭代器模式 √
  5. 职责连模式
  6. 命令模式
  7. 备忘录模式
  8. 状态模式 √
  9. 访问者模式
  10. 中介者模式
  11. 解释器模式

(一)工厂模式

  • 将new操作单独封装
    在这里插入图片描述
class Product {
  constructor(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
  // ...methods
}
class Creator {
  create(name) {
    return new Product(name)
  }
}
const creator = new Creator()
const p1 = creator.create('p1')
p1.getName()

设计原则:
构造函数和创建者分离
开放封闭原则

(四)单例模式

  • 只能有一个实例
class SingleObject {
  login() {
    console.log('login...')
  }
}
SingleObject.getInstance = (function() {
  let instance = null
  return function () {
    if (!instance) {
      instance = new SingleObject()
    }
    return instance
  }
})()
const obj1 = SingleObject.getInstance()
const obj2 = SingleObject.getInstance()
console.log('obj1 === obj2', obj1 === obj2)

注:js中没有private类型做限制,只能通过注释来告诉使用者通过SingleObject.getInstance()来创建实例,多次创建new SingleObject(),也不会报错

设计原则:
单一职责原则
开放封闭原则

(六)适配器模式

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

场景:旧接口封装、vue computed
在这里插入图片描述

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

设计原则:
将旧接口与使用者分离
开放封闭原则

(七)装饰器模式

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

场景:es7装饰器(@babel/plugin-proposal-decorators),core-decorator(第三方开源库)
在这里插入图片描述

// 装饰器模式(es6)
class Circle {
  draw() {
    console.log('画一个圆')
  }
}
class Decorator {
  constructor(circle) {
    this.circle = circle
  }
  draw() {
    this.circle.draw()
    this.setRedBorder()
  }
  setRedBorder() {
    console.log('设置红色边框')
  }
}
const circle = new Circle()
circle.draw()
console.log('------分割线--------')
const dec = new Decorator(circle)
dec.draw()

// 类装饰器(es7)
@decorator
class Demo {}
function decorator(target) {
  target.isDec = true
}
console.log(Demo.isDec)

// 类装饰器(es7) - 传参
@decorator2(false)
class Demo2 {}
function decorator2(isDec) {
  return function(target) {
    target.isDec = isDec
  }
}
console.log(Demo2.isDec)

// 类装饰器(es7) - mixins
const Foo = {
  foo() {
    console.log('foo')
  }
}
function mixins(...list) {
  return function(target) {
    Object.assign(target.prototype, ...list)
  }
}
@mixins(Foo)
class Demo3 {}
const demo3 = new Demo3()
demo3.foo()

// 方法装饰器 - readonly
function readonly(target, name, descriptor) {
  descriptor.writable = false
  return descriptor
}
class Person {
  constructor() {
    this._name = '张三'
  }

  @readonly
  name() {
    return this._name
  }
}
const person = new Person()
console.log(person.name())
person.name = function() {
  return '李四'
}
console.log(person.name()) // 获取到的还是 张三

// 方法装饰器 - log
function log(target, name, descriptor) {
  const oldValue = descriptor.value
  decorator.value = function() {
  	console.log(`log: ${name}`, arguments)
    return oldValue.apply(this, arguments)
  }
  return decorator
}
class Math {
  @log
  add(a, b) {
    return a + b
  }
}
const math = new Math()
const result = math.add(3, 8)
console.log(result)

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

(八)代理模式

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

场景:网页事件代理、$.proxy、ES6 Proxy
在这里插入图片描述

// 代理模式(es6)
class RealImg {
  constructor(fileName) {
    this.fileName = fileName
    this.loadFromDisk()
  }
  loadFromDisk() {
    console.log('load...', this.fileName)
  }
  display() {
    console.log('display...', this.fileName)
  }
}
class ProxyImg {
  constructor(fileName) {
    this.realImg = new RealImg(fileName)
  }
  display() {
    this.realImg.display()
  }
}
const proxyImg = new ProxyImg('1.png')
proxyImg.display()

// 代理模式($.proxy)
$('#app').click(function() {
  var fn = function() {
    $(this).css('background', '#f00')
  }
  setTimeout($.proxy(fn, this), 1000)
})

// 代理模式(ES6 proxy)
const star = {
  name: '张三',
  phone: '13000000000'
}
const agent = new Proxy(star, {
  set(target, key, value, receiver) {
    if (key === 'price') {
      if (value < 100000) {
        target[key] = 100000
      } else {
        target[key] = value
      }
      return true
    }
  },
  get(target, key, receiver) {
    if (key === 'phone') {
      return '14000000000'
    }
    if (key === 'price' && !target[key]) {
      return 120000
    }
    return target[key]
  }
})

console.log('name', agent.name)
console.log('phone', agent.phone)
console.log('price砍价前', agent.price)
agent.price = 90000
console.log('price砍价后', agent.price)

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

代理模式 vs 适配器模式

  • 适配器模式:提供不同的接口
  • 代理模式:提供一模一样的接口

代理模式 vs 装饰器模式

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

(九)外观模式

  • 常用于处理需要兼容的情况

(十五)观察者模式
注:使用最多也是最重要的一种模式

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

场景:网页事件绑定、Promise、jQuery callbacks、nodejs自定义事件(流的方式读取/写入文件、逐行读取文件流)等等
其他场景:nodejs中处理http请求、多进程通,vue和react组件生命周期,vue watch等等

// 观察者模式
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers() {
    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()}`)
  }
}

const s = new Subject()
const o1 = new Observer('o1', s)
const o2 = new Observer('o2', s)
const o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)

// 观察者模式(jQuery Callback)
const callback = $.Callbacks()
callback.add(function (info) {
  console.log('fn1', info)
})
callback.add(function (info) {
  console.log('fn2', info)
})
callback.add(function (info) {
  console.log('fn3', info)
})
callback.fire('第一次触发')

// 观察者模式(node自定义事件,也可以被继承)
const EventEmitter = require('events').EventEmitter
const emitter = new EventEmitter()
// 监听 some 事件
emitter.on('some', data => {
  console.log('fn1', data)
})
// 监听 some 事件
emitter.on('some', data => {
  console.log('fn2', data)
})
// 触发 some 事件
emitter.emit('some', '触发')

// 观察者模式(node自定义事件,Stream)
const fs = require('fs')
const readStream = fs.createReadStream('./data/file.text')
let length = 0
readStream.on('data', data => {
  length += data.toString().length
  console.log('length', length)
})
readStream.on('data', data => {
  console.log('len', data.toString().length)
})
readStream.on('end', data => {
  console.log('length', length)
})

// 观察者模式(node自定义事件,readline)
const readline = require('readline')
const rl = readline.createInterface({
  input: readStream
})
let lineNum = 0
rl.on('line', line => {
  lineNum++
})
rl.on('close', () => {
  console.log('lineNum', lineNum)
})

设计原则:
将目标和观察者分离,不是主动触发而是被动监听,两者解耦
符合开放封闭原则

(十六)迭代器模式

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

场景:jQuery each、ES6 Iterator

ES6 Iterator为何存在:
1、ES6语法中,有序集合的数据已经有很多
2、Array、Map、Set、String、TypedArray、arguments、NodeList
3、需要有一个统一的遍历接口来遍历所有数据类型
4、(注意,object不是有序集合,可以 用Map代替)
ES6 Iterator是什么?
1、以上数据类型,都有 [Symbol.iterator] 属性
2、属性值是函数,执行函数返回一个迭代器
3、这个迭代器就有 next 方法可顺序迭代子元素
4、可运行 Array.prototype[Symbol.iterator] 来测试
ES6 Iterator 与 Generator
1、Iterator 的价值不限于上述几个类型的遍历
2、还有 Generator 函数的使用
3、即只要返回的数据符合Iterator接口的要求
4、即可使用Iterator语法,这就是迭代器模式

// 迭代器模式(jQuery each)
const arr = [1, 2, 3, 4]
const nodeList = document.getElementsByTagName('p')
const $p = $('p')
function each(data) {
  const $data = $(data) // 生成迭代器
  $data.each(function (key, item) {
    console.log(key, item)
  })
}
each(arr)
each(nodeList)
each($p)

// 迭代器模式(es6 自定义)
class Iterator {
  constructor(container) {
    this.list = container.list
    this.index = 0
  }
  next() {
    if (this.hasNext) {
      return this.list[this.index++]
    }
    return null
  }
  hasNext() {
    if (this.index >= this.list.length) {
      return false
    }
    return true
  }
}
class Container {
  constructor(list) {
    this.list = list
  }
  getIterator() {
    return new Iterator(this)
  }
}

const container = new Container([1, 2, 3, 4, 5, 6])
const iterator = container.getIterator()
while (iterator.hasNext()) {
  console.log(iterator.next())
}

// 迭代器模式(es6 Iterator)
const it = {
  each: function (data) {
    const iterator = data[Symbol.iterator]()
    let item = { done: false }
    while (!item.done) {
      item = iterator.next()
      if (!item.done) {
        console.log('value', item.value)
      }
    }
  }
}
it.each('abcdef')
// for...of 是 iterator 的语法糖,我们不需要重复的去封装 each 方法

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

(二十)状态模式

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

场景:有限状态机(javascript-state-machine)、Promise

// 状态模式
class State {
  constructor(color) {
    this.color = color
  }
  handle(context) {
    console.log('turn to', this.color)
    context.setState(this)
  }
}
class Context {
  constructor() {
    this.state = null
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
  }
}

const context = new Context()
const green = new State('green')
const yellow = new State('yellow')
const 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())

// 状态模式(javascript-state-machine)
import StateMachine from 'javascript-state-machine'
const fms = new StateMachine({
  init: '收藏', // 初始状态
  transitions: [
    {
      name: 'collect', // 方法名
      from: '收藏',
      to: '取消收藏'
    },
    {
      name: 'cancelCollect', // 方法名
      from: '取消收藏',
      to: '收藏'
    }
  ],
  methods: {
    // 收藏,与 name 名一致,前面加on,驼峰命名
    onCollect() {
      console.log('收藏')
    },
    // 取消收藏
    onCancelCollect() {
      console.log('取消收藏')
    }
  }
})
const $btn = $('#btn')
// 更新文案
function updateBtnText() {
  $btn.text(fms.state)
}
// 初始化文案
updateBtnText()
// 点击事件
$btn.on('click', function () {
  if (fms.is('收藏')) {
    fms.collect()
  } else if (fms.is('取消收藏')) {
    fms.cancelCollect()
  }
  updateBtnText()
})

// 状态模式(Promise)
const promiseFms = new StateMachine({
  init: 'pending',
  transitions: [
    {
      name: 'resolve',
      form: 'pending',
      to: 'fulfilled'
    },
    {
      name: 'reject',
      form: 'pending',
      to: 'rejected'
    }
  ],
  methods: {
    onResolve(state, data) {
      data.resolveCallbacks.forEach(fn => fn())
    },
    onReject(state, data) {
      data.rejectCallbacks.forEach(fn => fn())
    }
  }
})
class MyPromise {
  constructor(fn) {
    this.value = undefined
    this.reason = undefined
    this.resolveCallbacks = []
    this.rejectCallbacks = []
    const handleResolve = (value) => {
      promiseFms.resolve(this)
    }
    const handleReject = (reason) => {
      promiseFms.reject(this)
    }
    fn(handleResolve, handleReject)
  }
  then(fn1, fn2) {
    this.resolveCallbacks.push(fn1)
    this.rejectCallbacks.push(fn2)
  }
}
const myPromise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(true)
  }, 1000)
})
myPromise.then(res => {
  console.log(1)
})
myPromise.then(res => {
  console.log(2)
})

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值