toBeTheLight.github.io 荒原

阅读:《图解设计模式》6 访问数据结构


书中将 Visitor 访问者模式、Chain of Responsibility 责任链模式归纳为漫步数据结构的设计模式。 访问者模式:将数据结构与对其中元素的操作分离。 责任链模式:将多个处理对象组成一条责任链,然后将待处理目标沿着这条链传递进行处理。

Visitor 访问者模式

定义

将数据结构与对其中元素的操作分离,由单独的访问者类聚合所有处理方式,添加新的方式时,不需要改动数据结构。

角色

  • 抽象访问者:定义一个访问具体元素的接口,需要为每种具体元素类定义访问接口(visitor)。
  • 具体访问者:实现抽象访问者的接口。
  • 抽象元素:定义接收访问者的接口(accept)
  • 具体元素:实现抽象元素接口。
  • 对象结构:管理具体元素,提供能使访问者访问对象结构中所有元素的方法(迭代或其他方式)。

JavaScript 实现

在 Java 中,为每种具体元素类定义访问接口使用的是多态模式。JavaScript 中不支持,所以我们换个方式来实现。

// 抽象元素
class Item {
  constructor ({ name, size, hidden }) {
    this.name = name
    this.size = size
    this.hidden = hidden
  }
  accept () {
    throw new Error('需实现 Item 类的 accept 方法')
  }
}
// 具体元素
class File extends Item {
  constructor ({ name, size, hidden }) {
    super({ name, size, hidden })
  }
  accept (visitor) {
    visitor.visit(this)
  }
}
class Dir extends Item {
  constructor ({ size, hidden }) {
    super({ size, hidden })
  }
  accept (visitor) {
    visitor.visit(this)
  }
}
// 抽象访问者
class Visitor {
  visit (instance) {
    switch (instance.constructor.name) {
      case 'File':
        this.visitFile(instance)
        break
      case 'Dir':
        this.visitDir(instance)
        break
      default:
        break
    }
  }
  visitFile (instance) {
    throw new Error('需实现 Visitor 类的 visitFile 方法')
  }
  visitDir (instance) {
    throw new Error('需实现 Visitor 类的 visitDir 方法')
  }
}
// 具体访问者
class ListVisitor extends Visitor {
  visitFile (instance) {
    if (!instance.hidden) {
      console.log(`文件大小为${instance.size}`)
    }
  }
  visitDir (instance) {
    if (!instance.hidden) {
      console.log(`文件夹大小为${instance.size}`)
    }
  }
}
// 数据结构
class List {
  constructor () {
    this.list = []
  }
  add (instance) {
    this.list.push(instance)
  }
  iterator (visitor) {
    this.list.forEach(item => item.accept(visitor))
  }
}
// 使用
const list = new List()
const dirA = new Dir({ name: '文件夹A', size: 10 })
const fileA = new File({ name: '文件A', size: 2, hidden: true })
const fileB = new File({ name: '文件B', size: 3 })
list.add(dirA)
list.add(fileA)
list.add(fileB)
const listVisitor = new ListVisitor()
list.iterator(listVisitor)

拓展

应该值得注意的是保存数据结构和以数据结构为基础进行处理是两种不同的事情,访问者模式就是将两者分为了数据结构和访问者两个部分。

同时访问者模式又有以下问题:

  • 违反了依赖倒置原则:因为元素有所差异,所以访问者需要熟知每种类型才能进行具体操作,从而访问者会依赖具体类而非抽象类。
  • 添加新的元素比较困难,需要修改所有的访问者(抽象和具体)类。所以访问者模式更适合数据结构比较稳定的情况。

Chain of Responsibility 责任链模式

定义

将多个处理对象组成一条责任链,然后将待处理目标沿着这条链传递进行处理。

很明显,koa 的中间件洋葱圈模型就是责任链模式。

角色

  • 抽象处理者:定义一个处理请求的接口,包含抽象处理方法和一个后继转交责任的方法。
  • 具体处理者:实现具体的处理方法。
  • 请求者:创建责任链,并将待处理对象交给责任链的第一个对象。

JavaScript 实现

我们改造部分内容,模仿 koa 的中间件做一版实现。

// 请求者
class Koa {
  constructor () {
    this.list = []
    this.current = 0
    this.ctx = {
      body: ''
    }
  }
  use (middleware) {
    this.list.push(middleware)
  }
  start () {
    this.run()
  }
  run () {
    if (this.current >= this.list.length) return
    this.list[this.current](this.ctx, this.next.bind(this))
  }
  next () {
    this.current += 1
    this.run()
  }
}

// 具体处理者(类)
// next 即转交责任的方法
function block (ctx, next) {
  ctx.body += 'block'
  next()
}
function process (ctx, next) {
  ctx.body += 'process'
  next()
}
function sweep (ctx, next) {
  next()
  ctx.body += 'sweep'
}
// 使用
const koa = new Koa()
koa.use(sweep)
koa.use(block)
koa.use(process)
koa.start()
console.log(koa.ctx)

Content