设计模式系列二

2019-11-28  本文已影响0人  fanstastic

发布订阅模式又叫观察者模式,它定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会被通知。在js中,我们一般用事件模型来替代传统的发布订阅模式

  1. 我们可以订阅ajax的error,succ事件。或者在动画的每一帧完成之后做一些事情,那我们可以订阅一个事件,然后在动画的每一帧之后发布这个事件。
  2. 发布订阅模式可以取代对象之间硬编码的通知机制,一个对象不再显式的调用另一个对象的某个接口。当有新的订阅者出现时,发布者的代码不需要做任何修改,同样,发布者需要改变时,也不会影响之前的订阅者。
  1. 首先指定谁是发布者
  2. 给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者
  3. 最后发布消息时,发布者会遍历缓存列表,依次触发里面存放的订阅者回调函数
let sales = {} // 定义一个售楼处作为发布者
sales.list = [] // 给这个发布者一个缓存队列存放订阅者的回调函数
sales.listen = (fn) => { this.list.push(fn) } 增加订阅者
sales.trigger = (...args) => { for(){ fn.apply(this, args) } } // 执行发布函数

至此我们实现了一个最简单的发布订阅模式,但是还有问题,我们接收到了发布者发布的每一个消息,如果我们只想接收自己感兴趣的消息怎么办。

let salesOffices = {} 
salesOffices.list = {}
salesOffices.listen = (key, fn) => {
  if (!this.list[key]) {
    this.list[key] = []
  }
  this.list[key].push(fn)
}
salesOffices.trigger = (...args) => {
  const key = args.shift()
  if (!salesOffices.list[key] || !salesOffices.list[key].length) {
    return false
  }
  for (let i=0, fn;fn = fn[i++]) {
      fn.apply(this, ...args)
  }
}
let event = {
  clientList: {},
  listen: (key, fn) => {
    if (!this.clientList[key]) {
        this.clientList[key] = []
    }
    this.clientList.push(fn)
  },
  trigger: (...args) => {
    let key = this.args.shift()
    if (!this.clientList[key] || !this.clientList[key].length) { return false } 
    for (let i=0, fn;fn = this.clientList[key][i++])
  }
}
const installEvent = (obj) => {
  for (let name in event) { obj[name] = event[name] } 
}
event.remove = (key, fn) => {
  const fns = this.clientList[key]
  if (!fns) { return false } else if (!fn) {
    fns && fns.length = 0;
  } else {
    for (let i=fns.length-1;i>=0;i--) {
      if (fns[i] == fn) fns.splice(i, 1)
    }
  }
}
$.ajax('http://xxx.com', function(data) {
  login.trigger('loginSucc', data) // 执行的时候触发所有的监听函数
})
let header = (() => {
  login.listen('loginSucc', (data) => { header.set(data) })
  return {
    set: (data) => {}
  }
})()
let nav = (() => {
  login.listen('loginSucc', (data) => nav.set(data))
  return { set: (data) => {} }
})()
  var event = (() => {
  _listen = (key, fn, cache) => {
     if (!cache[key]) { cache[key] = [] }
      cache[key].push(fn)
  }
  _remove = (key, cache, fn) => {
      if (cache[key] ){
          if (fn) {
              for (var i=cache[key].length;i>=0;i--) {
                if (cache[key][i]==fn) cache[key].splice(i, 1)  
              }
          } else { cache[key].length = 0 }
      } 
    }
  _trigger = () => {}
 _create = (namespace) => {
    
  }
  return {
    
  }
})()
let Ryu = {
  attack: () => {},
  defense: () => {},
  jump: () => {},
  crouch: () => {}
}
let MacroCommand = function() {
  return {
    commandList: [],
    add: function(command) {
        this.commandList.push(command)
    },
    excute: function() {
        for (var i=0, command; command=this.commandList[i++]){
            command.excute()
        }  
    }
  }
}
public abstract class Component {
  public void add (Component child) {}
  public void remove(Component child) {}
}
public class Composite extends Component {
  public void add (Component child) {}
  public void remove (Component child) {}
}
public class Leaf extends Component {
  public void 
}

js的对象天生具有多态性

