amenzai / myDiary

Record what you do every day
4 stars 0 forks source link

设计模式笔记 #55

Closed amenzai closed 5 years ago

amenzai commented 6 years ago

JS 设计模式

面向对象

搭建开发环境

npm / webapck / webpack-dev-server / babel babel-core babel-loader babel-polyfill babel-preset-env

什么是面向对象

概念 类(对象的模板)、对象(实例)

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
  speak() {
    console.log(`My name is ${this.name}, age ${this.age}`)
  }
}

let zhang = new People('zhang', 20)
zhang.eat()
zhang.speak()

let wang = new People('wang', 21)
wang.eat()
wang.speak()

三要素:继承封装多态

// 继承
class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
  speak() {
    console.log(`My name is ${this.name}, age ${this.age}`)
  }
}

class Student extends People {
  constructor(name, age, number) {
    super(name, age)
    this.number = number
  }
  study() {
    console.log(`${this.name} study`)
  }
}

let xiaoming = new Student('xiaoming', 10, 'A1')
xiaoming.study()
console.log(xiaoming.number)
let xiaohong = new Student('xiaohong', 11, 'A2')
xiaohong.study()

// 多态
class People {
  constructor(name) {
    this.name = name
  }
  saySomething() {

  }
}
class A extends People {
  constructor(name) {
    super(name)
  }
  saySomething() {
    console.log('I am A')
  }
}
class B extends People {
  constructor(name) {
    super(name)
  }
  saySomething() {
    console.log('I am B')
  }
}
let a = new A('a')
a.saySomething()
let b = new B('b')
b.saySomething()

JS 应用举例

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(node) {

  }
  addClass(name) {

  }
  html(data) {

    }
    // 此处省略若干 API
}
window.$ = function(selector) {
  return new jQuery(selector)
}

面向对象的意义

UML类图

统一建模语言。

关系: image

设计原则

设计模式

从设计到模式

两个面试题

01 image

class Car {
  constructor(number, name) {
    this.number = number
    this.name = name
  }
}
class Kuaiche extends Car {
  constructor(number, name) {
    super(number, name)
    this.price = 1
  }
}
class Zhuanche extends Car {
  constructor(number, name) {
    super(number, name)
    this.price = 2
  }
}

class Trip {
  constructor(car) {
    this.car = car
  }
  start() {
    console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.price}`)
  }
  end() {
    console.log('行程结束,价格: ' + (this.car.price * 5))
  }
}

let car = new Kuaiche(100, '桑塔纳')
let trip = new Trip(car)
trip.start()
trip.end()

02

image

// 车
class Car {
  constructor(num) {
    this.num = num
  }
}

// 入口摄像头
class Camera {
  shot(car) {
    return {
      num: car.num,
      inTime: Date.now()
    }
  }
}

// 出口显示器
class Screen {
  show(car, inTime) {
    console.log('车牌号', car.num)
    console.log('停车时间', Date.now() - inTime)
  }
}

// 停车场
class Park {
  constructor(floors) {
    this.floors = floors || []
    this.camera = new Camera()
    this.screen = new Screen()
    this.carList = {}
  } in (car) {
    // 获取摄像头的信息:号码 时间
    const info = this.camera.shot(car)
      // 停到某个车位
    const i = parseInt(Math.random() * 100 % 100)
    const place = this.floors[0].places[i]
    place.in()
    info.place = place
      // 记录信息
    this.carList[car.num] = info
  }
  out(car) {
    // 获取信息
    const info = this.carList[car.num]
    const place = info.place
    place.out()

    // 显示时间
    this.screen.show(car, info.inTime)

    // 删除信息存储
    delete this.carList[car.num]
  }
  emptyNum() {
    return this.floors.map(floor => {
      return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个车位`
    }).join('\n')
  }
}

// 层
class Floor {
  constructor(index, places) {
    this.index = index
    this.places = places || []
  }
  emptyPlaceNum() {
    let num = 0
    this.places.forEach(p => {
      if (p.empty) {
        num = num + 1
      }
    })
    return num
  }
}

// 车位
class Place {
  constructor() {
    this.empty = true
  } in () {
    this.empty = false
  }
  out() {
    this.empty = true
  }
}

// 测试代码------------------------------
// 初始化停车场
const floors = []
for (let i = 0; i < 3; i++) {
  const places = []
  for (let j = 0; j < 100; j++) {
    places[j] = new Place()
  }
  floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)

// 初始化车辆
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')

console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)

console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)

工厂模式

介绍:

class Product {
  constructor(name) {
    this.name = name
  }
  init() {

  }
  fun1() {

  }
}
class Creator {
  create(name) {
    return new Product(name)
  }
}
// test
let creator = new Creator()
let p1 = creator.create('p1')
p1.init()

场景:

学习经典库的方法:

设计原则验证:

登陆框、购物车...

说明:

场景:

jQuery

