摘要:只能在不同的時(shí)候選用不同的假設(shè)和不同的理論來(lái)解釋問(wèn)題,許來(lái)西的文章講到科學(xué)一定程度上通過(guò)放棄一貫性換取了實(shí)用性,放棄自洽性換取了它洽性。然而遺憾的是本身只提供了模塊和洋蔥模型的最小封裝。
在寫干貨之前,我想先探(qiang)討(diao)兩個(gè)問(wèn)題,模式的局限性?模式有什么用?
最近看到一篇文章對(duì)我啟發(fā)很大,許來(lái)西在知乎的回答《哲學(xué)和科學(xué)有什么關(guān)聯(lián)?》,全篇較長(zhǎng),這里摘錄我要引出的一點(diǎn):
科學(xué)作為一種經(jīng)驗(yàn)主義的認(rèn)識(shí)論,有著經(jīng)驗(yàn)主義的巨大缺陷:它永遠(yuǎn)不能產(chǎn)生絕對(duì)正確的真理。這是歸納法的本質(zhì)決定的。而且值得注意的是,歸納不具有唯一性。
舉一個(gè)簡(jiǎn)單的例子,我們假設(shè)一個(gè)世界,如下圖:
科學(xué)家很快有了兩種歸納方式:
世界上所有的青蛙都戴眼鏡
世界上所有戴眼鏡的都是青蛙
在沒(méi)有更多的信息的時(shí)候,我們應(yīng)該如何選擇正確的理論呢?答案是無(wú)法選擇。
舉個(gè)模式的例子,Scott Wlaschin 在《Functional Programming Design Patterns》(函數(shù)型編程模式)中對(duì)比了常用面向?qū)ο竽J?、原則,在函數(shù)型編程語(yǔ)言里面等價(jià)實(shí)現(xiàn):
OOP 和 FP,到底哪種編程范式更加先進(jìn)呢?答案同樣是無(wú)法選擇。只能在不同的時(shí)候選用不同的假設(shè)和不同的理論來(lái)解釋問(wèn)題,許來(lái)西的文章講到科學(xué)一定程度上通過(guò)放棄一貫性換取了實(shí)用性,放棄自洽性換取了它洽性??茖W(xué)追求實(shí)用和工具(實(shí)用主義和工具主義)。當(dāng)我看完許來(lái)西的文章,欣喜若狂,一直對(duì)編程技術(shù)理論的善變和不自洽感到恐懼和厭惡,其實(shí)只是經(jīng)驗(yàn)主義科學(xué)發(fā)展的必然過(guò)程,善變代表更好的理論(更方便)在替換基礎(chǔ)理論,代表蓬勃發(fā)展。
所以我想引入第一個(gè)觀點(diǎn):
模式是一套立足于特定背景,基于共性總結(jié)出的方案,它絕不是真理。
了解這些有助于幫助從對(duì)模式的盲目崇拜到探究它的實(shí)用性和工具性,也就是我要引出的第二個(gè)問(wèn)題:模式有什么用?
不好好寫代碼看哲學(xué)文章不是偶然,在文章落筆之前,我有思考過(guò)在 JavaScript 這門動(dòng)態(tài),多范式,單線程,基于事件I/O的語(yǔ)言環(huán)境下,甚至在當(dāng)前時(shí)代,模式是否還有意義?顯然我不是唯一這樣想的,還有篇深度好文《20年前GoF提出的設(shè)計(jì)模式,對(duì)這個(gè)時(shí)代是否還有指導(dǎo)意義?》。這篇文章引經(jīng)據(jù)典,摘錄了GoF(又稱Gang of Four,即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)在設(shè)計(jì)模式一書(shū)中觀點(diǎn):
這本書(shū)的實(shí)際價(jià)值也許還值得商榷。畢竟它并沒(méi)有提出任何前所未有的算法或者編程技術(shù)。它也沒(méi)能給出任何嚴(yán)格的系統(tǒng)設(shè)計(jì)方法或者新的設(shè)計(jì)開(kāi)發(fā)理論——它只是對(duì)現(xiàn)有設(shè)計(jì)成果的一種審視。大家當(dāng)然可以將其視為一套不錯(cuò)的教程,但它顯然無(wú)法為經(jīng)驗(yàn)豐富的面向?qū)ο笤O(shè)計(jì)人員帶來(lái)多少幫助。
換言之,模式顯然毫無(wú)實(shí)際用處。
不僅如此,文章還列舉了一度模式濫用導(dǎo)致許多弊端,可謂警鐘長(zhǎng)鳴。
但是……模式這一稱謂仍然不斷出現(xiàn),直到今天我們亦在大量使用。為什么?GoF實(shí)際早設(shè)計(jì)模式的書(shū)中做出了預(yù)言:
“設(shè)計(jì)模式為設(shè)計(jì)師們提供一種共通的詞匯儲(chǔ)備,幫助其溝通、編寫文檔并探索設(shè)計(jì)方案。設(shè)計(jì)模式允許我們立足于高級(jí)抽象層面進(jìn)行探討,而非設(shè)計(jì)標(biāo)注或者編程語(yǔ)言,這就大大降低了系統(tǒng)復(fù)雜性。設(shè)計(jì)模式提升了我們?cè)O(shè)計(jì)及與同事進(jìn)行設(shè)計(jì)探討時(shí)的切入點(diǎn)層級(jí)?!保ǖ?89頁(yè))
簡(jiǎn)言之,模式方便了我們的溝通,提升了思考問(wèn)題的抽象層級(jí)。
這個(gè)意義非常巨大,想象一下沒(méi)有 MVC 架構(gòu)模式,可能所有的 Web 框架必然的會(huì)實(shí)現(xiàn)一套幾乎解決同樣問(wèn)題的方案,但是命名和文檔卻各不一樣,當(dāng)你去看一個(gè)新的框架文檔的api 接口,從頭到尾看完以后才恍然大悟,這不就是之前用的框架里面的 XXX 類似嗎,這樣的編程世界簡(jiǎn)直地獄。慶幸的是,得益于計(jì)算機(jī)科學(xué)家(碼農(nóng))對(duì)問(wèn)題和方案持續(xù)的抽象成模式,使得當(dāng)前高度復(fù)雜的計(jì)算機(jī)科學(xué)也能得到合理分層和適配,大大簡(jiǎn)化了學(xué)習(xí)和溝通的成本。
為了感謝模式,是時(shí)候?qū)W習(xí)一波了,本文要介紹的主要有三種架構(gòu)模式:Middleware,MVC,DI。
Middleware 中間件模式相信做過(guò) Node.js 服務(wù)端開(kāi)發(fā)的同學(xué)對(duì)這個(gè)模式一定不陌生,考慮如下 Web 應(yīng)用的場(chǎng)景:
在一個(gè)簡(jiǎn)單的 HTTP 請(qǐng)求響應(yīng)周期里,有如下條件處理,
記錄開(kāi)始時(shí)間
需要驗(yàn)證用戶的身份 authentication。
解析cookie 并加載body
根據(jù)路由返回不同的業(yè)務(wù)處理結(jié)果
沒(méi)有命中路由則返回404頁(yè)面
記錄日志
記錄總共花費(fèi)時(shí)間
處理異常并顯示頁(yè)面(開(kāi)發(fā)環(huán)境)
有些處理會(huì)根據(jù)是否成功決定是否繼續(xù)后面的粗粒,有些處理會(huì)生成額外的數(shù)據(jù),還有的要求攔截某些處理的開(kāi)始和結(jié)束,最后異常處理和記錄日志要求一定被執(zhí)行。
一般的解決方法是用嵌套條件判斷結(jié)合 try catch finally return 等控制語(yǔ)句,但是這樣的方案會(huì)導(dǎo)致代碼碎片化和復(fù)制粘貼的編碼風(fēng)格,因?yàn)榭刂屏骱瓦壿嬹詈系搅艘黄?。理想的方案?yīng)當(dāng)如下:
中心化控制流
解耦處理模塊(重用性)
聲明式、可配置的服務(wù)(配置和代碼無(wú)關(guān))
這些場(chǎng)景由來(lái)已久,很久以前J2EE總結(jié)了 Intercepting Filter 模式,有興趣大家可以看看這篇文(lun)章(wen),里面由淺入深提到三種方案,其中最初級(jí)的方案代碼如下:
public class DebuggingFilter implements Processor { private Processor target; public DebuggingFilter(Processor myTarget) { target = myTarget; } public void execute(ServletRequest req, ServletResponse res) throws IOException, ServletException { // preprocess target.execute(req, res); // post-process } }
這個(gè)和 express 和 Koa 的中間件模式極其相似,但是因?yàn)殪o態(tài)語(yǔ)言本身一些特征,導(dǎo)致最后形成的企業(yè)級(jí)代碼極其繁瑣,并且有許多局限性。最主要的問(wèn)題是處理模塊之間難以重用和共享數(shù)據(jù),因?yàn)?ServletRequest ServletResponse 無(wú)法動(dòng)態(tài)添加屬性。以至于 JavaEE 把這個(gè)模式的適用性加了許多限制,包括和核心處理邏輯分開(kāi)。
在動(dòng)態(tài)語(yǔ)言的世界里面,我們可以很方便的往 req 和 res 里面添加數(shù)據(jù)(基于約定),因?yàn)闆](méi)有了很多 OOP 世界里面的”束縛“,Node.js 的實(shí)現(xiàn)通常更加優(yōu)雅和通用。
Express 中間件模式express 實(shí)現(xiàn)如今廣泛接受的 Middleware 中間件模式。中間件的意思是在 請(qǐng)求 和 響應(yīng) 中間執(zhí)行的函數(shù)(為了區(qū)分另一個(gè)中間件),簽名如下:
var express = require("express"); var app = express();
這個(gè)模式包含了一套聲明式的路由規(guī)則,和 middleware 函數(shù)上的 next 簽名,它們共同構(gòu)成了整個(gè)中間件模式的控制流,如圖:
這個(gè)模式的核心構(gòu)成不是權(quán)限,解析等中間件邏輯,而是路由判斷,next和中斷響應(yīng)(驗(yàn)證失敗、解析失?。?/b>,其作為中間件執(zhí)行控制,解耦了具體的處理邏輯,使得更容易寫出通用的細(xì)粒度的中間件。express 內(nèi)置的強(qiáng)大的聲明式路由,并且路由和 middleware 分離可以說(shuō)是它最成功的設(shè)計(jì)之一。
然而在一些稍微復(fù)雜點(diǎn)的業(yè)務(wù)中,比如一個(gè)網(wǎng)站有管理端和用戶端,兩個(gè)端相當(dāng)于獨(dú)立的app。express 4.0 提供了一個(gè)非常強(qiáng)大的功能 Router。Router 拓展了鏈?zhǔn)經(jīng)Q策變成樹(shù)形決策,可以讓 express 更好的支持大型項(xiàng)目。
/* 文件 bird.js */ var express = require("express") var router = express.Router() router.get("/", function (req, res) { res.send("Birds home page") }) module.exports = router /* 文件 app.js */ var birds = require("./birds") // ... app.use("/birds", birds)Koa 異步中間件模型
Koa 的異步中間件模式-洋蔥模型,相比 Express,其中間件函數(shù)返回 Promise,支持 async/await,并且可以輕松實(shí)現(xiàn)前置和后置的處理。毫無(wú)疑問(wèn)這個(gè)模式更加先進(jìn),一些在 express 里面不好實(shí)現(xiàn)的攔截處理邏輯,比如異常處理和統(tǒng)計(jì)時(shí)間,在 Koa 里用一個(gè)中間件就能搞定。然而遺憾的是 Koa 本身只提供了 Http 模塊和洋蔥模型的最小封裝。
未來(lái)我看好 Koa,其實(shí) express 也意識(shí)到這點(diǎn),他們計(jì)劃在 5.0 版本里添加 Promise 的支持,然而作為一個(gè)老牌和完整生態(tài)的框架,要克服的困難遠(yuǎn)不是技術(shù)層面上看似的簡(jiǎn)單,直到目前仍然沒(méi)有看到 5.x 宣布支持 Promise, 讓我們拭目以待。
MVC 模式MVC 模式也需要介紹嗎,我們天天都在聊 MVC,不管前、后端框架,說(shuō)一句 MVC,對(duì)一下眼神,基本確定對(duì)方懂你了。
事實(shí)是,前端框架已經(jīng)不適合用 MVC 討論,這個(gè)模式從1979年提出以來(lái),作為萬(wàn)精油模式,在各個(gè)框架和場(chǎng)景中被套用,背負(fù)了太多的歷史包袱,大家可以看 winter 的文章 談?wù)刄I架構(gòu)設(shè)計(jì)的演化。撥亂反正我覺(jué)得有希望,討論前端框架大家以后統(tǒng)稱 MV 模式就好了,就是模型和視圖分離。
我們今天要講的 MVC 模式是指在服務(wù)器上(后端) MVC 模式,它的定義經(jīng)受了時(shí)間和實(shí)踐的檢驗(yàn),在許多企業(yè)級(jí) Web 框架的實(shí)現(xiàn)中高度一致。先列舉場(chǎng)景:
如果你的網(wǎng)站只有幾個(gè)簡(jiǎn)單的頁(yè)面,所有邏輯都寫在 Controller 里面,是沒(méi)有問(wèn)題的。隨后網(wǎng)站迅速的增長(zhǎng),你發(fā)現(xiàn),
許多頁(yè)面里面的視圖是一致的,但是背后的數(shù)據(jù)模型不一致。比如:網(wǎng)站上幾乎沒(méi)有一個(gè)視圖或者組件是獨(dú)一無(wú)二的,表格,下拉框等。
許多頁(yè)面里面的數(shù)據(jù)模型是一樣的,但是展現(xiàn)的視圖不一致。比如:同時(shí)支持PC和移動(dòng)端,國(guó)際化本地化。
我們做一個(gè)數(shù)學(xué)模型模擬極端情況,大家很容易能看到問(wèn)題
假設(shè)左邊是我們的系統(tǒng)最終的樣子,它剛好可以表示成 M(模型)和 V(視圖)的內(nèi)積,我們更傾向于右邊的表達(dá),因?yàn)樗?jiǎn)潔而且沒(méi)有重復(fù)。這里的內(nèi)積操作大家就可以理解成控制器,實(shí)際上不會(huì)如此巧合,但是分離模型和視圖幫助我們提高代碼復(fù)用,降低設(shè)計(jì)復(fù)雜度的好處是很顯然的,一個(gè)更通用的表達(dá)
模型視圖和控制器之間都是單向鏈接,所以整個(gè)系統(tǒng)的行為非??煽厍胰菀诇y(cè)試,多帶帶把路由分開(kāi)是想強(qiáng)調(diào) Router 和 Controller 是兩個(gè)概念,Router 只是一個(gè)觸發(fā)器(或者提供了一種映射關(guān)系),在寫測(cè)試的時(shí)候,我們也可以跳開(kāi) Router 多帶帶調(diào)用 Controller。
看到上面的兩種模式,是不是已經(jīng)開(kāi)始想,那有沒(méi)有一個(gè)框架同時(shí)是 Koa + Router + MVC 呢,推薦大家一個(gè)非常好用的企業(yè)級(jí) Web 框架 ThinkJS 3.0,最新版的 ThinkJS 集成了大量最佳實(shí)踐和完善的文檔,不管是學(xué)習(xí)或者企業(yè)級(jí)開(kāi)發(fā)都非常推薦。而且 ThinkJS 同樣實(shí)現(xiàn)了接下來(lái)要講的模式。
DI 依賴注入模式還是先說(shuō)場(chǎng)景,假如服務(wù)端需要實(shí)現(xiàn)session,前期考慮到成本和用戶量,單臺(tái)服務(wù)器存到文件就夠用了。后期如果用戶量大的時(shí)候,需要橫向擴(kuò)展(Scale-out),就把 session 實(shí)現(xiàn)基于中心化的 Redis 服務(wù)。
我們系統(tǒng)設(shè)計(jì)目標(biāo)是:
不需要修改業(yè)務(wù)邏輯代碼實(shí)現(xiàn)替換
不需要關(guān)注服務(wù)的創(chuàng)建和生命周期
解決這類系統(tǒng)擴(kuò)展性問(wèn)題有一個(gè)非常著名的設(shè)計(jì)原則 控制反轉(zhuǎn)(IoC Inversion of control),而 依賴注入(DI dependency injection) 就是其中的一個(gè)實(shí)現(xiàn)模式。
DI 的基本思路是這樣,首先我們的代碼不能依賴具體的服務(wù),需要總結(jié)歸納出一套抽象接口,業(yè)務(wù)實(shí)現(xiàn)依賴接口,而服務(wù)實(shí)現(xiàn)接口,最后通過(guò)框架專門負(fù)責(zé)創(chuàng)建和提供接口的實(shí)例。
這里的 IoC 容器或者說(shuō) Ioc 框架,會(huì)在啟動(dòng)的時(shí)候讀取配置文件,并在運(yùn)行的時(shí)候根據(jù)需要?jiǎng)?chuàng)建實(shí)例提供給使用者,在靜態(tài)語(yǔ)言如 java,c# 需要用到反射等高級(jí)語(yǔ)法,而 JavaScript 本身是動(dòng)態(tài)的,接口基于約定,并且使用的方式也更加靈活。比如 ThinkJS 3.0 里面的 extend 和 adapter 就可以理解成接口和實(shí)現(xiàn),如圖:
那之所以稱為 extend,是因?yàn)榭蚣軙?huì)直接把接口注入到 controller 或者 think 對(duì)象中。這樣的好處是使用起來(lái)更方便,缺點(diǎn)是不同 extend 需要約定好不能重名。
最后本文介紹的三個(gè)架構(gòu)模式,你會(huì)發(fā)現(xiàn)幾乎在所有的Web框架實(shí)現(xiàn)都大同小異,這就是模式的好處。模式的意義類似于 IoC,我關(guān)注抽象和接口,抹平了具體語(yǔ)言特性下的細(xì)節(jié)問(wèn)題,幫助我們更好的學(xué)習(xí),溝通和思考。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95023.html
摘要:前言這里筑夢(mèng)師是一名正在努力學(xué)習(xí)的開(kāi)發(fā)工程師目前致力于全棧方向的學(xué)習(xí)希望可以和大家一起交流技術(shù)共同進(jìn)步用簡(jiǎn)書(shū)記錄下自己的學(xué)習(xí)歷程個(gè)人學(xué)習(xí)方法分享本文目錄更新說(shuō)明目錄學(xué)習(xí)方法學(xué)習(xí)態(tài)度全棧開(kāi)發(fā)學(xué)習(xí)路線很長(zhǎng)知識(shí)拓展很長(zhǎng)在這里收取很多人的建議以后決 前言 這里筑夢(mèng)師,是一名正在努力學(xué)習(xí)的iOS開(kāi)發(fā)工程師,目前致力于全棧方向的學(xué)習(xí),希望可以和大家一起交流技術(shù),共同進(jìn)步,用簡(jiǎn)書(shū)記錄下自己的學(xué)習(xí)歷程...
摘要:前言這里筑夢(mèng)師是一名正在努力學(xué)習(xí)的開(kāi)發(fā)工程師目前致力于全棧方向的學(xué)習(xí)希望可以和大家一起交流技術(shù)共同進(jìn)步用簡(jiǎn)書(shū)記錄下自己的學(xué)習(xí)歷程個(gè)人學(xué)習(xí)方法分享本文目錄更新說(shuō)明目錄學(xué)習(xí)方法學(xué)習(xí)態(tài)度全棧開(kāi)發(fā)學(xué)習(xí)路線很長(zhǎng)知識(shí)拓展很長(zhǎng)在這里收取很多人的建議以后決 前言 這里筑夢(mèng)師,是一名正在努力學(xué)習(xí)的iOS開(kāi)發(fā)工程師,目前致力于全棧方向的學(xué)習(xí),希望可以和大家一起交流技術(shù),共同進(jìn)步,用簡(jiǎn)書(shū)記錄下自己的學(xué)習(xí)歷程...
摘要:歡迎使用中文文檔架構(gòu)概覽是網(wǎng)易項(xiàng)目團(tuán)隊(duì)開(kāi)發(fā)的一個(gè)基于進(jìn)行開(kāi)發(fā)的應(yīng)用層框架,提供了一個(gè)輕量級(jí)的容器來(lái)編寫簡(jiǎn)單可維護(hù)的。 JavaScript 可以……嘛,不就是操作一下 DOM,可以讓元素飛來(lái)飛去嗎JavaScript 是……不就是用 jQuery 讓網(wǎng)頁(yè)動(dòng)起來(lái),頂多就是再用用 Ajax 和后端進(jìn)行一下數(shù)據(jù)交換嗎JavaScript 是一門……最討厭和鄙視這種弱類型不需要編譯的腳本語(yǔ)言...
閱讀 4119·2021-09-24 10:24
閱讀 1439·2021-09-22 16:01
閱讀 2753·2021-09-06 15:02
閱讀 1051·2019-08-30 13:01
閱讀 1034·2019-08-30 10:52
閱讀 657·2019-08-29 16:36
閱讀 2266·2019-08-29 12:51
閱讀 2376·2019-08-28 18:29