聊聊軟件的分層設(shè)計(jì)思想(1)基礎(chǔ)

分層思想是一種軟件工程方法中最常用的思想,本系列力圖通過直白的語(yǔ)言來探討這種思想背后的故事。
說到軟件工程,很多人學(xué)軟件工程就只是在學(xué)校里學(xué)了一堆過時(shí)的技術(shù),踏上工作崗位后卻發(fā)現(xiàn)大部分的技術(shù)都很難用得上,也因此很多人都在質(zhì)疑軟件工程的價(jià)值。
這種現(xiàn)象是有原因的,軟件工程是一門年輕的學(xué)科,實(shí)際開發(fā)中的工程方法一直在變化,很多時(shí)候當(dāng)工程方法形成教材的時(shí)候就已經(jīng)過時(shí)了;另外因?yàn)檐浖幌衿渌I(lǐng)域有物理空間、自然法則等的約束(可能唯一的約束就是摩爾定律了),夸張點(diǎn)說擁有無限的可能性,而在實(shí)際應(yīng)用中大部分軟件都是直接面向“人”的問題的,任何問題只要一有“人”的因素,復(fù)雜度就會(huì)成倍的增加,因此也就很難像其他領(lǐng)域的工程學(xué)一樣把大部分情況都照顧到并形成一致的規(guī)范。所以想要通過學(xué)習(xí)一些規(guī)范就能成為一名合格的工程師是不可能的。
軟件工程中缺少普適性的規(guī)范(當(dāng)然現(xiàn)在也正在不同的細(xì)分領(lǐng)域中逐步探索),很多時(shí)候現(xiàn)有的規(guī)范和方法都只能當(dāng)作一種參考,在實(shí)踐中更需要?jiǎng)?chuàng)造性,如果只會(huì)死記硬背所謂的規(guī)范而不去理解背后的思想是沒有意義的。
從MVC說起

MVC是一種重用戶交互的軟件架構(gòu)模式,基于MVC的思想還衍生出MVP、MVVM等模式,當(dāng)然你也可以說是架構(gòu)風(fēng)格、框架,這些稱呼無所謂,總之這是一種分層思想的典型產(chǎn)物。
提到MVC很多人可能會(huì)想到不同的技術(shù),后端的有SSH、SSM、ASP.NET MVC,前端的有AngularJS、VueJS,以及ReactiveX系列的框架。
當(dāng)然這篇不說具體技術(shù),我只想通過MVC來聊聊為什么?我們?yōu)槭裁葱枰謱樱?/p>
為什么需要MVC,或者說用MVC有什么好處,很多書本中都給出了一些標(biāo)準(zhǔn)答案,大多是圍繞著“復(fù)用”這個(gè)關(guān)鍵詞進(jìn)行解釋的;還有一個(gè)解釋是通過明確的分層能夠使模型、視圖和控制器能夠獨(dú)立的變化,修改其中一層中的代碼不會(huì)影響其他層的代碼;在一些老的書本中還會(huì)說因?yàn)镸、V、C各自獨(dú)立以后可以通過替換視圖(V)層的方式把 C/S 架構(gòu)的程序重構(gòu)成 B/S 架構(gòu)。
但我對(duì)這些解釋很不滿意,比如,通過替換視圖層來把 C/S 架構(gòu)的程序重構(gòu)成 B/S 架構(gòu)這種說法根本是無稽之談。且不說根本不會(huì)有哪個(gè)MVC框架會(huì)同時(shí)支持C/S、B/S兩種架構(gòu),即便是有,在實(shí)際工作中也很難遇到這種需求,即便在實(shí)際工作中真的遇到了這種情況,那這個(gè)時(shí)候關(guān)注這種代碼級(jí)別的問題就好比一把火把房子燒了還在考慮今晚怎么刷碗一樣。
第二種說法稍微好一些,模型、視圖和控制器能夠獨(dú)立的變化,看上去很美,但實(shí)際上真的有這么大作用嗎?我們看下常見的變更會(huì)有哪些:
調(diào)整界面布局
調(diào)整數(shù)據(jù)的顯示方式
調(diào)整數(shù)據(jù)字
調(diào)整處理邏輯
對(duì)于中小型的項(xiàng)目我們可能會(huì)有如下開發(fā)模式:
MVC
RAD(基于事件的類WinForm、WebForm框架)
混合編碼(類JSP)
三種模式在面對(duì)這四種變更:

