學了這麼久的設計模式,最近一直在看Node.js
的設計模式,一直納悶爲什麼會有模式這一類東西的存在,那麼模式到底是什麼東西?後面在看了《面向模式的軟件架構》以後才慢慢知道有了一些系統的概念。javascript
面對特定問題時,專家不多去尋找與既有解決方案大相徑庭的新方案,而一般會想起一個之前解決過的相似問題,並將其解決方案的精髓用於解決這個新問題。前端
從特定問題—解決方案中提煉出通用的因素即可獲得模式:這些問題—解決方案一般是一系列熟悉的問題和解決方案,其中每對問題—解決方案都呈現出相同的模式。java
MVC
模式大量用在現代軟件開發流程中,爲什麼會有MVC
模式的存在,來看這一個例子:開發帶人機界面的軟件。數據庫
用戶界面需求容易變化。例如,添加應用程序功能時,必須修改菜單以便可以訪問新功能,還可能須要針對特定客戶調整用戶界面。系統可能須要移植到另外一個平臺,而該平臺採用的「外觀」標準徹底不一樣。即使是升級到新的窗口系統版本,也可能須要修改代碼。總之,若是系統的使用壽命很長,可能常常須要修改用戶界面。設計靈活的系統時,讓用戶界面與功能核心緊密地交織在一塊兒將付出高昂的代價,且容易出錯。這樣作的後果是,可能須要開發和維護多個大不相同的軟件系統——每種用戶界面實現一個,且修改將涉及衆多不一樣的模塊。總之,開發這種交互式軟件系統時,必須考慮以下兩個方面:express
爲解決這種問題,應將交互式應用程序劃分紅三部分:處理、輸出和輸入。npm
model
)組件封裝核心數據和功能,獨立於輸出表示方式和輸入行爲。view
)組件向用戶顯示信息。視圖從模型那裏獲取它顯示的信息,一個模型能夠 有多個視圖。controller
)組件。控制器接受輸入,一般是表示鼠標移動、鼠標按鈕激活或鍵盤輸入的事件。事件被轉換爲服務請求,而服務請求要麼被髮送給模型,要麼被髮送給視圖。用戶只經過控制器與系統交互。經過將模型與視圖和控制器組件分開,讓同一個模型能夠有多個視圖。若是用戶經過一個視圖的控制器修改了模型,這種變動應在依賴相關數據的其餘全部視圖中反映出來。爲此,每當模型的數據發生變化時,它都會通知全部視圖,而視圖將從模型那裏檢索新數據,並更新顯示的信息。這種解決方案確保了修改應用程序的一個子系統時不會嚴重影響其餘子系統。例如,可將非圖形用戶界面改爲圖形用戶界面而無需修改模型子系統,還可支持新的輸入設備而不影響信息的顯示和功能核心。全部軟件版本均可依賴同一個模型子系統,該子系統獨立於「外觀」。編程
咱們從下圖所示的結構開始分析:json
上圖顯示了Model-View-Controller模式
的典型示例;它描述了一個簡單的鑑權服務的結構。AuthController
接受來自客戶端的輸入,從請求中提取登陸信息,並執行一些初步驗證。以後AuthService
檢查客戶端提供的憑證是否與存儲在數據庫中的信息匹配;最後使用db
模塊執行一些特定的查詢來完成的,做爲與數據庫通訊的一種手段。這三個組件鏈接在一塊兒的方式將決定應用程序的可重用性,可測試性和可維護性。設計模式
在這裏:模型(Model
)指的就是db
模塊,控制器(Controller
)指的就是AuthController
和AuthService
,而視圖則是前端的用戶界面,也就是HTML
文檔。服務器
將這些組件鏈接在一塊兒的最天然的方法是經過AuthService
請求db
模塊,而後從AuthController
請求AuthService
。
讓咱們經過實際實現剛剛描述的系統來演示這一點。那麼咱們來設計一個簡單的鑑權服務器,它將有如下兩個HTTP API
:
POST '/ login'
:接收包含用戶名和密碼對進行身份驗證的JSON
對象。 成功時,它會返回一個JSON Web Token(JWT)
,隨後的請求中使用它來驗證用戶的身份。
GET'/ checkToken'
:查看用戶是否具備權限。
對於這個例子,咱們將使用幾種技術;這對咱們來講並不陌生。咱們使用express來實現Web API
和levelup來存儲用戶的數據。
咱們先從底層開始構建應用程序;首先實現levelUp
數據庫實例的模塊。咱們來建立一個名爲lib/db.js
的新文件,其中包含如下內容:
const level = require('level');
const sublevel = require('level-sublevel');
module.exports = sublevel(
level('example-db', {
valueEncoding: 'json'
})
);
複製代碼
前面的模塊是存儲在./example-db
目錄中的LevelDB
數據庫的鏈接,而後使用sublevel來修飾實例,經過這一模塊實現了增刪查改數據庫。模塊導出的對象是數據庫對象自己。
如今咱們有了db
單例,咱們可使用它來實現lib/authService.js
模塊,它負責查詢數據庫,根據用戶身份憑證查看用戶是否具備權限。 代碼以下(只顯示相關部分):
"use strict";
const jwt = require('jwt-simple');
const bcrypt = require('bcrypt');
const db = require('./db');
const users = db.sublevel('users');
const tokenSecret = 'SHHH!';
exports.login = (username, password, callback) => {
users.get(username, (err, user) => {
if(err) return callback(err);
bcrypt.compare(password, user.hash, (err, res) => {
if(err) return callback(err);
if(!res) return callback(new Error('Invalid password'));
let token = jwt.encode({
username: username,
expire: Date.now() + (1000 * 60 * 60) //1 hour
}, tokenSecret);
callback(null, token);
});
});
};
exports.checkToken = (token, callback) => {
let userData;
try {
//jwt.decode will throw if the token is invalid
userData = jwt.decode(token, tokenSecret);
if (userData.expire <= Date.now()) {
throw new Error('Token expired');
}
} catch(err) {
return process.nextTick(callback.bind(null, err));
}
users.get(userData.username, (err, user) => {
if (err) return callback(err);
callback(null, {username: userData.username});
});
};
複製代碼
authService
模塊實現login()
服務,該服務負責查詢數據庫,檢查用戶名和密碼信息,checkToken()
服務接受token
做爲參數並驗證其有效性。
繼續在應用程序的層次上,咱們如今要看看lib/authController.js
模塊。這個模塊負責處理HTTP
請求,它本質上是Express
路由的集合;該模塊的代碼以下:
"use strict";
const authService = require('./authService');
exports.login = (req, res, next) => {
authService.login(req.body.username, req.body.password,
(err, result) => {
if (err) {
return res.status(401).send({
ok: false,
error: 'Invalid username/password'
});
}
res.status(200).send({ok: true, token: result});
}
);
};
exports.checkToken = (req, res, next) => {
authService.checkToken(req.query.token,
(err, result) => {
if (err) {
return res.status(401).send({
ok: false,
error: 'Token is invalid or expired'
});
}
res.status(200).send({ok: 'true', user: result});
}
);
};
複製代碼
authController
模塊實現兩個Express
路由:login()
用於執行登陸操做並返回相應的token
,checkToken()
用於檢查token
的有效性。這兩個路由委託他們的大部分邏輯到authService
,因此他們惟一的工做是處理HTTP
請求和響應。
最後,在應用程序的入口點,咱們調用咱們的controller
。遵循約定,咱們將把這個邏輯放在名爲app.js
的模塊中,放在咱們項目的根目錄下,以下所示:
"use strict";
const Express = require('express');
const bodyParser = require('body-parser');
const errorHandler = require('errorhandler');
const http = require('http');
const authController = require('./lib/authController');
let app = module.exports = new Express();
app.use(bodyParser.json());
app.post('/login', authController.login);
app.get('/checkToken', authController.checkToken);
app.use(errorHandler());
http.createServer(app).listen(3000, () => {
console.log('Express server started');
});
複製代碼
咱們能夠看到,咱們的應用程序模塊是很是基礎的。 它包含一個簡單的Express
服務器,它註冊了一些中間件和authController
導出的兩條路由。這也就是一個簡單的包含controller
和model
的Web
服務,添加好前端HTML
頁面,也就實現了MVC
架構的分離
每一個模式都包含三部分:
Context
) 問題出現的背景;Problem
) 該背景下反覆出現的問題;Solution
) 通過實踐檢驗的解決之道。背景描繪了問題發生的情形,讓本來平淡無奇的問題—解決方案更爲豐滿。模式的背景可能很是籠統,如「開發帶人機界面的軟件」,也可能將具體的模式聯繫在一塊兒,如「在模型、視圖和控制器之間實現變動傳播機制」。
模式描述綱要的這部分闡述了給定背景下反覆出現的問題。它以籠統的問題陳述開始,闡述了問題的本質:必須解決的具體設計問題是什麼?例如,Model-View-Controller
模式解決的是用戶界面頻繁變動的問題。模式表示解決問題時須要考慮的方方面面:
Model-View-Controller
模式說明了兩種做用力:修改用戶界面應垂手可得,且這種修改不該影響軟件的核心功能。
模式的解決方案部分指出瞭如何解決反覆出現的問題,更準確地說是如何平衡相關的做用 力。在軟件架構中,這樣的解決方案包括兩個方面:
Model-View-Controller
模式的描述中有這樣一句話:「將交互式應用程序分紅三部分——處理、輸出和輸入。」Model-View-Controller
模式的「解決方案」部分有這樣一句話:「控制器接受輸入,這一般是表示鼠標移動、鼠標按鈕激活或鍵盤輸入的事件。事件被轉換爲服務請求,而服務請求要麼被髮送給模型,要麼被髮送給視圖。」模式通常分爲三類:
Model-View-Controller
模式C++
語言的Counted Body
模式。模式提供了一種前途無量的方法,可用於開發具備指定特徵的軟件。它們記錄了既有的設計知識,有助於找到設計問題的妥善解決方案。模式的規模和抽象程度各異,涵蓋了衆多重要的軟件開發領域。模式彼此交織在一塊兒,咱們可使用一個模式來改善另外一個更大的模式,還可結合使用多個模式來解決複雜的問題。模式論述了軟件架構的一些重要方面,並給既有技術和方法提供了補充。模式能夠和任何編程範式結合使用,且幾乎可以使用任何編程語言實現。