美文网首页
设计模式系列三

设计模式系列三

作者: fanstastic | 来源:发表于2019-11-30 18:24 被阅读0次
  • 职责链模式
    职责链的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。一系列可能会处理请求的对象被连成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点。职责链模式的最大的优点是:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
  1. 灵活可拆分的职责链节点
    我们约定,如果某个节点不能处理请求,则返回一个特定的字符串 'nextSuccessor'来表示该请求需要继续传递:
let order500 = function(orderType, pay, stock) {
  if (orderType == 1 && pay == true) {
    
  } else return 'nextSuccessor'
}
let order200 = function(orderType, pay, stock) {
  if (orderType == 2 && pay == true) {
    
  } else return 'nextSuccessor'
}
let orderNormal = function(orderType, pay, stock) {
  if (stock > 0) {
    
  } else {}
}

接下来需要把函数包装进职责链节点,我们定义一个构造函数Chain,在new Chain的时候传递的参数即为需要被包装的函数,同时它还拥有一个实例属性this.successor,表示在链中的下一个节点。

let Chain = function(fn) {
   this.fn = fn
   this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor) {
  return this.successor = successor
}
Chain.prototype.passRequest = function() {
  let ret = this.fn.apply(this, arguments)
  
  return ret
}
  • 异步的职责链
    每个节点函数同步返回一个特定的值'nextSuccessor', 来表示是否把请求传递到下一个节点。异步请求返回的结果才能决定是否继续在职责链中passRequest。

  • 中介者模式

function Player(name, teamColor) {
  this.name = name
  this.teamColor = teamColor
  this.status = 'alive'
}
Player.prototype.win = function() {}
Player.prototype.loose = function() {}
Player.prototype.die = function() {
  this.state = 'die'
  playerDirector.receiveMessage('playerDead', this)
}
Player.prototype.loose = function() {
    playerDirector.receiveMessage('removePLayer', this)
}
Player.prototype.changeTeam = function() {
    playerDirector.receiveMessage('changeTeam', this, color)
}
let playerFac = function(name, teamColor) {
  let newPlayer = new Player(name, teamColor)
  playerDirector.receiveMessage('addPlayer', newPlayer)
  return newPlayer
}

最后,我们需要实现这个中介者对象,一般有两种方式

  1. 利用发布订阅模式。将playerDirector实现为订阅者,各player作为发布者,一旦player的状态改变,便推送消息给playerDirector
  2. 在playerDirector中开放一些接收消息的接口,各player可以直接调用这些接口来给
    playerDirector发送消息,player只需传递一个参数给playerDirector
let playerDirector =  (function() {
  let player = {}, // 保存所有玩家
  operations = {} // 中介者可以进行的操作 
  operations.addPlayer = fucntion(player) {
    let teamColor = player.teamColor // 玩家队伍颜色
    players[teamColor] = players[teamColor] || [] 不存在则创建一个团队
    players[teamColor].push(player)
  }
  operations.removePlayer = fucntion(player) {
    let teamColor = player.teamColor, // 玩家队伍颜色
    teamPlayers = players[teamColor] || [] 不存在则创建一个团队
    for (i=teamPlayers.length-1;i>=0;i--) {
        if (teamPlayers[i] == player) teamPlayers.splice(i, 1)
    }
  }
  let receiveMessage = function(...args) {
    let message = args[0]
    operations[message].apply(this, args)
  }  
  return {
    receiveMessage: receiveMessage
  }
})()
  • 装饰者模式
    装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。在传统的面向对象的语言中,给对象添加功能常常使用继承的方式,但继承的方式并不灵活,一方面会导致超类和子类的强耦合,当超类改变时,子类也会随之改变;给对象动态地增加职责的方式被称为装饰者模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地增加职责。
let Plane = function(){}
Plane.prototype.fire = () => {发射普通子弹}
MissileDecorator = function(plane) {
  this.plane = plane
}
MissileDecorator.prototype.fire = function() {
  this.plane.fire()
  console.log('发射导弹')
}