// jQuery 只有一个 $
if (window.jQuery != null) {
  return window.jQuery
} else {
  // init
}
class SingleObject {
  login() {
    console.log('login...')
  }
}
SingleObject.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new SingleObject();
    }
    return instance
  }
})()

// 测试
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2)
class LoginForm {
  constructor() {
    this.state = 'hide'
  }
  show() {
    if (this.state === 'show') {
      console.log('已经显示')
      return
    }
    this.state = 'show'
    console.log('登录框已显示')
  }
  hide() {
    if (this.state === 'hide') {
      console.log('已经隐藏')
      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()
  // login1.hide()

// 另一个页面中调用登录框
let login2 = LoginForm.getInstance()
login2.show()

// 两者是否相等
console.log('login1 === login2', login1 === login2)

设计原则验证:

适配器模式

插头,转接口...

介绍:

UML类图:

image

演示:

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: 'getData',
  type: 'post',
  dataType: 'json',
  data: {
    id: "123"
  }
}).done(function(){})
// 历史代码
// $.ajax({...})

// 做一层适配器
var $ = {
  ajax: function(options) {
    return ajax(options)
  }
}

设计原则验证:

装饰器模式

介绍:

例子:手机壳

UML类图:

image

image

ES7 装饰器 babel插件:babel-plugin-transform-decorators-legacy 库:core-decorators(常用装饰器已经写好了,直接拿来用)

// 装饰类
// @testable
// class MyTestableClass {
//   // ...
// }

// function testable(target) {
//   target.isTestable = true;
// }

// console.log(MyTestableClass.isTestable) // true

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

const Foo = {
  foo() { console.log('foo') }
}

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

// 装饰方法
function readonly(target, name, descriptor) {
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

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 是只读属性

// another
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;
}

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

const math = new Math();
const result = math.add(2, 4);
console.log('result', result);
// core-decorators

// import { readonly } from 'core-decorators'

// class Person {
//     @readonly
//     name() {
//         return 'zhang'
//     }
// }

// let p = new Person()
// console.log(p.name())
// // p.name = function () { /*...*/ }  // 此处会报错

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.

设计原则验证:

代理模式

介绍:

例子:

UML类图:

image

演示:

image

场景:

// 明星
let star = {
  name: '张XX',
  age: 25,
  phone: '13910733521'
}

// 经纪人
let agent = new Proxy(star, {
  get: function(target, key) {
    if (key === 'phone') {
      // 返回经纪人自己的手机号
      return '18611112222'
    }
    if (key === 'price') {
      // 明星不报价,经纪人报价
      return 120000
    }
    return target[key]
  },
  set: function(target, key, val) {
    if (key === 'customPrice') {
      if (val < 100000) {
        // 最低 10w
        throw new Error('价格太低')
      } else {
        target[key] = val
        return true
      }
    }
  }
})

// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)

// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
  // agent.customPrice = 90000  // 报错:价格太低
console.log('customPrice', agent.customPrice)

访问代理,接口地址是不会变的。 设计原则验证:

代理模式 VS 适配器模式

代理模式 VS 装饰器模式

image

观察者模式

介绍:

UML类图:

image

例子:

// 主题,接收状态变化,触发每个观察者
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  attach(observer) {
    this.observers.push(observer)
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
}

// 观察者,等待被触发
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 s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)

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

场景:

// jquery callbacks
var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function(info) {
  console.log('fn1', info)
})
callbacks.add(function(info) {
  console.log('fn2', info)
})
callbacks.add(function(info) {
  console.log('fn3', info)
})
callbacks.fire('gogogo') // 发布
callbacks.fire('fire')
// nodejs 自定义事件
const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', () => {
  // 监听 some 事件
  console.log('some event is occured 1')
})
emitter1.on('some', () => {
    // 监听 some 事件
    console.log('some event is occured 2')
  })
  // 触发 some 事件
emitter1.emit('some')

const emitter = new EventEmitter()
emitter.on('sbowName', name => {
  console.log('event occured ', name)
})
emitter.emit('sbowName', 'zhangsan') // emit 时候可以传递参数过去

---

// 任何构造函数都可以继承 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)

node 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('end', function() {
  console.log(length)
})

// 监听一行一行的数据(监听文件有多少行)
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)
});

其他场景:

// node 处理 http 请求
var http = require('http')

function serverCallback(req, res) {
  var method = req.method.toLowerCase() // 获取请求的方法
  if (method === 'get') {}
  if (method === 'post') {
    // 接收 post 请求的内容
    var data = ''
    req.on('data', function(chunk) {
      // “一点一点”接收内容
      console.log('chunk', chunk.toString())
      data += chunk.toString()
    })
    req.on('end', function() {
      // 接收完毕,将内容输出
      console.log('end')
      res.writeHead(200, { 'Content-type': 'text/html' })
      res.write(data)
      res.end()
    })
  }

}
http.createServer(serverCallback).listen(8081) // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')

设计原则验证:

UML类图:

image

使用 jQuery 示例:

