Node.js && JavaScript 面试常用的设计模式二

468 阅读4分钟

观察者模式

这是一个非常有趣的模式,它允许你通过对某个输入作出反应来响应,而不是主动地检查是否提供了输入。换句话说,使用此模式,可以指定你正在等待的输入类型,并被动地等待,直到提供该输入才执行代码。

在这里,观察者是一个对象,它们知道想要接收的输入类型和要响应的操作,这些对象的作用是“观察”另一个对象并等待它与它们通信。

另一方面,可观察对象将让观察者知道何时有新的输入可用,以便他们可以对它做出反应(如果适用的话)。如果这听起来很熟悉,那是因为Node.js中处理事件的任何东西都是这种模式。

下面的代码是一段HTTP Server的实现

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Your own server here');
})

server.on('error', err => {
    console.log(“Error:: “, err)
})

server.listen(3000, '127.0.0.1', () => {
  console.log('Server up and running');
})

在上面的代码中隐藏了观察者模式。服务器对象将充当可观察对象,而回调函数是实际的观察者。你可以看到,这种模式非常适合这种HTTP异步调用。这种模式的另一个广泛使用的用例是触发特定事件。这种模式可以在任何容易异步触发事件(例如错误或状态更新)的模块上找到。例如数据库驱动程序,甚至套接字。io,它允许你在自己代码之外触发的特定事件上设置观察者。

下面我们简单的实现一个EventEmitter类来实现观察者模式:


class eventEmitter {
  constructor() {
    this.eventObj = {}
  }
  on(evName, fn) {
    this.eventObj[evName] = this.eventObj[evName] ? this.eventObj[evName].concat(fn) : [fn]
  }
  emit(evName, ...params) {
    for (let fn of this.eventObj[evName]) {
      fn.apply(null, params)
    }
  }
}
const event = new eventEmitter()
event.on('error', err => {
  console.log(err, 1)
})
event.on('error', err => {
  console.log(err, 2)
})

event.emit('error')

上面的例子中,error事件作为一个被观察对象,回调函数是观察者,当触发on事件时,观察者会被加入到一个队列中,当error事件触发时,回调函数(观察者)会受到通知,并且依次被调用。

职责链模式

职责链模式是Node.js世界中很多人使用过的一种模式,他们甚至没有意识到这一点。

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

下面是一个非常基本的实现这个模式,你可以看到在底部,我们有四个可能的值需要处理,但是我们不在乎谁来处理它们,我们只需要,至少,一个函数来使用它们,因此我们只是寄给链,让链中的每一个函数决定他们是否应该使用它或忽略它。

function processRequest(r, chain) {

    let lastResult = null
    let i = 0
    do {
     lastResult = chain[i](r)
     i++
    } while(lastResult != null && i < chain.length)
    if(lastResult != null) {
     console.log("Error: request could not be fulfilled")
    }
}

let chain = [
    function (r) {
     if(typeof r == 'number') {
         console.log("It's a number: ", r)
         return null
     }
     return r
    },
    function (r) {
     if(typeof r == 'string') {
         console.log("It's a string: ", r)
         return null
     }
     return r
    },
    function (r) {
     if(Array.isArray(r)) {
         console.log("It's an array of length: ", r.length)
         return null
     }
     return r
    }
]

processRequest(1, chain)
processRequest([1,2,3], chain)
processRequest('[1,2,3]', chain)
processRequest({}, chain)

输出的结果是:

It's a number:  1
It's an array of length:  3
It's a string:  [1,2,3]
Error: request could not be fulfilled

使用例子

在我们经常使用的开发库中,这种模式最明显的例子是ExpressJS的中间件。使用该模式,实际上是在设置一系列函数(中间件),这些函数计算请求对象并决定对其运行一个函数或者忽略它。可以将该模式看作上面示例的异步版本,在这个版本中,不是检查函数是否返回值,而是检查将哪些值传递给它们调用的下一个回调。

var app = express();

app.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next(); //call the next function on the chain
})

中间件是这种模式的一种特殊实现,因为可以认为所有中间件都可以完成请求,而不是链中的一个成员。然而,其背后的原理是一样的。

下面我们来自己简单实现一个中间件函数

class App {
  constructor() {
    this.middleware = []
    this.index = 0
  }

  use(fn) {
    this.middleware.push(fn)
  }

  exec() {
    this.next()
  }

  next() {
    if (this.index < this.middleware.length) {
      const fn = this.middleware[this.index]
      this.index++
      fn.call(this, this.next.bind(this))
    }
  }
}

const app = new App()

app.use(function (next) {
  console.log(1)
  next()
})

app.use(function (next) {
  console.log(2)
})

app.exec()

输出结果

1,2

当使用use函数时,在中间件队列中加入回调函数,在执行时,可以调用next()来进入下一个中间件。

上一篇我们理解了IIFE工厂模式还有单例模式

关注作者github,第一时间获得更新。