导弹类和原子弹类的构造函数都接受参数plane对象,并且保存好这个参数,在它们的fire方法中,除了执行自身的操作,还调用plane对象的fire方法。
这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象拥有相同的方法。

  • 通过aop装饰函数,给函数动态的添加功能
Function.prototype.before = function(beforefn) {
  let self = this
  return function(...args) {
    beforefn.apply(this, args)
    return self.apply(this, args)
  }
}
// 使用不污染原型的方式
  • aop的应用实例
    我们可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起
    箭头函数的this不是函数调用的对象,而是父级的this

  • 状态模式

let Light = function() {
  this.state = 'off'
  this.button = null
}

通常我们谈到封装,一般都会优先封装对象的行为,而不是状态。状态模式是把事物的状态封装成单独的类,和此种状态有关的行为都被封装在类的内部.

let OffLightstate = function(light) {
  this.light = light
}
OffLightstate.prototype.buttonWasPressed = function() {
  this.light.setState(this.light.WeakLightState)
}
let WeakLightstate = function(light) {
  this.light = light
}
WeakLightstate.prototype.buttonWasPressed = function() {
  this.light.setState(this.light.StrongLightState)
}
let StrongLightState = function(light) {
  this.light = light
}
StrongLightState.prototype.buttonWasPressed = function() {
  this.light.setState(this.light.OffLightState)
}
let Light = function() {
  this.offLightstate = new OffLightstate(this)
this.WeakLightstate = new WeakLightstate(this)
this.StrongLightState = new StrongLightState(this)
this.button=null
}
Light.prototype.init = function() {
  this.currentState = this.offLightstate
this.button.onClick = function() {
  self.currentState.buttonWasPressed()
}
}
  • 缺少抽象类的变通方式
    在状态类中定义一些共同的行为方法,Context会将请求委托给状态对象的实现方法,无论增加多少个状态类都必须实现buttonWasPressed方法。
    在java中,所有的状态类都必须继承一个State抽象父类,这样做的原因一方面是向上转型,另一方面是保证所有状态子类都实现buttonWasPressed方法,子类的原型指向抽象父类的实例实现继承,在父类的原型上定义一个公共方法抛出错误
let State = function() {}
State.prototype.buttonWasPressed = () => { throw new Error('error') }
  • 文件上传
let plugin = (() => {
  let plugin = document.createElement('embed')
  plugin.style.display = 'none'
  plugin.sign = () => { 扫描文件 } 
  plugin.plause = () => {}
  plugin.uoploading = () => {}
  plugin.del = () => {}
  plugin.done = () => {}
  document.body.appendChild(plugin)
  return plugin
})()

let Upload = function(fileName) {
  this.plugin=plugin
  this.fileName = fileName
  this.button1 = null
  this.button2 = null
  this.state = 'sign'
}
Upload.prototype.init = function() {
  let that = this
  this.dom = document.createElement('div')
  this.dom.innerHTML = '<button data-action="button1">扫描中</button>'
  document.body.appendChild(this.dom)
  this.button1 = this.dom.querySelector('[data-action="button1"]')
  this.bindEvent()
}
Upload.prototype.bindEvent = function() {
  let self = this
  this.button1.onClick = function() {
    if (self.state === 'sign') {} else if (){
        self.changeStatus('pause')
    }
}
}
  • 状态模式重构上传程序
    将按钮的吗每一个状态都当做类,每个状态都有自己的行为,为构造函数每种状态子类都创建一个实例对象:
let Upload = function(fileName) {
  this.plugin = plugin
  this.fileName = fileName
  this.button1 = null
  this.button2 = null
  this.signState = new SignState(this)
  this.uploadingState = new UploadingaState(this)
  this.pauseState = new PauseState(this)
  this.doneState = new DoneState(this)
  this.errorState = new ErrorState(this)
  this.currState = new this.signState
}
Upload.prototype.bindEvent = function() {
  let self = this
  this.button1.onClick = function() {
    self.curState.clickHander1()
  }
  this.button2.onClick = function() {
    self.curState.clickHander2()
  }
}