var Folder = function(name) {
  this.name = name
  this.files = []
}
Folder.prototype.add = function (file) {
   this.files.push(file)
}
Folder.prototype.scan = () => {
  conso;le.log(sao miao)
  for (let i = 0, file; file = this.files[i++]) { file.scan() }
}
  1. 组合模式不是父子关系
  2. 对叶操作的一致性
    组合模式除了要求组合对象和叶对象拥有相同的接口之外,就是ui一组叶对象的操作必须有一致性
let Folder = function(name) {
  this.name = name
  this.parent = null
  this.files=[]
}
Folder.prototype.add = (file) => {
  file.parent = this
  this.files.push(file)
}

Folder.prototype.scan = () => {
  for (let i=0, file;file = this.files[i++]) {
    file.scan()
  }
}

Folder.prototype.remove = () => {
  if (!this.parent) { return }
  for (let files = this.parent.files, l=files.length-1;l>=0;l--) {
    if (files[l] == this) { files.splice(l,1) }
  }
}
let Beverage = function() {}
Beverage.prototype.boilWater = function() {}
Beverage.prototype.brew = function(){}
Beverage.prototype.init = function() {
  this.boilWater()
  this.brew()
}
let Coffee = function() {}
Coffee.prototype = new Beverage()
Coffee.prototype.brew = function() {}
Coffee.prototype.pourInCup = function() {}
Coffee.prototype.addCondiments = function() {}
let coffee = new Coffee()
coffee.init()

当调用coffee对象的init方法时,由于coffee对象和Coffee构造器的原型都没有init方法,所以该请求会顺着原型链,被委托给Beverage原型上的init方法。
而init方法中已经规定好了泡饮料的顺序。
所以其中Beverage.prototype.init被称为模板方法,该方法中封装了子类的算法框架,它作为一个算法的模板,指导子类以何种顺序去执行哪些方法。

let id=0;
window.startUpload = (uploadType, files) => {
  for (let i=0,file;file=files[i++];) {
    let uploadObj = new Upload(uploadType, file.fileName, file.fileSize)
    uploadObj.init(id++)
  }
}
let Upload = function(uploadType, fileName, fileSize) {
   this.uploadType = uploadType
    this.fileName = fileName
    this.fileSize = fileSize
    this.dom = null
}
Upload.prototype.init = (id) => {
  let that = this
  this.id = id
  this.dom = document.createElement('div')
  this.dom.innerHTML = '...'
  this.dom.querySelector('.delFile').onClick = () => {
    that.delFile()
  }
  document.body.appendChild(this.dom)
}
Upload.prototype.delFile = () => {
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom)
  }
 
  if (window.confirm('xxx')) { return this.dom.parentNode.removeChild(this.dom) }
}
  1. 剥离外部状态
let Upload = function(uploadType) {
  this.uploadType = uploadType
}
Upload.prototype.delFile = function(id) {
  UploadManager.setExternalState(id, this) // 把当前id对应的对象的fileSize值设置到共享对象中
  ...
}

在开始读取文件前,需要读取文件的实际大小,而文件的实际大小被存储在
setExternalState当中,所以需要通过setExternalState方法给共享对象设置正确的fileSize

接下来定义一个工厂来创建upload对象,如果某种内部状态对应的共享对象被创建过,那么直接返回这个对象,否则创建一个新对象:

let UploadFac = (function(){
  let obj = {}
  return {
    create: function(uploadType) {
        if (obj[uploadType]) { return  obj[uploadType]}
        return obj[uploadType] = new Upload(uploadType)
    }
  }
})()
let uploadmanage = (() => {
  let uploaddatabase = {}
  return {
    add: (id, uploadType, fileName, fileSize) => {
       let weightObj = UploadFac.create(uploadType)
       // 创建一个dom元素,绑定删除事件,再push到页面
        let dom = ...
       document.body.appendChild(dom)
        uploaddatabase[id] = {
          fileName,
          fileSize,
          dom
        },
      setExternalState: function(id, obj) {
        给obj共享对象设置fileSize
        let uploadData = uploaddatabase[id]
        
      }
    }
  }
})()

假设我们在开发一些地图应用,在搜索我家附近的时候,页面出现了2个小气泡。当我再搜索时,页面出现了6个小气泡。按照对象池的思想,第二次搜索开始前,我们只需要创建4个对象。
先定义一个获取小气泡节点的工厂,作为对象池的数组称为私有属性被包含在工厂闭包里,这个工厂有两个方法,create表示获取一个div节点,recover表示回收一个div节点。

上一篇 下一篇

猜你喜欢

热点阅读