Node.js && JavaScript 面試經常使用的設計模式

設計模式是任何軟件開發人員平常工做的一部分,無論他們是否意識到這一點。git

在本文中,咱們將研究如何識別這些設計模式,以及如何在本身的項目中開始使用它們。github

什麼是設計模式

簡單地說,設計模式就是一種可讓你以某種方式更好的組織代碼的方法,從而得到一些好處。好比更快的開發速度、代碼的可重用性等等。數據庫

全部模式都很容易採用OOP範式。儘管考慮到JavaScript的靈活性,你也能夠在非OOP項目中實現這些概念。事實上,每一年都會有新的設計模式被建立,有太多的設計模式沒法在一篇文章中涵蓋,本文將重點介紹和實現幾種常見的設計模式。json

當即執行函數表達式(IIFE)

我要展現的第一個是容許同時定義和調用函數的模式。因爲JavaScript做用域的工做方式,使用IIFE能夠很好地模擬類中的私有屬性之類的東西。事實上,這個特定的模式有時被用做其餘更復雜需求的一部分。咱們待會再看。c#

IIFE常見例子

在咱們深刻研究用例和它背後的機制以前,讓我快速地看一下它究竟是什麼樣子的:設計模式

(function() {
   const x = 20
   const y = 20
   const answer = x + y
   console.log(answer)
})()

複製代碼

經過將上述代碼粘貼到瀏覽器的控制檯,你將當即獲得結果,由於正如其名所示,一旦定義了函數,就會當即執行它。數組

IIFE由一個匿名函數聲明組成,在一組括號內(括號將定義轉換爲函數表達式),而後在它的尾部有一組調用括號。像這樣:瀏覽器

(function(/*received parameters*/) {
//your code here
})(/*parameters*/)
複製代碼

用例

模擬靜態變量

還記得靜態變量嗎? 例如在C或c#中。使用靜態變量的好處是,若是你在一個函數中定義一個靜態變量,無論你調用它多少次,這個變量對函數的全部實例都是通用的。一個簡單的例子是像下面這樣的:閉包

function autoIncrement() {
  static let number = 0
  number++
  return number
}
複製代碼

上面的函數每次調用都會返回一個新的數字(固然,假設靜態關鍵字在JS中可用)。咱們能夠在JS中來實現它,你能夠像這樣模擬一個靜態變量:dom

let autoIncrement = (function() {
  let number = 0

  return function () {
    number++
    return number
  }
})()
複製代碼

當即函數執行後返回一個新函數(閉包),該函數內部引用了number變量。因爲JS閉包機制,當即函數執行完畢後,number變量不會當即被垃圾回收掉,因此autoIncrement每次執行老是能夠訪問number變量(就像它是一個全局變量同樣)。

模擬私有變量

ES6類將每一個成員都視爲公共的,這意味着沒有私有屬性或方法。可是多虧了IIFE,若是你想的話,你能夠模擬它。

const autoIncrementer = (function() {
  let value = 0

  return {
    incr() {
      value++
    },

    get value() {
      return value
    }
  }
})()
> autoIncrementer.incr()
undefined
> autoIncrementer.incr()
undefined
> autoIncrementer.value
2
> autoIncrementer.value = 3
3
> autoIncrementer.value
2
複製代碼

上面的代碼展現了一種建立私有變量的方法,當即函數執行後,返回一個object賦值給變量autoIncrementer,在外部只能經過value方法和incr方法獲取和修改value的值。

工廠方法模式

這個模式是我最喜歡的模式之一,由於它可讓代碼變更清楚簡潔。

工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不暴露建立對象的具體邏輯,而是將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。

這可能聽起來並非那麼有用,但請看下面這個簡單例子就會明白。

( _ => {

    let factory = new MyEmployeeFactory()

    let types = ["fulltime", "parttime", "contractor"]
    let employees = [];
    for(let i = 0; i < 100; i++) {
     employees.push(factory.createEmployee({type: types[Math.floor( (Math.random(2) * 2) )]})    )}

    //....
    employees.forEach( e => {
     console.log(e.speak())
    })

})()
複製代碼

上面的代碼的關鍵點是經過factory.createEmployee添加了多個對象到employees數組中,全部這些對象都共享相同的接口(他們有相同的一組方法),也有本身特有的屬性,可是你真的不須要關心對象建立的細節和何時建立。

下面咱們具體看一下構造函數MyEmployeeFactory的實現

class Employee {

  speak() {
    return "Hi, I'm a " + this.type + " employee"
  }

}

class FullTimeEmployee extends Employee{
  constructor(data) {
    super()
    this.type = "full time"
    //....
  }
}


class PartTimeEmployee extends Employee{
  constructor(data) {
    super()
    this.type = "part time"
    //....
  }
}

class ContractorEmployee extends Employee{
  constructor(data) {
    super()
    this.type = "contractor"
    //....
  }
}

class MyEmployeeFactory {
  createEmployee(data) {
    if(data.type == 'fulltime') return new FullTimeEmployee(data)
    if(data.type == 'parttime') return new PartTimeEmployee(data)
    if(data.type == 'contractor') return new ContractorEmployee(data)
  }
}

複製代碼

用例

前面的代碼已經顯示了一個通用用例,可是若是咱們想更具體一些,接下來使用這個模式的來處理錯誤對象的建立。

假設有一個大約有10個端點的Express應用程序,其中每一個端點都須要根據用戶輸入返回2到3個錯誤,假設咱們有30個語句大概像這樣:

if(err) {
  res.json({error: true, message: 「Error message here」})
}
複製代碼

若是忽然須要給error對象新加一個屬性,那麼須要修改30個位置,這是很是麻煩的。能夠過將錯誤對象定義到一個簡單的類中解決這個問題。若是有多種類型的錯誤對象,這個時候就能夠建立一個工廠函數來決定實例化那個錯誤對象。

若是你要集中建立錯誤對象的邏輯,那麼你在整個代碼中要作的就是:

if(err) {
  res.json(ErrorFactory.getError(err))
}
複製代碼

單例模式

這是另外一個老的但很好用的設計模式。注意,這是一個很是簡單的模式,可是它能夠幫助你跟蹤正在實例化的類的實例數量。實際上,它能夠幫助你一直保持這個數字爲1。單例模式容許實例化一個對象一次,而後當每次須要時都使用它,而不是建立一個新的對象。這樣能夠更好的追蹤對這個對象的引用。

一般,其餘語言使用一個靜態屬性實現此模式,一旦實例存在,它們將存儲該實例。這裏的問題是,正如我以前提到的,咱們沒法訪問JS中的靜態變量。咱們能夠用兩種方式實現,一種是使用IIFE而不是類。

另外一種方法是使用ES6模塊,並讓咱們的單例類使用本地全局變量來存儲實例。經過這樣作,類自己被導出到模塊以外,可是全局變量仍然是模塊的本地變量。

這聽起來比看起來要複雜得多:

let instance = null

class SingletonClass {

  constructor() {
    this.value = Math.random(100)
  }

  printValue() {
    console.log(this.value)
  }

  static getInstance() {
    if(!instance) {
        instance = new SingletonClass()
    }

    return instance
  }
}

module.exports = SingletonClass
複製代碼

你能夠像下面這樣使用:

const Singleton = require(「./singleton」)
const obj = Singleton.getInstance()
const obj2 = Singleton.getInstance()

obj.printValue()
obj2.printValue()

console.log("Equals:", obj === obj2)
複製代碼

上面的代碼輸出:

0.5035326348000628
0.5035326348000628
Equals::  true
複製代碼

確實,咱們只實例化對象一次,並返回現有實例。

用例

在決定是否須要相似於單例的實現時,須要考慮如下問題:你真正須要多少類實例?若是答案是2或更多,那麼這不是你的模式。

可是當處理數據庫鏈接時,你可能須要考慮它。

考慮一下,一旦鏈接到數據庫,在整個代碼中保持鏈接活動和可訪問性是一個好主意。注意,這能夠用不少不一樣的方法來解決,可是單例模式確實是其中之一。

利用上面的例子,咱們能夠把它寫成這樣:

const driver = require("...")

let instance = null


class DBClass {

  constructor(props) {
    this.properties = props
    this._conn = null
  }

  connect() {
    this._conn = driver.connect(this.props)
  }

  get conn() {
    return this._conn
  }

  static getInstance() {
    if(!instance) {
        instance = new DBClass()
    }

    return instance
  }
}

module.exports = DBClass

複製代碼

如今,能夠肯定,不管在哪裏使用getInstance方法,都將返回唯一的活動鏈接(若是有的話)。

結論

上面幾種是咱們寫代碼時經常使用的設計模式,下一篇咱們將重點深刻理解觀察者模式職責鏈模式

關注做者github查看更多內容。第一時間得到更新。

相關文章
相關標籤/搜索