Upload.prototype.sign = function(){
  this.plugin.sign()
  this.currState = this.signState
}
Upload.prototype.uploading = function(){
  this.plugin.uploading()
  this.currState = this.uploadingState
}
let StateFactory = (() => {
  let State = function() {}
 State.prototype.clickHandler1 = function() {
  throw new Error('子类必须重写父类的clickHandler1方法')  
}
  return function(param) {
      let F = function(uploadObj)  {
          this.uploadObj = uploadObj;
      } 
      F.prototype = new State()
      for (var i in param) {
         F.prototype[i] = param[i] // 将传入对象的方法拷贝到原型上
      }
    return F
  }
})()
let SignState = StateFactory({
  clickHandler1: function() { 扫描中,点击无效 },
  clickHandler2: function() { 文件正在扫描不能删除 }
})

  • 状态模式的优缺点
  1. 状态模式定义了状态与行为的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
  2. 避免Context的无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了context中过多的条件分支
  3. 用对象代替字符串记录当前状态,更加一目了然
  • 状态模式中的性能优化点
  1. 有两种选择来管理state对象的创建和销毁。第一种是当state对象被需要时裁创建并随后销毁,另一种是一开始就创建好所有状态不销毁。
  2. 本章,我们为每个context对象都创建了一组state对象,实际上这些state对象是可以共享的,各context对象可以共享一个state对象。
  • js版本的状态机
let Light = function() {
 this.curState = FSM.off
 this.button = null
}

let deletgate = function(client, delegation) {
  return {
   buttonWasPressed: function(...args) {
       return delegation.buttonWasPressed.apply(client, args)
   }
 }
}

let FSM = {
 off: {
   buttonWasPressed: function() {
       
   }
 }
}
  • 表驱动的有限状态机
    下一个状态由当前状态和行为共同决定,这样一来我们就可以在表中查找状态。

适配器模式
适配器模式是解决两个软件实体间接口不兼容问题。

  • 单一职责原则
  1. 代理模式
    把img标签添加到页面的功能和预加载图片的功能分开放到两个对象中去
let myImage = (function() {
  let imgNode= document.createElement('img')
  document.body.appendChild(imgNode)
  return {
    setSrc: function(src) { imgNode.src = src }
  }
})()

// 此时已经创建好img标签放入页面中,proxyImg则添加预加载的功能并且放入一张//默认图片

let proxyImage = (() => {
  // 制造一个假节点预加载图片
  let img = new Image()
  img.onload = () => {
    myImage.setSrc(this.src)
  }
  return {
    setSrc: function() {
      myImage.setSrc('default gif') //设置一张本地已经加载好的图片
      myImage.src=  src
    }
  }
})()
  • 单例模式
    我们把管理单例的职责和创建浮窗的职责分别封装在两个方法里
let getSingle = function(fn) {
  let result
  return function(...args) {
    return result || (result = fn.apply(this, args))
  }
}
// 函数执行的时候将弹框保存在单例函数的变量里
let createLoginLayer = () => {
  let div = document.createElement('div')
  div.innerHTML = 'login'
  document.body.appendChild(div)
  return div
}

let createSingleLoginLayer = getSingle(createLoginLayer)
let loginLayer1 = createSingleLoginLayer()
let loginLayer2 = createSingleLoginLayer()
  • 装饰者模式
    使用装饰者的时候,我们通常让类或者对象一开始只具有一些基础的职责,更多的职责在代码运行时被动态装饰到对象上面,单一职责的原则会增加代码的复杂度。
