摘要:模式記錄了已得到充分證明的既有設(shè)計(jì)經(jīng)驗(yàn)。模式有助于創(chuàng)建具有指定特征的軟件。每個(gè)模式都說明了運(yùn)行階段的行為。應(yīng)用設(shè)計(jì)模式不會(huì)影響軟件系統(tǒng)的基本架構(gòu),但可能嚴(yán)重影響子系統(tǒng)的架構(gòu)。成例如何解決特定的設(shè)計(jì)問題。
學(xué)了這么久的設(shè)計(jì)模式,最近一直在看Node.js的設(shè)計(jì)模式,一直納悶為何會(huì)有模式這一類東西的存在,那么模式究竟是什么東西?后面在看了《面向模式的軟件架構(gòu)》之后才慢慢知道有了一些系統(tǒng)的概念。
模式是什么?面對(duì)特定問題時(shí),專家很少去尋找與既有解決方案截然不同的新方案,而通常會(huì)想起一個(gè)以前解決過的類似問題,并將其解決方案的精髓用于解決這個(gè)新問題。
從特定問題—解決方案中提煉出通用的因素便可得到模式:這些問題—解決方案通常是一系列熟悉的問題和解決方案,其中每對(duì)問題—解決方案都呈現(xiàn)出相同的模式。
Model-View-Controller模式MVC模式大量用在現(xiàn)代軟件開發(fā)流程中,為何會(huì)有MVC模式的存在,來看這一個(gè)例子:開發(fā)帶人機(jī)界面的軟件。
用戶界面需求容易變化。例如,添加應(yīng)用程序功能時(shí),必須修改菜單以便能夠訪問新功能,還可能需要針對(duì)特定客戶調(diào)整用戶界面。系統(tǒng)可能需要移植到另一個(gè)平臺(tái),而該平臺(tái)采用的“外觀”標(biāo)準(zhǔn)完全不同。即便是升級(jí)到新的窗口系統(tǒng)版本,也可能需要修改代碼??傊绻到y(tǒng)的使用壽命很長,可能經(jīng)常需要修改用戶界面。設(shè)計(jì)靈活的系統(tǒng)時(shí),讓用戶界面與功能核心緊密地交織在一起將付出高昂的代價(jià),且容易出錯(cuò)。這樣做的后果是,可能需要開發(fā)和維護(hù)多個(gè)大不相同的軟件系統(tǒng)——每種用戶界面實(shí)現(xiàn)一個(gè),且修改將涉及眾多不同的模塊??傊?,開發(fā)這種交互式軟件系統(tǒng)時(shí),必須考慮如下兩個(gè)方面:
應(yīng)該能夠輕松地修改用戶界面,在運(yùn)行階段就能完成;
調(diào)整或移植用戶界面時(shí),不應(yīng)影響到應(yīng)用程序功能核心的代碼。
為解決這種問題,應(yīng)將交互式應(yīng)用程序劃分成三部分:處理、輸出和輸入。
模型(model)組件封裝核心數(shù)據(jù)和功能,獨(dú)立于輸出表示方式和輸入行為。
視圖(view)組件向用戶顯示信息。視圖從模型那里獲取它顯示的信息,一個(gè)模型可以
有多個(gè)視圖。
每個(gè)視圖都有相關(guān)聯(lián)的控制器(controller)組件??刂破鹘邮茌斎?,通常是表示鼠標(biāo)移動(dòng)、鼠標(biāo)按鈕激活或鍵盤輸入的事件。事件被轉(zhuǎn)換為服務(wù)請(qǐng)求,而服務(wù)請(qǐng)求要么被發(fā)送給模型,要么被發(fā)送給視圖。用戶只通過控制器與系統(tǒng)交互。
通過將模型與視圖和控制器組件分開,讓同一個(gè)模型可以有多個(gè)視圖。如果用戶通過一個(gè)視圖的控制器修改了模型,這種變更應(yīng)在依賴相關(guān)數(shù)據(jù)的其他所有視圖中反映出來。為此,每當(dāng)模型的數(shù)據(jù)發(fā)生變化時(shí),它都會(huì)通知所有視圖,而視圖將從模型那里檢索新數(shù)據(jù),并更新顯示的信息。這種解決方案確保了修改應(yīng)用程序的一個(gè)子系統(tǒng)時(shí)不會(huì)嚴(yán)重影響其他子系統(tǒng)。例如,可將非圖形用戶界面改成圖形用戶界面而無需修改模型子系統(tǒng),還可支持新的輸入設(shè)備而不影響信息的顯示和功能核心。所有軟件版本都可依賴同一個(gè)模型子系統(tǒng),該子系統(tǒng)獨(dú)立于“外觀”。
用Model-View-Controller模式實(shí)現(xiàn)一個(gè)鑒權(quán)服務(wù)我們從下圖所示的結(jié)構(gòu)開始分析:
上圖顯示了Model-View-Controller模式的典型示例;它描述了一個(gè)簡單的鑒權(quán)服務(wù)的結(jié)構(gòu)。AuthController接受來自客戶端的輸入,從請(qǐng)求中提取登錄信息,并執(zhí)行一些初步驗(yàn)證。之后AuthService檢查客戶端提供的憑證是否與存儲(chǔ)在數(shù)據(jù)庫中的信息匹配;最后使用db模塊執(zhí)行一些特定的查詢來完成的,作為與數(shù)據(jù)庫通信的一種手段。這三個(gè)組件連接在一起的方式將決定應(yīng)用程序的可重用性,可測試性和可維護(hù)性。
在這里:模型(Model)指的就是db模塊,控制器(Controller)指的就是AuthController和AuthService,而視圖則是前端的用戶界面,也就是HTML文檔。
將這些組件連接在一起的最自然的方法是通過AuthService請(qǐng)求db模塊,然后從AuthController請(qǐng)求AuthService。
讓我們通過實(shí)際實(shí)現(xiàn)剛剛描述的系統(tǒng)來演示這一點(diǎn)。那么我們來設(shè)計(jì)一個(gè)簡單的鑒權(quán)服務(wù)器,它將有以下兩個(gè)HTTP API:
POST "/ login":接收包含用戶名和密碼對(duì)進(jìn)行身份驗(yàn)證的JSON對(duì)象。 成功時(shí),它會(huì)返回一個(gè)JSON Web Token(JWT),隨后的請(qǐng)求中使用它來驗(yàn)證用戶的身份。
GET"/ checkToken":查看用戶是否具有權(quán)限。
對(duì)于這個(gè)例子,我們將使用幾種技術(shù);這對(duì)我們來說并不陌生。我們使用express來實(shí)現(xiàn)Web API和levelup來存儲(chǔ)用戶的數(shù)據(jù)。
db模塊我們先從底層開始構(gòu)建應(yīng)用程序;首先實(shí)現(xiàn)levelUp數(shù)據(jù)庫實(shí)例的模塊。我們來創(chuàng)建一個(gè)名為lib/db.js的新文件,其中包含以下內(nèi)容:
const level = require("level"); const sublevel = require("level-sublevel"); module.exports = sublevel( level("example-db", { valueEncoding: "json" }) );
前面的模塊是存儲(chǔ)在./example-db目錄中的LevelDB數(shù)據(jù)庫的連接,然后使用sublevel來修飾實(shí)例,通過這一模塊實(shí)現(xiàn)了增刪查改數(shù)據(jù)庫。模塊導(dǎo)出的對(duì)象是數(shù)據(jù)庫對(duì)象本身。
authService模塊現(xiàn)在我們有了db單例,我們可以使用它來實(shí)現(xiàn)lib/authService.js模塊,它負(fù)責(zé)查詢數(shù)據(jù)庫,根據(jù)用戶身份憑證查看用戶是否具有權(quán)限。 代碼如下(只顯示相關(guān)部分):
"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模塊實(shí)現(xiàn)login()服務(wù),該服務(wù)負(fù)責(zé)查詢數(shù)據(jù)庫,檢查用戶名和密碼信息,checkToken()服務(wù)接受token作為參數(shù)并驗(yàn)證其有效性。
authController模塊繼續(xù)在應(yīng)用程序的層次上,我們現(xiàn)在要看看lib/authController.js模塊。這個(gè)模塊負(fù)責(zé)處理HTTP請(qǐng)求,它本質(zhì)上是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模塊實(shí)現(xiàn)兩個(gè)Express路由:login()用于執(zhí)行登錄操作并返回相應(yīng)的token,checkToken()用于檢查token的有效性。這兩個(gè)路由委托他們的大部分邏輯到authService,所以他們唯一的工作是處理HTTP請(qǐng)求和響應(yīng)。
app模塊最后,在應(yīng)用程序的入口點(diǎn),我們調(diào)用我們的controller。遵循約定,我們將把這個(gè)邏輯放在名為app.js的模塊中,放在我們項(xiàng)目的根目錄下,如下所示:
"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"); });
我們可以看到,我們的應(yīng)用程序模塊是非?;A(chǔ)的。 它包含一個(gè)簡單的Express服務(wù)器,它注冊(cè)了一些中間件和authController導(dǎo)出的兩條路由。這也就是一個(gè)簡單的包含controller和model的Web服務(wù),添加好前端HTML頁面,也就實(shí)現(xiàn)了MVC架構(gòu)的分離
模式的特征模式闡述了在特定設(shè)計(jì)情形下反復(fù)出現(xiàn)的問題,并提供了解決方案。
模式記錄了已得到充分證明的既有設(shè)計(jì)經(jīng)驗(yàn)。
模式描述了超越類、實(shí)例和組件的抽象。
模式提供了一種通用語言,并讓大家對(duì)設(shè)計(jì)原則有一致的認(rèn)識(shí)。
模式是一種記錄軟件架構(gòu)的手段。
模式有助于創(chuàng)建具有指定特征的軟件。
模式有助于打造復(fù)雜而異質(zhì)的軟件架構(gòu)。
模式有助于控制軟件的復(fù)雜度。
為什么叫模式每個(gè)模式都包含三部分:
背景(Context) 問題出現(xiàn)的背景;
問題(Problem) 該背景下反復(fù)出現(xiàn)的問題;
解決方案(Solution) 經(jīng)過實(shí)踐檢驗(yàn)的解決之道。
背景背景描繪了問題發(fā)生的情形,讓原本平淡無奇的問題—解決方案更為豐滿。模式的背景可能非?;\統(tǒng),如“開發(fā)帶人機(jī)界面的軟件”,也可能將具體的模式聯(lián)系在一起,如“在模型、視圖和控制器之間實(shí)現(xiàn)變更傳播機(jī)制”。
問題模式描述綱要的這部分闡述了給定背景下反復(fù)出現(xiàn)的問題。它以籠統(tǒng)的問題陳述開始,闡述了問題的本質(zhì):必須解決的具體設(shè)計(jì)問題是什么?例如,Model-View-Controller模式解決的是用戶界面頻繁變更的問題。模式表示解決問題時(shí)需要考慮的方方面面:
解決方案必須滿足的需求,如進(jìn)程之間的對(duì)等通信必須高效;
必須考慮的約束條件,如進(jìn)程間通信必須遵守特定協(xié)議;
解決方案必須具備的特征,如應(yīng)該能夠輕松地修改軟件。
Model-View-Controller模式說明了兩種作用力:修改用戶界面應(yīng)輕而易舉,且這種修改不應(yīng)影響軟件的核心功能。
解決方案模式的解決方案部分指出了如何解決反復(fù)出現(xiàn)的問題,更準(zhǔn)確地說是如何平衡相關(guān)的作用
力。在軟件架構(gòu)中,這樣的解決方案包括兩個(gè)方面:
每個(gè)模式都指定了特定的結(jié)構(gòu),即元素的空間配置。例如,Model-View-Controller模式的描述中有這樣一句話:“將交互式應(yīng)用程序分成三部分——處理、輸出和輸入?!?/p>
每個(gè)模式都說明了運(yùn)行階段的行為。例如,在Model-View-Controller模式的“解決方案”部分有這樣一句話:“控制器接受輸入,這通常是表示鼠標(biāo)移動(dòng)、鼠標(biāo)按鈕激活或鍵盤輸入的事件。事件被轉(zhuǎn)換為服務(wù)請(qǐng)求,而服務(wù)請(qǐng)求要么被發(fā)送給模型,要么被發(fā)送給視圖?!?/p> 模式的類型
模式一般分為三類:
架構(gòu)模式:具體軟件架構(gòu)的模板,描繪了應(yīng)用程序的系統(tǒng)級(jí)結(jié)構(gòu)特征,并將影響子系統(tǒng)的架構(gòu)。例如Model-View-Controller模式
設(shè)計(jì)模式:是一種中型模式,規(guī)模比架構(gòu)模式小,但通常獨(dú)立于編程語言和編程范式。應(yīng)用設(shè)計(jì)模式不會(huì)影響軟件系統(tǒng)的基本架構(gòu),但可能嚴(yán)重影響子系統(tǒng)的架構(gòu)。例如:觀察者模式。
成例:如何解決特定的設(shè)計(jì)問題。針對(duì)于特定的語言的模式。例如C++語言的Counted Body模式。
總結(jié)模式提供了一種前途無量的方法,可用于開發(fā)具有指定特征的軟件。它們記錄了既有的設(shè)計(jì)知識(shí),有助于找到設(shè)計(jì)問題的妥善解決方案。模式的規(guī)模和抽象程度各異,涵蓋了眾多重要的軟件開發(fā)領(lǐng)域。模式彼此交織在一起,我們可以使用一個(gè)模式來改善另一個(gè)更大的模式,還可結(jié)合使用多個(gè)模式來解決復(fù)雜的問題。模式論述了軟件架構(gòu)的一些重要方面,并給既有技術(shù)和方法提供了補(bǔ)充。模式可以和任何編程范式結(jié)合使用,且?guī)缀蹩墒褂萌魏尉幊陶Z言實(shí)現(xiàn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/92438.html
摘要:前言這里筑夢(mèng)師是一名正在努力學(xué)習(xí)的開發(fā)工程師目前致力于全棧方向的學(xué)習(xí)希望可以和大家一起交流技術(shù)共同進(jìn)步用簡書記錄下自己的學(xué)習(xí)歷程個(gè)人學(xué)習(xí)方法分享本文目錄更新說明目錄學(xué)習(xí)方法學(xué)習(xí)態(tài)度全棧開發(fā)學(xué)習(xí)路線很長知識(shí)拓展很長在這里收取很多人的建議以后決 前言 這里筑夢(mèng)師,是一名正在努力學(xué)習(xí)的iOS開發(fā)工程師,目前致力于全棧方向的學(xué)習(xí),希望可以和大家一起交流技術(shù),共同進(jìn)步,用簡書記錄下自己的學(xué)習(xí)歷程...
摘要:前言這里筑夢(mèng)師是一名正在努力學(xué)習(xí)的開發(fā)工程師目前致力于全棧方向的學(xué)習(xí)希望可以和大家一起交流技術(shù)共同進(jìn)步用簡書記錄下自己的學(xué)習(xí)歷程個(gè)人學(xué)習(xí)方法分享本文目錄更新說明目錄學(xué)習(xí)方法學(xué)習(xí)態(tài)度全棧開發(fā)學(xué)習(xí)路線很長知識(shí)拓展很長在這里收取很多人的建議以后決 前言 這里筑夢(mèng)師,是一名正在努力學(xué)習(xí)的iOS開發(fā)工程師,目前致力于全棧方向的學(xué)習(xí),希望可以和大家一起交流技術(shù),共同進(jìn)步,用簡書記錄下自己的學(xué)習(xí)歷程...
摘要:只能在不同的時(shí)候選用不同的假設(shè)和不同的理論來解釋問題,許來西的文章講到科學(xué)一定程度上通過放棄一貫性換取了實(shí)用性,放棄自洽性換取了它洽性。然而遺憾的是本身只提供了模塊和洋蔥模型的最小封裝。 在寫干貨之前,我想先探(qiang)討(diao)兩個(gè)問題,模式的局限性?模式有什么用? 最近看到一篇文章對(duì)我啟發(fā)很大,許來西在知乎的回答《哲學(xué)和科學(xué)有什么關(guān)聯(lián)?》,全篇較長,這里摘錄我要引出的一點(diǎn):...
閱讀 1333·2021-09-01 10:30
閱讀 2260·2021-07-23 10:38
閱讀 964·2019-08-29 15:06
閱讀 3211·2019-08-29 13:53
閱讀 3324·2019-08-26 11:54
閱讀 1896·2019-08-26 11:38
閱讀 2435·2019-08-26 10:29
閱讀 3187·2019-08-23 18:15