var arr = [1, 2, 3]
var nodeList = document.getElementsByTagName('p')
var $p = $('p')

// 要对这三个变量进行遍历,需要写三个遍历方法
// 第一
arr.forEach(function(item) {
    console.log(item)
  })
  // 第二
var i, length = nodeList.length
for (i = 0; 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, p) {
    console.log(key, p)
  })
}
each(arr)
each(nodeList)
each($p)
class Iterator {
  constructor(conatiner) {
    this.list = conatiner.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)
  }
}

// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while (iterator.hasNext()) {
  console.log(iterator.next())
}

使用场景:

为何 ES6 Iterator 存在?

ES6 Iterator 是什么?

ES6 Iterator 示例:

// 手动实现遍历器
function each(data) {
  // 生成遍历器
  let iterator = data[Symbol.iterator]()

  // console.log(iterator.next())  // 有数据时返回 {value: 1, done: false}
  // console.log(iterator.next())
  // console.log(iterator.next())
  // console.log(iterator.next())
  // console.log(iterator.next())  // 没有数据时返回 {value: undefined, done: true}

  let item = { done: false }
  while (!item.done) {
    item = iterator.next()
    if (!item.done) {
      console.log(item.value)
    }
  }
}

// 数组已经部署了 Iterator 接口
// for of 可以遍历 所有部署了 Iterator 接口的数据结构
function each(data) {
  for (let item of data) {
    console.log(item)
  }
}

let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)

each(arr)
each(nodeList)
each(m)

ES6 Iterator 与 Generator

// function* helloWorldGenerator() {
//   yield 'hello';
//   yield 'world';
//   return 'ending';
// }

// var hw = helloWorldGenerator();
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}

设计原则验证:

状态模式

介绍:

UML类图:

image

// 状态
class State {
  constructor(color) {
    this.color = color
  }
  handle(context) {
    console.log(`turn to ${this.color} light`)
    context.setState(this)
  }
}
// 主体
class Context {
  constructor() {
    this.state = null
  }
  setState(state) {
    this.state = state
  }
  getState() {
    return this.state
  }
}

// 测试代码
let context = new Context()

let greed = new State('greed')
let yellow = new State('yellow')
let red = new State('red')

// 绿灯亮了
greed.handle(context)
console.log(context.getState())
  // 黄灯亮了
yellow.handle(context)
console.log(context.getState())
  // 红灯亮了
red.handle(context)
console.log(context.getState())

场景:

<script src="./03-javascript-state-machine.js"></script>
// 状态机模型
var fsm = new StateMachine({
  init: '收藏', // 初始状态,待收藏
  transitions: [{
    name: 'doStore',
    from: '收藏',
    to: '取消收藏'
  }, {
    name: 'deleteStore',
    from: '取消收藏',
    to: '收藏'
  }],
  methods: {
    // 监听执行收藏
    onDoStore: function() {
      console.log('收藏成功')
      updateText()
    },
    // 监听取消收藏
    onDeleteStore: function() {
      console.log('已取消收藏')
      updateText()
    }
  }
})

var $btn = $('#btn')

// 点击事件
$btn.click(function() {
  if (fsm.is('收藏')) {
    fsm.doStore()
  } else {
    fsm.deleteStore()
  }
})

// 更新文案
function updateText() {
  $btn.text(fsm.state)
}

// 初始化文案
updateText()
// promise 简单实现
// 模型
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(state, data) {
      // 参数:state - 当前状态示例; data - fsm.reject(xxx) 执行时传递过来的参数
      data.failList.forEach(fn => fn())
    }
  }
})

// 定义 Promise
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)
  }
}

// 测试代码
function loadImg(src) {
  const promise = new MyPromise(function(resolve, reject) {
    var img = document.createElement('img')
    img.onload = function() {
      resolve(img)
    }
    img.onerror = function() {
      reject()
    }
    img.src = src
  })
  return promise
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
console.log(result)

result.then(function(img) {
  console.log('success 1')
}, function() {
  console.log('failed 1')
})
result.then(function(img) {
  console.log('success 2')
}, function() {
  console.log('failed 2')
})

设计原则验证:

其他设计模式

原型模式

桥接模式

image image

组合模式

image

享元模式(共享元数据)

例子:事件代理(ul>li)

策略模式

模板方法模式

class Action {
  handle() {
    this.handle1()
    this.handle2()
    this.handle3()
  }
  handle1() {

  }
  handle2() {

  }
  handle3() {

  }
}

职责链模式

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()
    }
  }
}
// test
let a1 = new Action('组长')
let a2 = new Action('主管')
let a2 = new Action('经理')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

命令模式

image

备忘录模式

编辑器案例:备忘列表存储编辑器中设置的每一项设置的内容

中介者模式

联想现实中介。1 image image

访问者模式&解释器模式

关于面试

重点设计模式要深入理解 非常用的,视业务场景需要选择

综合应用

介绍:

UML类图: image