通過這個(gè)表格,能看得出來MVC的優(yōu)勢(shì)在哪嗎?
再來看看“復(fù)用”,似乎“復(fù)用”是一個(gè)很好的理由。但是稍微有點(diǎn)經(jīng)驗(yàn)的人會(huì)明白,說這種與業(yè)務(wù)緊密相關(guān)的代碼根本不可能復(fù)用有點(diǎn)絕對(duì),但也絕不是一件容易的事。實(shí)際上最容易復(fù)用的代碼大多是工具性質(zhì)的,比如各種日期、字符串、數(shù)組處理,格式轉(zhuǎn)換等代碼。
當(dāng)然分層之后模型(M)層在項(xiàng)目?jī)?nèi)部還是可以實(shí)現(xiàn)一定程度的復(fù)用的,但這就是用MVC的理由了嗎?
不,在我看來上面的這些理由都不是最關(guān)鍵的。
分層真正的理由
要了解MVC乃至分層思想的真正理由,我們首先要明白一點(diǎn),不是程序需要分層,而是我們?nèi)祟愋枰謱印_@是因?yàn)槲覀兊拇竽X的局限性:我們?cè)谒伎紗栴}的時(shí)候所用到的工作記憶容量小的可憐。工作記憶相當(dāng)于電腦中的內(nèi)存(RAM),在思考問題的時(shí)候大腦要從長(zhǎng)期記憶(相當(dāng)于硬盤)中提取信息放入工作記憶,到底有多少小呢,最早的說法是7±2個(gè)事物,但后來的研究“成功”的把這個(gè)數(shù)量降低到了4~5個(gè),感興趣的的同學(xué)可以百度一下這篇文章:
《神經(jīng)現(xiàn)實(shí):我們短期只能記4、5個(gè)事物?是什么限制了工作記憶的容量?》
那為什么我們還能處理復(fù)雜的問題呢?心理學(xué)家告訴我們,我們大腦會(huì)通過組塊來提高工作記憶的效率,組塊也就是把相關(guān)的多個(gè)信息組成一個(gè)信息塊,這樣的信息塊仍然是占用一格工作記憶,通過這種打包的方式,我們就可以同時(shí)處理更多的信息了。
為了照顧人的大腦的這個(gè)缺陷日常生活中有很多地方都做了便于組塊的設(shè)計(jì),比如1位電話號(hào)碼就是分了3組數(shù)字每組數(shù)字都不超過4位數(shù),像這樣:138 0000 0000。
看到這里,聰明的小伙伴已經(jīng)明白了我為什么說我們?nèi)祟愋枰謱恿?。分層最關(guān)鍵的作用就是幫助我們對(duì)問題進(jìn)行切分和組塊。鍵盤鼠標(biāo)等外設(shè)有個(gè)“人體工學(xué)”的說法,人體工學(xué)的目的就是使工具盡量的符合人體自然形態(tài),降低使用疲勞、提高效率。相比之下分層思想可以說是一種“人腦工學(xué)”。
其實(shí)從這里反推回其他軟件工程方法,最典型的就是面向?qū)ο?,面向?qū)ο笕齻€(gè)特征封裝、繼承、多態(tài),三個(gè)當(dāng)中最重要的特征就是封裝,封裝的關(guān)鍵作用就是方便人腦對(duì)零散的信息組塊,當(dāng)然繼承和多態(tài)可以更進(jìn)一步的提高信息密度的作用,只是這兩個(gè)的作用不如封裝來得那么直接。
分層的原則
前面說了分層的理由,其實(shí)分層的原則也是要從分層的理由出發(fā)的。分層的理由是為了方便信息組塊,那分層的原則就是分出來的層次能夠符合大多數(shù)人直覺。
這個(gè)原則可操作性并不強(qiáng),因?yàn)檫@種判斷力并不是能夠輕易獲得的,創(chuàng)造分層模式其實(shí)就是在構(gòu)建一個(gè)新的邏輯和話語(yǔ)體系,需要?jiǎng)?chuàng)造者有足夠的理論基礎(chǔ)和實(shí)踐經(jīng)驗(yàn)。
但即便不需要?jiǎng)?chuàng)造新的分層模式,只是應(yīng)用已經(jīng)存在的分層模式,也是需要這樣的判斷力的。
拿很多新人在學(xué)習(xí)階段經(jīng)常拿來練手的圖書借閱管理系統(tǒng)舉例(話說,每年各高校和培訓(xùn)機(jī)構(gòu)開發(fā)的圖書借閱系統(tǒng)也有個(gè)幾萬個(gè)了吧??),假設(shè)系統(tǒng)采用MVC框架,且增加了Service層??紤]這個(gè)場(chǎng)景:會(huì)員能夠在借書模塊中瀏覽圖書檔案模塊中的數(shù)據(jù),選出來的圖書能否借閱需要結(jié)合會(huì)員等級(jí)(決定了能同時(shí)借多少本)和圖書狀態(tài)進(jìn)行判斷,確定借出后保存借閱記錄,這些功能接口該如何安排在各個(gè)模塊的各個(gè)層次中?
我見過很多人會(huì)照葫蘆畫瓢的各自寫出各個(gè)實(shí)體的數(shù)據(jù)訪問層代碼,Service 層只是簡(jiǎn)單的調(diào)用數(shù)據(jù)訪問層 CRUD 代碼,然后在 Controller 中寫出相關(guān)邏輯判斷,然后調(diào)用 View 層渲染顯示。結(jié)果是薄薄的 DAO 層和 Service 層,厚重的 Controller 層。

相對(duì)合理的方式是把各個(gè)功能分布在 Service 層中,如下所示:

這種程度的重構(gòu),相信即便是新人也能做的到,再考慮以下場(chǎng)景:
會(huì)員通過個(gè)人控制面板頁(yè)面可以查詢自己的借閱歷史
管理員通過圖書檔案的頁(yè)面可以查詢圖書的借閱歷史
Service層的代碼應(yīng)該放在哪個(gè)模塊?借閱記錄Service?還是會(huì)員信息Service和圖書檔案Service?
從這里開始就需要斟酌了,以主體為中心的思維方式會(huì)傾向于把代碼分別放在會(huì)員信息Service和圖書館檔案Service中,以客體為中心的思維方式會(huì)傾向于把代碼都放在借閱記錄Service中。
那到底哪種方式更合理一些呢?其實(shí)兩種方式都有一定的合理性,這是最麻煩的。
無論選擇哪種方式都必須要保證邏輯的一貫性,在這里用以客體為中心的思維方式把借閱歷史都放在借閱記錄 Service 中似乎最能夠保持邏輯的一貫性。
但是在項(xiàng)目達(dá)到一定規(guī)模的時(shí)候還要注意設(shè)計(jì)要能便于分工,如果圖書檔案和會(huì)員信息兩個(gè)模塊分給不同人來做,那么他們就會(huì)面臨可能會(huì)同時(shí)修改借閱記錄模塊 Service 的情況(其實(shí)這時(shí)候就引出了面向接口編程的理由)。
在實(shí)際設(shè)計(jì)中,只有一個(gè)大原則是不夠的,還要把其他因素都列舉出來并排序,然后再進(jìn)行取舍。
小結(jié)
本篇通過最常見的MVC模式簡(jiǎn)單討論了一下分層思想的原因和原則,在軟件設(shè)計(jì)中分層思想在功能設(shè)計(jì)、軟件的功能橫向擴(kuò)展和性能縱向擴(kuò)展等領(lǐng)域都有用到,我會(huì)在后續(xù)的文章中進(jìn)一步討論。