序
- 程序員的三個層次
(1) 普通程序員
編寫代碼,能夠讓程序跑起來的人。
(2) 工程師
有“潔癖”、有工匠精神、有修養(yǎng)的程序員。他們把編程當成一種設(shè)計,一種工業(yè)設(shè)計。
(3) 架構(gòu)師
解決系統(tǒng)性問題、經(jīng)驗足、不怕難的工程師。 - 軟件架構(gòu)的規(guī)則其實就是排列組合代碼塊的規(guī)則。軟件架構(gòu)的規(guī)則是相同的,與應(yīng)用和系統(tǒng)無關(guān)。
第 1 部分 概述
第1章 設(shè)計與架構(gòu)究竟是什么
- 架構(gòu)即是設(shè)計。
- 軟件架構(gòu)的終極目標是,用最小的人力成本來滿足構(gòu)建和維護該系統(tǒng)的需求。
- 現(xiàn)在的軟件研發(fā)工程師都有點過于自信,但是他們真正偷懶的地方在于——持續(xù)低估那些好的、良好設(shè)計的、整潔的代碼的重要性。
第2章 兩個價值維度
- 每個軟件系統(tǒng),我們都可以通過行為和架構(gòu)兩個維度來體現(xiàn)它的實際價值。
- 行為價值
軟件系統(tǒng)的功能。 - 架構(gòu)價值
軟件系統(tǒng)容易被修改。
第 2 部分 從基礎(chǔ)構(gòu)件開始:編程范式
第3章 編程范式總覽
- 一共只有三個編程范式
(1) 結(jié)構(gòu)化編程(structured programming)
(2) 面向?qū)ο缶幊?object-oriented programming)
(3) 函數(shù)式編程(functional programming) - 結(jié)構(gòu)化編程(
structured programming)
結(jié)構(gòu)化編程對程序控制權(quán)的直接轉(zhuǎn)移進行了限制和規(guī)范。
限制
goto語句。
- 面向?qū)ο缶幊?
object-oriented programming)
面向?qū)ο缶幊虒Τ绦蚩刂茩?quán)的間接轉(zhuǎn)移進行了限制和規(guī)范。
限制函數(shù)指針。
- 函數(shù)式編程(
functional programming)
函數(shù)式編程對程序中的賦值進行了限制和規(guī)范。
限制賦值語句。
- 每個編程范式的目的都是設(shè)置限制。這些范式主要是為了告訴我們不能做什么,而不是可以做什么。
第4章 結(jié)構(gòu)化編程
- 人們可以用順序結(jié)構(gòu)、分支結(jié)構(gòu)、循環(huán)結(jié)構(gòu)這三種結(jié)構(gòu)構(gòu)造出任何程序。
-
goto是有害的
隨著編程語言的演進,goto語句的重要性越來越小,最終甚至消失了。如今大部分的現(xiàn)代編程語言中都已經(jīng)沒有了goto語句。就算那些還支持goto關(guān)鍵詞的編程語言也通常限制了goto的目標不能超出當前函數(shù)范圍。 - 測試
測試只能展示Bug的存在,并不能證明不存在Bug。
一段程序可以由一個測試來證明其錯誤性,但是卻不能被證明是正確的。測試的作用是讓我們得出某段程序已經(jīng)足夠?qū)崿F(xiàn)當前目標這一結(jié)論。 - 軟件開發(fā)雖然看起來是在操作很多數(shù)學結(jié)構(gòu),其實不是一個數(shù)學研究過程。恰恰相反,軟件開發(fā)更像是一門科學研究學科,我們通過無法證偽來證明軟件的正確性。
- 結(jié)構(gòu)化編程范式促使我們先將一段程序遞歸降解為一系列可證明的小函數(shù),然后再編寫相關(guān)的測試來視圖證明這些函數(shù)的錯誤。如果這些測試無法證偽這些函數(shù),那么我們就可以認為這些函數(shù)是足夠正確的,進而推導(dǎo)整個程序是正確的。
- 結(jié)構(gòu)化編程范式中最有價值的地方就是,它賦予我們創(chuàng)造可證偽程序單元的能力。
第5章 面向?qū)ο缶幊?/h2>
- 什么是面向?qū)ο螅?br>
數(shù)據(jù)和函數(shù)的組合?!粔蛸N切。
一種對真實世界進行建模的方式?!苤鼐洼p。
封裝、繼承、多態(tài)。——神秘術(shù)語。
第6章 函數(shù)式編程
- 函數(shù)式編程所依賴的原理, 在很多方面其實是早于編程本身出現(xiàn)的。
- 函數(shù)式編程語言中的變量是不可變的。
所有的競爭問題、死鎖問題、并發(fā)更新問題都是由可變變量導(dǎo)致的。如果變量永遠不會被更改,那就不可能產(chǎn)生競爭或者并發(fā)更新問題。如果鎖狀態(tài)是不可變的,那就永遠不會產(chǎn)生死鎖問題。
- 不可變性是否實際可行?
如果我們能忽略存儲器與處理器在速度上的限制,那么答案是肯定的。否則的話,不可變性只有在一定情況下是可行的。
第 3 部分 設(shè)計原則
-
SOLID原則
SRP:單一職責原則。
一個軟件模塊只有一個需要被改變的理由。
OCP:開放封閉原則。
如果軟件系統(tǒng)想要更容易被改變,那么其設(shè)計就必須允許新增代碼來修改系統(tǒng)行為,而非只能靠修改原來的代碼。
LSP:里氏替換原則。
如果想用可替換的組件來構(gòu)建軟件系統(tǒng),那么這些組件就必須遵守同一個約定,以便讓這些組件可以相互替換。
ISP:接口隔離原則。
軟件設(shè)計師應(yīng)該在設(shè)計中避免不必要的依賴。
DIP:依賴反轉(zhuǎn)原則。
高層策略性的代碼不應(yīng)該依賴實現(xiàn)底層細節(jié)的代碼,恰恰相反,那些實現(xiàn)底層細節(jié)的代碼應(yīng)該依賴高層策略性的代碼。
第7章 SRP:單一職責原則
- 任何一個軟件模塊都應(yīng)該有且僅有一個被修改的原因。
單一職責原則的含義并不是每個模塊應(yīng)該只做一件事。
- 任何一個軟件模塊都應(yīng)該只對某一類行為者負責。
在大部分情況下,軟件模塊就是指一個源代碼文件。
第8章 OCP:開放封閉原則
- 設(shè)計良好的計算機軟件應(yīng)該易于擴展,同時抗拒修改。
- 軟件架構(gòu)師根據(jù)相關(guān)函數(shù)被修改的原因、修改的方式及修改的時間來對其進行分組隔離,并將這些互相隔離的函數(shù)分組整理成組件結(jié)構(gòu),使得高階組件不會因低階組件被修改而受到影響。
- 軟件系統(tǒng)不應(yīng)該依賴其不直接使用的組件。
第9章 LSP:里氏替換原則
- 正方形/長方形問題是一個著名的違反
LSP的設(shè)計案例。用戶需要增加區(qū)分兩者的檢測邏輯(例如增加if語句),用戶的行為依賴它所使用的類,這兩個類就不能互相替換。
-
LSP可以且應(yīng)該被應(yīng)用于軟件架構(gòu)層面,因為一旦違背了可替換性,該系統(tǒng)架構(gòu)就不得不為此添加大量復(fù)雜的應(yīng)對機制。
第10章 ISP:接口隔離原則
- 有多個用戶需要操作
OPS類。現(xiàn)在,我們假設(shè)這里的User1只需要使用op1,User2只需要使用op2,User3只需要使用op3。
違反ISP的UML類圖:
UML類圖
符合ISP的UML類圖:
UML類圖
- 任何層次的軟件設(shè)計如果依賴于不需要的東西,都會是有害的。從源碼層次來說,這樣的依賴關(guān)系會導(dǎo)致不必要的重新編譯和重新部署,對更高層次的軟件架構(gòu)設(shè)計來說,問題也是類似的。
- 任何層次的軟件設(shè)計如果依賴了它并不需要的東西,就會帶來意料之外的麻煩。
第11章 DIP:依賴反轉(zhuǎn)原則
- 依賴反轉(zhuǎn)原則(
DIP)主要想告訴我們的是,如果想要設(shè)計一個靈活的系統(tǒng),在源代碼層次的依賴關(guān)系就應(yīng)該多引用抽象類型,而非具體實現(xiàn)。
- 每次修改抽象接口的時候,一定也會去修改對應(yīng)的具體實現(xiàn)。但反過來,當我們修改具體實現(xiàn)時,卻很少需要去修改相應(yīng)的抽象接口。所以,我們可以認為接口比實現(xiàn)更穩(wěn)定。
- 該設(shè)計原則可以歸結(jié)為以下幾條具體的編碼守則:
① 應(yīng)在代碼中多使用抽象接口,盡量避免使用那些多變的具體實現(xiàn)類。
對象的創(chuàng)建過程應(yīng)該受到嚴格限制,我們通常會選擇用抽象工廠(abstract factory)這一設(shè)計模式。
② 不要在具體實現(xiàn)類上創(chuàng)建衍生類。
繼承關(guān)系是所有一切源代碼依賴關(guān)系中最強的、最難被修改的,所以我們對繼承的使用應(yīng)該格外小心。
③ 不要覆蓋包含具體實現(xiàn)的函數(shù)。
創(chuàng)建一個抽象函數(shù),然后再為該函數(shù)提供多種具體實現(xiàn)。
④ 應(yīng)避免在代碼中寫入與任何具體實現(xiàn)相關(guān)的名字,或者是其他容易變動的事物的名字。
這基本上是DIP原則的另外一個表達方式。
第 4 部分 組件構(gòu)建原則
第12章 組件
- 組件是軟件的部署單元,是整個軟件系統(tǒng)在部署過程中可以獨立完成部署的最小實體。設(shè)計良好的組件都應(yīng)該永遠保持可被獨立部署的特性。
第13章 組件聚合
- 哪些類可以被組合成一個組件呢?
三個構(gòu)建組件相關(guān)的基本原則:
REP:復(fù)用/發(fā)布等同原則。
CCP:共同閉包原則。
CRP:共同復(fù)用原則。
- 復(fù)用/發(fā)布原則
軟件復(fù)用的最小粒度等同于其發(fā)布的最小粒度。
- 共同閉包原則
將由于相同原因而修改,并且需要同時修改的東西放在一起。將由于不同原因而修改,并且不同時修改的東西分開。對大部分應(yīng)用程序來說,而維護性的重要性要遠遠高于可復(fù)用性。
- 共同復(fù)用原則
不要強迫一個組件的用戶依賴他們不需要的東西。
將經(jīng)常共同復(fù)用的類和模塊放在同一個組件中。不是緊密相連的類不應(yīng)該被放在同一個組件里。
我們希望組件中的所有類是不能拆分的,即不應(yīng)該出現(xiàn)別人只需要依賴它的某幾個類而不需要其他類的情況。
- 三個原則之間存在著競爭關(guān)系,
REP和CCP原則是粘合性原則,他們會讓組件變得更大,而CRP原則是排除性原則,他會盡量讓組件變小。架構(gòu)師的任務(wù)就是要在這三個原則中間進行取舍。
第14章 組件耦合
- 無依賴環(huán)原則
組件依賴關(guān)系圖中不應(yīng)該出現(xiàn)環(huán)。
循環(huán)依賴的組件之間事實上被合并成了個一個更大的組件。
- 打破循環(huán)依賴(打破
A依賴B)
(1) 應(yīng)用依賴反轉(zhuǎn)原則
創(chuàng)建C,使A持有C,B繼承C。
(2) 創(chuàng)建一個新組件
創(chuàng)建C,并讓A和B都依賴C。
將A和B中互相依賴的類全部放入C。
- 自上而下的設(shè)計
組件結(jié)構(gòu)圖是不可能自上而下被設(shè)計出來的。他必須隨著系統(tǒng)的變化而擴張,而不可能在系統(tǒng)構(gòu)建的最初就被完美設(shè)計出來。
最初我們對項目中的共同閉包一無所知,也不可能知道哪些組件可以復(fù)用。因此組件依賴關(guān)系是必須要隨著項目的邏輯關(guān)系一起擴張和演進的。
- 穩(wěn)定性指標
Fan-in:入向依賴,指代了組件外部類依賴于組件內(nèi)部類的數(shù)量。
Fan-out:出向依賴,指代了組件內(nèi)部類依賴于組件外部類的數(shù)量。
I不穩(wěn)定性:Fan-out / Fan-in + Fan-out。
I指標的范圍時[0,1],I = 0最穩(wěn)定,I = 1最不穩(wěn)定。
該指標是通過統(tǒng)計和組件內(nèi)部類有依賴的組件外部類的數(shù)量來計算的。
- 穩(wěn)定依賴原則(
SDP)
依賴關(guān)系必須要指向更穩(wěn)定的方向。
任何一個我們預(yù)期會經(jīng)常變更的組件都不應(yīng)該被一個難于修改的組件所依賴。
穩(wěn)定依賴原則(SDP)要求組件結(jié)構(gòu)依賴圖中各組件的I指標必須要按其依賴關(guān)系方向遞減。
- 穩(wěn)定抽象原則
一個組件的抽象化程度應(yīng)該與其穩(wěn)定性保持一致。一方面,該原則要求穩(wěn)定的組件同時應(yīng)該是抽象的,這樣它的穩(wěn)定性就不會影響到擴展性。另一方面,該原則也要求一個不穩(wěn)定的組件應(yīng)該包含具體的實現(xiàn)代碼,這樣它的不穩(wěn)定性就可以通過具體的代碼被輕易修改。
即:依賴關(guān)系應(yīng)該指向更抽象的方向。
第 5 部分 軟件架構(gòu)
第15章 什么是軟件架構(gòu)
- “架構(gòu)”這個詞給人的直觀感受就充滿了權(quán)利和神秘感,因此談?wù)摷軜?gòu)總讓人有一種進行責任重大的決策或者深度技術(shù)分析的感覺。
- 首先,軟件架構(gòu)自身需要是程序員,并且必須一直堅持做一線程序員,絕對不要聽從那些說應(yīng)該讓軟件架構(gòu)師從代碼中解放出來以專心解決高階問題的偽建議。
- 軟件架構(gòu)師應(yīng)該是能力最強的一群程序員,他們通常會在自身承接編程任務(wù)的同時,逐漸引導(dǎo)整個團隊向一個能夠最大化生產(chǎn)力的系統(tǒng)設(shè)計方向前進。
- 軟件架構(gòu)師必須不停地承接編程任務(wù)。如果不親自承受因系統(tǒng)設(shè)計而帶來的麻煩,就體會不到設(shè)計不佳所帶來的痛苦,接著就會逐漸迷失正確的設(shè)計方向。
- 軟件系統(tǒng)的架構(gòu)質(zhì)量是由他的構(gòu)建者所決定,軟件架構(gòu)這項工作的實質(zhì)就是規(guī)劃如何將系統(tǒng)切分為組件,并安排好組件之間的排列關(guān)系,以及組件之間互相通信的方式。
- 軟件架構(gòu)設(shè)計的主要目標是支撐軟件系統(tǒng)的全生命周期,設(shè)計良好的架構(gòu)可以讓系統(tǒng)便于理解、易于修改、方便維護,并且能輕松部署。軟件架構(gòu)的終極目標就是最大化程序員的生產(chǎn)力,同時最小化系統(tǒng)的總運營成本。
- 如果想設(shè)計一個便于推進各項工作的系統(tǒng),其策略就是要在設(shè)計中盡可能長時間地保留盡可能多的可選項。一個優(yōu)秀的軟件架構(gòu)師應(yīng)該致力于最大化可選項的數(shù)量。
- 優(yōu)秀的架構(gòu)師會小心的將軟件的高層策略與其底層實現(xiàn)隔離開,讓高層策略與實現(xiàn)細節(jié)脫鉤,使其策略部分完全不關(guān)心底層細節(jié),當然也不會對這些細節(jié)有任何形式的依賴。另外,優(yōu)秀的架構(gòu)師所設(shè)計的策略應(yīng)該允許系統(tǒng)盡可能地推遲與實現(xiàn)細節(jié)相關(guān)的決策,越晚做決策越好。
第16章 獨立性
- 一個設(shè)計良好的軟件架構(gòu)必須支持以下幾點。
系統(tǒng)的用例與正常運行。
系統(tǒng)的維護。
系統(tǒng)的開發(fā)。
系統(tǒng)的部署。
- 重復(fù)
代碼重復(fù)有時候是假的,或者說只是表面上的重復(fù)。如果有兩段看起來重復(fù)的代碼,他們走的是不同的演進路徑,也就是說它們有著不同的變更速率和變更緣由,那么這兩段代碼就不是真正的重復(fù)代碼。
第17章 劃分邊界
- 軟件架構(gòu)設(shè)計本身就是一門劃分邊界的藝術(shù)。邊界的作用是將軟件分割成各種元素,以便約束邊界兩側(cè)之間的依賴關(guān)系。
- 架構(gòu)師所追求的目標是最大限度地降低構(gòu)建和維護一個系統(tǒng)所需的人力資源。一個系統(tǒng)最消耗人力資源的是系統(tǒng)中的耦合——尤其是那些過早做出的、不成熟的決策所導(dǎo)致的耦合。
- 通過劃清邊界,我們可以推遲和延后一些細節(jié)性的決策。邊界線應(yīng)該畫在那些不相關(guān)的事情中間。
GUI和業(yè)務(wù)邏輯無關(guān),數(shù)據(jù)庫和GUI無關(guān),數(shù)據(jù)庫和業(yè)務(wù)邏輯無關(guān),這些兩者之間都應(yīng)該有一條邊界線。
- 為了在軟件架構(gòu)中畫邊界線,我們需要先將系統(tǒng)分割成組件,其中一部分是系統(tǒng)的核心業(yè)務(wù)邏輯組件,而另一部分則是與核心業(yè)務(wù)邏輯無關(guān)但負責提供必要功能的插件。然后通過對源代碼的修改,讓這些非核心組件依賴于系統(tǒng)的核心業(yè)務(wù)邏輯組件。
即:依賴箭頭應(yīng)該由底層具體實現(xiàn)細節(jié)指向高層抽象的方向。
GUI和業(yè)務(wù)邏輯中間應(yīng)該有一條線。widgetView指向widgetModel,widgetModel不能指向widgetView。
第18章 邊界剖析
- 一個系統(tǒng)的架構(gòu)是由一系列軟件組件以及他們之間的邊界共同定義的。而這些邊界有著不同的存在形式。
第19章 策略與層次
- 所有軟件系統(tǒng)都是一組策略語句的集合。計算機程序不過就是一組仔細描述如何將輸入轉(zhuǎn)化為輸出的策略語句的集合。
- 軟件架構(gòu)設(shè)計的工作重心就是,將這些策略彼此分離,然后將它們按照變更的方式進行重新分組。其中變更原因、時間和層次相同的策略應(yīng)該被分到同一個組件中。反之,變更原因、時間和層次不同的策略則應(yīng)該分屬于不同的組件。
- 架構(gòu)設(shè)計的工作常常需要將組件重排組合成為一個有向無環(huán)圖。圖中的每一個節(jié)點代表的是一個擁有相同層次策略的組件,每一條單向鏈接都代表了一種組件之間的依賴關(guān)系,它們將不同級別的組件鏈接起來。
- 在一個設(shè)計良好的架構(gòu)中,依賴關(guān)系的方向通常取決于它們所關(guān)聯(lián)的組件的層次。一般來說,低層組件被設(shè)計為依賴于高層組件。
從另一個角度來說,低層組件應(yīng)該成為高層組件的插件。
第20章 業(yè)務(wù)邏輯
- 我們可以將自己的應(yīng)用程序劃分為業(yè)務(wù)邏輯和插件兩部分,前者是應(yīng)用程序的核心。
- 業(yè)務(wù)實體包含了一系列用于操作關(guān)鍵數(shù)據(jù)的業(yè)務(wù)邏輯。業(yè)務(wù)實體與數(shù)據(jù)庫、用戶界面、第三方框架等部分無關(guān)。業(yè)務(wù)實體這個概念中應(yīng)該只有業(yè)務(wù)邏輯,沒有別的。
- 業(yè)務(wù)邏輯是整個軟件系統(tǒng)的皇冠明珠。業(yè)務(wù)邏輯應(yīng)該保持純凈、不要摻雜用戶界面或者所使用的數(shù)據(jù)庫相關(guān)的東西。
- 在理想情況下,代表業(yè)務(wù)邏輯的代碼應(yīng)該是整個系統(tǒng)的核心,其他低層概念的實現(xiàn)應(yīng)該以插件形式接入系統(tǒng)中。業(yè)務(wù)邏輯應(yīng)該是系統(tǒng)中最獨立、復(fù)用性最高的代碼。
第21章 尖叫的軟件架構(gòu)
- 一個良好的架構(gòu)設(shè)計應(yīng)該圍繞著用例來展開,這樣的架構(gòu)設(shè)計可以在脫離框架、工具以及使用環(huán)境的情況下完整地描述用例。這就好像一個住宅建筑設(shè)計的首要目標應(yīng)該是滿足住宅的使用需求,而不是確保一定要用磚來構(gòu)建這個房子。
- 良好的架構(gòu)設(shè)計應(yīng)該盡可能地允許用戶推遲和延后決定采用什么框架、數(shù)據(jù)庫、
Web服務(wù)以及其他與環(huán)境相關(guān)的工具。架構(gòu)師應(yīng)該花費很多精力來確保該架構(gòu)的設(shè)計在滿足用例需要的情況下,盡可能地允許用戶能自由地選擇建筑材料(磚頭、石料或者木材)。
- 框架是工具而不是生活信條
框架作者往往對自己寫出的框架有著極深的信念,他們所寫出來的使用手冊一般都是從如何成為該框架的虔誠信徒的角度來描繪如何使用這個框架的。
這不應(yīng)該成為你的觀點。我們要帶著懷疑的態(tài)度審視每一個框架,避免讓框架主導(dǎo)我們的架構(gòu)設(shè)計。
- 可測試的架構(gòu)設(shè)計
我們在運行測試的時候不應(yīng)該運行Web服務(wù),也不應(yīng)該需要連接數(shù)據(jù)庫。我們測試的應(yīng)該只是一個簡單的業(yè)務(wù)實體對象,沒有任何與框架、數(shù)據(jù)庫相關(guān)的依賴關(guān)系。
第22章 整潔架構(gòu)
- 不同架構(gòu)在細節(jié)上各有不同,但總體上是非常相似的。他們都具有同一個設(shè)計目標:按照不同關(guān)注點對軟件進行切割。也就是說,這些架構(gòu)都會將軟件切割成不同的層,至少有一層是只包含該軟件的業(yè)務(wù)邏輯的,而用戶接口、系統(tǒng)接口則屬于其他層。
(1) 獨立于框架。
(2) 可被測試。
(3) 獨立于UI。
(4) 獨立于數(shù)據(jù)庫。
(5) 獨立于任何外部機構(gòu)。
第23章 展示器和謙卑對象
- 謙卑對象模式最初的設(shè)計目的是幫助單元測試的編寫者區(qū)分容易測試的行為與難以測試的行為,并將它們隔離。
第24章 不完全邊界
- 構(gòu)建完整的架構(gòu)邊界是一件很耗費成本的事。需要為系統(tǒng)設(shè)計雙向的多態(tài)邊界接口,用于輸入和輸出的數(shù)據(jù)結(jié)構(gòu),以及所有相關(guān)的依賴關(guān)系管理,以便將系統(tǒng)分割成可獨立編譯與部署的組件。這里會涉及大量的前期工作,以及大量的后期維護工作。
- 很多情況下,設(shè)計架構(gòu)邊界的成本太高了,但為了應(yīng)對將來可能的需要,需要預(yù)留一個邊界,這時就需要引入不完全邊界的概念。
第25章 層次與邊界
- 人們通常習慣于將系統(tǒng)分成三個組件:
UI、業(yè)務(wù)邏輯和數(shù)據(jù)庫。對于稍復(fù)雜一些的系統(tǒng),組件遠不止三個。
- 架構(gòu)邊界可以存在于任何地方。作為架構(gòu)師,我們必須要小心審視究竟在什么地方才需要設(shè)計架構(gòu)邊界。另外,我們還必須弄清楚這些邊界將會帶來多大的成本。
- 軟件架構(gòu)師必須仔細權(quán)衡成本,決定哪里需要設(shè)計架構(gòu)邊界,以及這些地方需要的是完整的邊界,還是不完整的邊界,還是可以忽略的邊界。
第26章 Main組件
-
Main組件是系統(tǒng)中最細節(jié)化的部分——也就是底層的策略,它是整個系統(tǒng)的初始點。在整個系統(tǒng)中,除了操作系統(tǒng)不會再有其他組件依賴它了。Main組件的任務(wù)是創(chuàng)建所有的工廠類、策略類以及其他的全局設(shè)施,并最終將系統(tǒng)的控制權(quán)轉(zhuǎn)交給最高抽象層的代碼來處理。
-
Main組件是整個系統(tǒng)中細節(jié)信息最多的組件。
第27章 服務(wù):宏觀與微觀
- 架構(gòu)設(shè)計的任務(wù)就是找到高層策略與低層細節(jié)之間的架構(gòu)邊界,同時保證這些邊界遵守依賴關(guān)系規(guī)則。
- 雖然服務(wù)化可能有助于提升系統(tǒng)的可擴展性和可研發(fā)性,但服務(wù)本身卻代表整個系統(tǒng)的架構(gòu)設(shè)計。系統(tǒng)的架構(gòu)是由系統(tǒng)內(nèi)部的架構(gòu)邊界,以及邊界之間的依賴關(guān)系定義的,與系統(tǒng)中各組件之間的調(diào)用和通信方式無關(guān)。
第28章 測試邊界
一種對真實世界進行建模的方式?!苤鼐洼p。
封裝、繼承、多態(tài)。——神秘術(shù)語。
所有的競爭問題、死鎖問題、并發(fā)更新問題都是由可變變量導(dǎo)致的。如果變量永遠不會被更改,那就不可能產(chǎn)生競爭或者并發(fā)更新問題。如果鎖狀態(tài)是不可變的,那就永遠不會產(chǎn)生死鎖問題。
如果我們能忽略存儲器與處理器在速度上的限制,那么答案是肯定的。否則的話,不可變性只有在一定情況下是可行的。
SOLID原則SRP:單一職責原則。一個軟件模塊只有一個需要被改變的理由。
OCP:開放封閉原則。如果軟件系統(tǒng)想要更容易被改變,那么其設(shè)計就必須允許新增代碼來修改系統(tǒng)行為,而非只能靠修改原來的代碼。
LSP:里氏替換原則。如果想用可替換的組件來構(gòu)建軟件系統(tǒng),那么這些組件就必須遵守同一個約定,以便讓這些組件可以相互替換。
ISP:接口隔離原則。軟件設(shè)計師應(yīng)該在設(shè)計中避免不必要的依賴。
DIP:依賴反轉(zhuǎn)原則。高層策略性的代碼不應(yīng)該依賴實現(xiàn)底層細節(jié)的代碼,恰恰相反,那些實現(xiàn)底層細節(jié)的代碼應(yīng)該依賴高層策略性的代碼。
單一職責原則的含義并不是每個模塊應(yīng)該只做一件事。
在大部分情況下,軟件模塊就是指一個源代碼文件。
LSP的設(shè)計案例。用戶需要增加區(qū)分兩者的檢測邏輯(例如增加if語句),用戶的行為依賴它所使用的類,這兩個類就不能互相替換。LSP可以且應(yīng)該被應(yīng)用于軟件架構(gòu)層面,因為一旦違背了可替換性,該系統(tǒng)架構(gòu)就不得不為此添加大量復(fù)雜的應(yīng)對機制。OPS類。現(xiàn)在,我們假設(shè)這里的User1只需要使用op1,User2只需要使用op2,User3只需要使用op3。違反
ISP的UML類圖:
UML類圖
符合
ISP的UML類圖:
UML類圖
DIP)主要想告訴我們的是,如果想要設(shè)計一個靈活的系統(tǒng),在源代碼層次的依賴關(guān)系就應(yīng)該多引用抽象類型,而非具體實現(xiàn)。① 應(yīng)在代碼中多使用抽象接口,盡量避免使用那些多變的具體實現(xiàn)類。
對象的創(chuàng)建過程應(yīng)該受到嚴格限制,我們通常會選擇用抽象工廠(
abstract factory)這一設(shè)計模式。② 不要在具體實現(xiàn)類上創(chuàng)建衍生類。
繼承關(guān)系是所有一切源代碼依賴關(guān)系中最強的、最難被修改的,所以我們對繼承的使用應(yīng)該格外小心。
③ 不要覆蓋包含具體實現(xiàn)的函數(shù)。
創(chuàng)建一個抽象函數(shù),然后再為該函數(shù)提供多種具體實現(xiàn)。
④ 應(yīng)避免在代碼中寫入與任何具體實現(xiàn)相關(guān)的名字,或者是其他容易變動的事物的名字。
這基本上是
DIP原則的另外一個表達方式。三個構(gòu)建組件相關(guān)的基本原則:
REP:復(fù)用/發(fā)布等同原則。CCP:共同閉包原則。CRP:共同復(fù)用原則。軟件復(fù)用的最小粒度等同于其發(fā)布的最小粒度。
將由于相同原因而修改,并且需要同時修改的東西放在一起。將由于不同原因而修改,并且不同時修改的東西分開。對大部分應(yīng)用程序來說,而維護性的重要性要遠遠高于可復(fù)用性。
不要強迫一個組件的用戶依賴他們不需要的東西。
將經(jīng)常共同復(fù)用的類和模塊放在同一個組件中。不是緊密相連的類不應(yīng)該被放在同一個組件里。
我們希望組件中的所有類是不能拆分的,即不應(yīng)該出現(xiàn)別人只需要依賴它的某幾個類而不需要其他類的情況。
REP和CCP原則是粘合性原則,他們會讓組件變得更大,而CRP原則是排除性原則,他會盡量讓組件變小。架構(gòu)師的任務(wù)就是要在這三個原則中間進行取舍。組件依賴關(guān)系圖中不應(yīng)該出現(xiàn)環(huán)。
循環(huán)依賴的組件之間事實上被合并成了個一個更大的組件。
A依賴B)(1) 應(yīng)用依賴反轉(zhuǎn)原則
創(chuàng)建
C,使A持有C,B繼承C。(2) 創(chuàng)建一個新組件
創(chuàng)建
C,并讓A和B都依賴C。將
A和B中互相依賴的類全部放入C。組件結(jié)構(gòu)圖是不可能自上而下被設(shè)計出來的。他必須隨著系統(tǒng)的變化而擴張,而不可能在系統(tǒng)構(gòu)建的最初就被完美設(shè)計出來。
最初我們對項目中的共同閉包一無所知,也不可能知道哪些組件可以復(fù)用。因此組件依賴關(guān)系是必須要隨著項目的邏輯關(guān)系一起擴張和演進的。
Fan-in:入向依賴,指代了組件外部類依賴于組件內(nèi)部類的數(shù)量。Fan-out:出向依賴,指代了組件內(nèi)部類依賴于組件外部類的數(shù)量。I不穩(wěn)定性:Fan-out / Fan-in + Fan-out。I指標的范圍時[0,1],I = 0最穩(wěn)定,I = 1最不穩(wěn)定。該指標是通過統(tǒng)計和組件內(nèi)部類有依賴的組件外部類的數(shù)量來計算的。
SDP)依賴關(guān)系必須要指向更穩(wěn)定的方向。
任何一個我們預(yù)期會經(jīng)常變更的組件都不應(yīng)該被一個難于修改的組件所依賴。
穩(wěn)定依賴原則(
SDP)要求組件結(jié)構(gòu)依賴圖中各組件的I指標必須要按其依賴關(guān)系方向遞減。一個組件的抽象化程度應(yīng)該與其穩(wěn)定性保持一致。一方面,該原則要求穩(wěn)定的組件同時應(yīng)該是抽象的,這樣它的穩(wěn)定性就不會影響到擴展性。另一方面,該原則也要求一個不穩(wěn)定的組件應(yīng)該包含具體的實現(xiàn)代碼,這樣它的不穩(wěn)定性就可以通過具體的代碼被輕易修改。
即:依賴關(guān)系應(yīng)該指向更抽象的方向。
系統(tǒng)的用例與正常運行。
系統(tǒng)的維護。
系統(tǒng)的開發(fā)。
系統(tǒng)的部署。
代碼重復(fù)有時候是假的,或者說只是表面上的重復(fù)。如果有兩段看起來重復(fù)的代碼,他們走的是不同的演進路徑,也就是說它們有著不同的變更速率和變更緣由,那么這兩段代碼就不是真正的重復(fù)代碼。
GUI和業(yè)務(wù)邏輯無關(guān),數(shù)據(jù)庫和GUI無關(guān),數(shù)據(jù)庫和業(yè)務(wù)邏輯無關(guān),這些兩者之間都應(yīng)該有一條邊界線。即:依賴箭頭應(yīng)該由底層具體實現(xiàn)細節(jié)指向高層抽象的方向。
GUI和業(yè)務(wù)邏輯中間應(yīng)該有一條線。widgetView指向widgetModel,widgetModel不能指向widgetView。
從另一個角度來說,低層組件應(yīng)該成為高層組件的插件。
Web服務(wù)以及其他與環(huán)境相關(guān)的工具。架構(gòu)師應(yīng)該花費很多精力來確保該架構(gòu)的設(shè)計在滿足用例需要的情況下,盡可能地允許用戶能自由地選擇建筑材料(磚頭、石料或者木材)。框架作者往往對自己寫出的框架有著極深的信念,他們所寫出來的使用手冊一般都是從如何成為該框架的虔誠信徒的角度來描繪如何使用這個框架的。
這不應(yīng)該成為你的觀點。我們要帶著懷疑的態(tài)度審視每一個框架,避免讓框架主導(dǎo)我們的架構(gòu)設(shè)計。
我們在運行測試的時候不應(yīng)該運行
Web服務(wù),也不應(yīng)該需要連接數(shù)據(jù)庫。我們測試的應(yīng)該只是一個簡單的業(yè)務(wù)實體對象,沒有任何與框架、數(shù)據(jù)庫相關(guān)的依賴關(guān)系。(1) 獨立于框架。
(2) 可被測試。
(3) 獨立于
UI。(4) 獨立于數(shù)據(jù)庫。
(5) 獨立于任何外部機構(gòu)。
UI、業(yè)務(wù)邏輯和數(shù)據(jù)庫。對于稍復(fù)雜一些的系統(tǒng),組件遠不止三個。Main組件是系統(tǒng)中最細節(jié)化的部分——也就是底層的策略,它是整個系統(tǒng)的初始點。在整個系統(tǒng)中,除了操作系統(tǒng)不會再有其他組件依賴它了。Main組件的任務(wù)是創(chuàng)建所有的工廠類、策略類以及其他的全局設(shè)施,并最終將系統(tǒng)的控制權(quán)轉(zhuǎn)交給最高抽象層的代碼來處理。Main組件是整個系統(tǒng)中細節(jié)信息最多的組件。