Function.prototype.after = function(fn) {
   let self = this
  return function(...args) {
    let ret = self.apply(this, args)
    afterfn.apply(this, args)
    return ret
  }
}
  • 最少知识原则
    单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象间可能会产生错综复杂的联系。最少知识原则要求我们尽量减少对象和对象之间的通信。
  1. 中介者模式
    当一个对象发生改变,只需要和中介者通信即可,对象和对象间不通信。
  2. 外观模式
    外观模式主要为子系统的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得子系统更加使用。
    外观模式对客户提供一个简单易用的高层接口,高层接口会把客户请求转发给子系统实现具体功能。请求外观接口不是强制的,客户可以选择越过外观接口直接访问子系统。
  • 开放封闭原则
  1. 找出变化
  2. 放置挂钩,我们在程序可能发生变化的地方放置挂钩,挂钩的返回结果决定下一步的走向。
  • 封闭原则
  1. 发布订阅模式
    发布订阅用来降低多个对象之间的依赖关系,它可以取代对象之间硬编码的通知机制,一个对象不用再显示的调用另一个对象的某个接口。当有新的订阅者出现时,发布者代码不需要任何修改。同样发布者需要修改时,也不会影响之前的订阅者。

  2. 模板方法模式
    在一个运用模板方法的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽离出来放到父类的模板方法里面;

  3. 策略模式
    策略模式将各种算法封装成单独的策略类,这些策略类可以被交换使用。

  4. 代理模式

  5. 职责链模式

  • 先假设变化不会发生,完成需求,当修改来的时候再重构,前期不要过度封装

  • 接口和面向接口编程
    java中的interface,interface关键字可以产生一个完全抽象的类,这个类专门负责建立类与类之间的联系。

  • 回到java的抽象类
    目前我们有一个鸭子类Duck, 还有一个让鸭子发出叫声的AnimalSound类,该类有一个makeSound方法接收Duck类型的对象作为参数

public class Duck {
  public void makeSound(){}
}
public class AnimalSound {
  public void makeSound(Duck duck) {duck.makeSound()}
}

在享受静态语言类型检查带来的安全性的同时,我们也失去了一些编写代码的自由
所以我们需要通过抽象类实现向上转型

public abstract class Animal {
  abstract void makeSound()
}

抽象类的两个作用

  1. 契约作用,继承抽象类的具体类都会继承抽象类中的具体方法,并要求覆盖
  2. 实现向上转型
  • interface
    除了用抽象类完成面向接口编程,interface也可以达到同样的效果。虽然很多人在实际中刻意区分抽象类和interface,但interface实际上也是一种继承的方式,叫接口继承。
    相对于单继承的抽象类,一个类可以实现多个interface。抽象类中除了abstract方法之外,还可以有一些供子类使用的具体方法。interface使抽象更进一步,它产生一个完全抽象的类,不提供任何具体的实现和方法体,但允许该interface的创建者确定方法名、参数列表、返回类型,这相当于提供一些行为上的约定,但不关心该行为的具体实现过程。
    抽象类是基于单继承的,就是说我们不可能让Duck和Chicken再继承另一个家禽类。如果使用interface,可以仅仅针对叫声这个行为编写程序,同时一个类也可以实现多个interface。
public interface Animal {
  abstract void makeSound();
}
public class Duck implements Animal {
  public void makeSound() {}
}

抽象类和interface的主要作用:

  1. 通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
  2. 约定类与类之间的一些契约关系。
  • 使用typescript
interface Command {
  execute: Function
}
class RefreshCommand implements Command {
  constructor() {}
  execute() {}
}
  • 重构
  1. 提炼函数
  2. 合并重复条件片段
  3. 把条件分支语句提炼成函数
  4. 提前让函数退出条件分支
  5. 传递对象参数代替过长的参数列表
  6. 尽量减少参数数量
  7. 少用三目运算符
  8. 合理使用链式调用
  9. 使用return退出多重循环

相关文章

网友评论

      本文标题:设计模式系列三

      本文链接:https://www.haomeiwen.com/subject/egibwctx.html