Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
lvwxx opened this issue Jul 21, 2019 · 0 comments
Open

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

lvwxx opened this issue Jul 21, 2019 · 0 comments
Labels
question Further information is requested

Comments

@lvwxx
Copy link
Owner

lvwxx commented Jul 21, 2019

观察者模式

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

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

另一方面,可观察对象将让观察者知道何时有新的输入可用,以便他们可以对它做出反应(如果适用的话)。如果这听起来很熟悉,那是因为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,第一时间获得更新。

@lvwxx lvwxx added the question Further information is requested label Aug 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant