設計模式是任何軟件開發人員平常工做的一部分,無論他們是否意識到這一點。git
在本文中,咱們將研究如何識別這些設計模式,以及如何在本身的項目中開始使用它們。github
簡單地說,設計模式就是一種可讓你以某種方式更好的組織代碼的方法,從而得到一些好處。好比更快的開發速度、代碼的可重用性等等。數據庫
全部模式都很容易採用OOP範式。儘管考慮到JavaScript的靈活性,你也能夠在非OOP項目中實現這些概念。事實上,每一年都會有新的設計模式被建立,有太多的設計模式沒法在一篇文章中涵蓋,本文將重點介紹和實現幾種常見的設計模式。json
我要展現的第一個是容許同時定義和調用函數的模式。因爲JavaScript做用域的工做方式,使用IIFE能夠很好地模擬類中的私有屬性之類的東西。事實上,這個特定的模式有時被用做其餘更復雜需求的一部分。咱們待會再看。c#
在咱們深刻研究用例和它背後的機制以前,讓我快速地看一下它究竟是什麼樣子的:設計模式
(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查看更多內容。第一時間得到更新。