摘要:模式記錄了已得到充分證明的既有設計經驗。模式有助于創建具有指定特征的軟件。每個模式都說明了運行階段的行為。應用設計模式不會影響軟件系統的基本架構,但可能嚴重影響子系統的架構。成例如何解決特定的設計問題。
學了這么久的設計模式,最近一直在看Node.js的設計模式,一直納悶為何會有模式這一類東西的存在,那么模式究竟是什么東西?后面在看了《面向模式的軟件架構》之后才慢慢知道有了一些系統的概念。
模式是什么?面對特定問題時,專家很少去尋找與既有解決方案截然不同的新方案,而通常會想起一個以前解決過的類似問題,并將其解決方案的精髓用于解決這個新問題。
從特定問題—解決方案中提煉出通用的因素便可得到模式:這些問題—解決方案通常是一系列熟悉的問題和解決方案,其中每對問題—解決方案都呈現出相同的模式。
Model-View-Controller模式MVC模式大量用在現代軟件開發流程中,為何會有MVC模式的存在,來看這一個例子:開發帶人機界面的軟件。
用戶界面需求容易變化。例如,添加應用程序功能時,必須修改菜單以便能夠訪問新功能,還可能需要針對特定客戶調整用戶界面。系統可能需要移植到另一個平臺,而該平臺采用的“外觀”標準完全不同。即便是升級到新的窗口系統版本,也可能需要修改代碼。總之,如果系統的使用壽命很長,可能經常需要修改用戶界面。設計靈活的系統時,讓用戶界面與功能核心緊密地交織在一起將付出高昂的代價,且容易出錯。這樣做的后果是,可能需要開發和維護多個大不相同的軟件系統——每種用戶界面實現一個,且修改將涉及眾多不同的模塊。總之,開發這種交互式軟件系統時,必須考慮如下兩個方面:
應該能夠輕松地修改用戶界面,在運行階段就能完成;
調整或移植用戶界面時,不應影響到應用程序功能核心的代碼。
為解決這種問題,應將交互式應用程序劃分成三部分:處理、輸出和輸入。
模型(model)組件封裝核心數據和功能,獨立于輸出表示方式和輸入行為。
視圖(view)組件向用戶顯示信息。視圖從模型那里獲取它顯示的信息,一個模型可以
有多個視圖。
每個視圖都有相關聯的控制器(controller)組件。控制器接受輸入,通常是表示鼠標移動、鼠標按鈕激活或鍵盤輸入的事件。事件被轉換為服務請求,而服務請求要么被發送給模型,要么被發送給視圖。用戶只通過控制器與系統交互。
通過將模型與視圖和控制器組件分開,讓同一個模型可以有多個視圖。如果用戶通過一個視圖的控制器修改了模型,這種變更應在依賴相關數據的其他所有視圖中反映出來。為此,每當模型的數據發生變化時,它都會通知所有視圖,而視圖將從模型那里檢索新數據,并更新顯示的信息。這種解決方案確保了修改應用程序的一個子系統時不會嚴重影響其他子系統。例如,可將非圖形用戶界面改成圖形用戶界面而無需修改模型子系統,還可支持新的輸入設備而不影響信息的顯示和功能核心。所有軟件版本都可依賴同一個模型子系統,該子系統獨立于“外觀”。
用Model-View-Controller模式實現一個鑒權服務我們從下圖所示的結構開始分析:
上圖顯示了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來存儲用戶的數據。
db模塊我們先從底層開始構建應用程序;首先實現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來修飾實例,通過這一模塊實現了增刪查改數據庫。模塊導出的對象是數據庫對象本身。
authService模塊現在我們有了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作為參數并驗證其有效性。
authController模塊繼續在應用程序的層次上,我們現在要看看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請求和響應。
app模塊最后,在應用程序的入口點,我們調用我們的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模式。
總結模式提供了一種前途無量的方法,可用于開發具有指定特征的軟件。它們記錄了既有的設計知識,有助于找到設計問題的妥善解決方案。模式的規模和抽象程度各異,涵蓋了眾多重要的軟件開發領域。模式彼此交織在一起,我們可以使用一個模式來改善另一個更大的模式,還可結合使用多個模式來解決復雜的問題。模式論述了軟件架構的一些重要方面,并給既有技術和方法提供了補充。模式可以和任何編程范式結合使用,且幾乎可使用任何編程語言實現。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92438.html
摘要:前言這里筑夢師是一名正在努力學習的開發工程師目前致力于全棧方向的學習希望可以和大家一起交流技術共同進步用簡書記錄下自己的學習歷程個人學習方法分享本文目錄更新說明目錄學習方法學習態度全棧開發學習路線很長知識拓展很長在這里收取很多人的建議以后決 前言 這里筑夢師,是一名正在努力學習的iOS開發工程師,目前致力于全棧方向的學習,希望可以和大家一起交流技術,共同進步,用簡書記錄下自己的學習歷程...
摘要:只能在不同的時候選用不同的假設和不同的理論來解釋問題,許來西的文章講到科學一定程度上通過放棄一貫性換取了實用性,放棄自洽性換取了它洽性。然而遺憾的是本身只提供了模塊和洋蔥模型的最小封裝。 在寫干貨之前,我想先探(qiang)討(diao)兩個問題,模式的局限性?模式有什么用? 最近看到一篇文章對我啟發很大,許來西在知乎的回答《哲學和科學有什么關聯?》,全篇較長,這里摘錄我要引出的一點:...
閱讀 1246·2021-09-01 10:30
閱讀 2118·2021-07-23 10:38
閱讀 895·2019-08-29 15:06
閱讀 3151·2019-08-29 13:53
閱讀 3277·2019-08-26 11:54
閱讀 1822·2019-08-26 11:38
閱讀 2370·2019-08-26 10:29
閱讀 3128·2019-08-23 18:15