面向?qū)ο缶幊陶Z言四大特性
封裝
封裝也叫作信息隱藏或者數(shù)據(jù)訪問保護。類通過暴露有限的訪問接口,授權(quán)外部僅能通過類提供的方式(或者叫函數(shù))來訪問內(nèi)部信息或者數(shù)據(jù)。對于封裝這個特性,我們需要編程語言本身提供一定的語法機制來支持。這個語法機制就是訪問權(quán)限控制。Java通過private、public等關(guān)鍵字實現(xiàn),go語言通過方法和屬性首字母是否大寫來實現(xiàn)。如果我們對類中屬性的訪問不做限制,那任何代碼都可以訪問、修改類中的屬性,雖然這樣看起來更加靈活,但從另一方面來說,過度靈活也意味著不可控,屬性可以隨意被以各種奇葩的方式修改,而且修改邏輯可能散落在代碼中的各個角落,勢必影響代碼的可讀性、可維護性。類僅僅通過有限的方法暴露必要的操作,也能提高類的易用性。
抽象
講完了封裝特性,我們再來看抽象特性。 封裝主要講的是如何隱藏信息、保護數(shù)據(jù),而抽象講的是如何隱藏方法的具體實現(xiàn),讓調(diào)用者只需要關(guān)心方法提供了哪些功能,并不需要知道這些功能是如何實現(xiàn)的。在面向?qū)ο缶幊讨校覀兂=柚幊陶Z言提供的接口類(比如 Java和go語言 中的 interface 關(guān)鍵字語法)或者抽象類(比如 Java 中的 abstract 關(guān)鍵字語法)這兩種語法機制,來實現(xiàn)抽象這一特性。
抽象這個概念是一個非常通用的設(shè)計思想,并不單單用在面向?qū)ο缶幊讨?,也可以用來指?dǎo)架構(gòu)設(shè)計等。而且這個特性也并不需要編程語言提供特殊的語法機制來支持,只需要提供“函數(shù)”這一非?;A(chǔ)的語法機制,就可以實現(xiàn)抽象特性、所以,它沒有很強的“特異性”,有時候并不被看作面向?qū)ο缶幊痰奶匦灾?。如果上升一個思考層面的話,抽象及其前面講到的封裝都是人類處理復(fù)雜性的有效手段。在面對復(fù)雜系統(tǒng)的時候,人腦能承受的信息復(fù)雜程度是有限的,所以我們必須忽略掉一些非關(guān)鍵性的實現(xiàn)細節(jié)。而抽象作為一種只關(guān)注功能點不關(guān)注實現(xiàn)的設(shè)計思路,正好幫我們的大腦過濾掉許多非必要的信息。除此之外,抽象作為一個非常寬泛的設(shè)計思想,在代碼設(shè)計中,起到非常重要的指導(dǎo)作用。很多設(shè)計原則都體現(xiàn)了抽象這種設(shè)計思想,比如基于接口而非實現(xiàn)編程、開閉原則(對擴展開放、對修改關(guān)閉)、代碼解耦(降低代碼的耦合性)等。換一個角度來考慮,我們在定義(或者叫命名)類的方法的時候,也要有抽象思維,不要在方法定義中,暴露太多的實現(xiàn)細節(jié),以保證在某個時間點需要改變方法的實現(xiàn)邏輯的時候,不用去修改其定義。“基于接口而非實現(xiàn)編程”這條原則的另一個表述方式,是“基于抽象而非實現(xiàn)編程”。后者的表述方式其實更能體現(xiàn)這條原則的設(shè)計初衷。在軟件開發(fā)中,最大的挑戰(zhàn)之一就是需求的不斷變化,這也是考驗代碼設(shè)計好壞的一個標(biāo)準(zhǔn)。越抽象、越頂層、越脫離具體某一實現(xiàn)的設(shè)計,越能提高代碼的靈活性,越能應(yīng)對未來的需求變化。好的代碼設(shè)計,不僅能應(yīng)對當(dāng)下的需求,而且在將來需求發(fā)生變化的時候,仍然能夠在不破壞原有代碼設(shè)計的情況下靈活應(yīng)對。而抽象就是提高代碼擴展性、靈活性、可維護性最有效的手段之一。
從“基于接口而非實現(xiàn)編程”的原則,具體來講,我們需要做到下面這 3 點。1)函數(shù)的命名不能暴露任何實現(xiàn)細節(jié);2)封裝具體的實現(xiàn)細節(jié);3)為實現(xiàn)類定義抽象的接口。
抽象類VS接口
抽象類更多的是為了代碼復(fù)用,而接口就更側(cè)重于解耦。接口是對行為的一種抽象,相當(dāng)于一組協(xié)議或者契約,你可以聯(lián)想類比一下 API 接口。調(diào)用者只需要關(guān)注抽象的接口,不需要了解具體的實現(xiàn),具體的實現(xiàn)代碼對調(diào)用者透明。接口實現(xiàn)了約定和實現(xiàn)相分離,可以降低代碼間的耦合性,提高代碼的可擴展性。實際上,接口是一個比抽象類應(yīng)用更加廣泛、更加重要的知識點。比如,我們經(jīng)常提到的“基于接口而非實現(xiàn)編程”,就是一條幾乎天天會用到,并且能極大地提高代碼的靈活性、擴展性的設(shè)計思想。
繼承
如果你熟悉的是類似 Java、C++ 這樣的面向?qū)ο蟮木幊陶Z言,那你對繼承這一特性,應(yīng)該不陌生了。繼承是用來表示類之間的is-a 關(guān)系,比如貓是一種哺乳動物。從繼承關(guān)系上來講,繼承可以分為兩種模式,單繼承和多繼承。單繼承表示一個子類只繼承一個父類,多繼承表示一個子類可以繼承多個父類,比如貓既是哺乳動物,又是爬行動物。繼承最大的一個好處就是代碼復(fù)用。假如兩個類有一些相同的屬性和方法,我們就可以將這些相同的部分,抽取到父類中,讓兩個子類繼承父類。這樣,兩個子類就可以重用父類中的代碼,避免代碼重復(fù)寫多遍。不過,這一點也并不是繼承所獨有的,我們也可以通過其他方式來解決這個代碼復(fù)用的問題,比如利用組合關(guān)系而不是繼承關(guān)系。從人類認知的角度上來說,is-a 關(guān)系通過繼承來關(guān)聯(lián)兩個類,反應(yīng)真實世界中的這種關(guān)系,非常符合人類的認知,而且,從設(shè)計的角度來說,也有一種結(jié)構(gòu)美感。繼承的概念很好理解,也很容易使用。不過,過度使用繼承,繼承層次過深過復(fù)雜,就會導(dǎo)致代碼可讀性、可維護性變差。為了了解一個類的功能,我們不僅需要查看這個類的代碼,還需要按照繼承關(guān)系一層一層地往上查看“父類、父類的父類......”的代碼。還有,子類和父類高度耦合,修改父類的代碼,會直接影響到子類。所以,繼承這個特性也是一個非常有爭議的特性。很多人覺得繼承是一種反模式。我們應(yīng)該盡量少用,甚至不用。
繼承主要有三個作用:表示 is-a 關(guān)系,支持多態(tài)特性,代碼復(fù)用。而這三個作用都可以通過其他技術(shù)手段來達成。比如 is-a 關(guān)系,我們可以通過組合和接口的 has-a 關(guān)系來替代;多態(tài)特性我們可以利用接口來實現(xiàn);代碼復(fù)用我們可以通過組合和委托來實現(xiàn)。所以,從理論上講,通過組合、接口、委托三個技術(shù)手段,我們完全可以替換掉繼承,在項目中不用或者少用繼承關(guān)系,特別是一些復(fù)雜的繼承關(guān)系。裝飾者模式(decoratorpattern)、策略模式(strategy pattern)、組合模式(composite pattern)等都使用了組合關(guān)系,而模板模式(template pattern)使用了繼承關(guān)系。
多態(tài)
多態(tài)是指,子類可以替換父類,實現(xiàn)可以賦值給接口,在實際的代碼運行過程中,調(diào)用子類方法或接口實現(xiàn)的方法。
對于多態(tài)特性的實現(xiàn)方式,利用“繼承加方法重寫”是一種典型實現(xiàn),還有其他兩種比較常見的的實現(xiàn)方式,一個是利用接口類語法,另一個是利用 duck-typing 語法。不過,并不是每種編程語言都支持接口類或者 duck-typing 這兩種語法機制,比如 C++就不支持接口類語法,而 duck-typing 只有一些動態(tài)語言才支持,比如 Python、JavaScrip、go 等。所謂的 duck-typing,就是兩個類具有相同的方法,就可以實現(xiàn)多態(tài),相同方法的定義就可以抽取出來組成一個接口,任何用到這個接口方法的地方都可以用這兩個類替換。多態(tài)特性能提高代碼的可擴展性和復(fù)用性。多態(tài)也是很多設(shè)計模式、設(shè)計原則、編程技巧的代碼實現(xiàn)基礎(chǔ),比如策略模式、基于接口而非實現(xiàn)編程、依賴倒置原則、里式替換原則、利用多態(tài)去掉冗長的 if-else 語句等等。
面向?qū)ο?VS 面向過程
面向?qū)ο缶幊滔啾让嫦蜻^程編程的優(yōu)勢主要有三個。面向?qū)ο缶幊桃话闶褂妹嫦驅(qū)ο缶幊陶Z言來進行,但是,不用面向?qū)ο缶幊陶Z言,我們照樣可以進行面向?qū)ο缶幊?。反過來講,即便我們使用面向?qū)ο缶幊陶Z言,寫出來的代碼也不一定是面向?qū)ο缶幊田L(fēng)格的,也有可能是面向過程編程風(fēng)格的。面向?qū)ο蠛兔嫦蜻^程兩種編程風(fēng)格并不是非黑即白、完全對立的。在用面向?qū)ο缶幊陶Z言開發(fā)的軟件中,面向過程風(fēng)格的代碼并不少見,甚至在一些標(biāo)準(zhǔn)的開發(fā)庫(比如 JDK、Apache Commons、Google Guava)中,也有很多面向過程風(fēng)格的代碼。不管使用面向過程還是面向?qū)ο竽姆N風(fēng)格來寫代碼,我們最終的目的還是寫出易維護、易讀、易復(fù)用、易擴展的高質(zhì)量代碼。只要我們能避免面向過程編程風(fēng)格的一些弊端,控制好對于大規(guī)模復(fù)雜程序的開發(fā),程序的處理流程并非單一的一條主線,而是錯綜復(fù)雜的網(wǎng)狀結(jié)構(gòu)。面向?qū)ο缶幊瘫绕鹈嫦蜻^程編程,更能應(yīng)對這種復(fù)雜類型的程序開發(fā)。面向?qū)ο缶幊滔啾让嫦蜻^程編程,具有更加豐富的特性(封裝、抽象、繼承、多態(tài))。利用這些特性編寫出來的代碼,更加易擴展、易復(fù)用、易維護。從編程語言跟機器打交道方式的演進規(guī)律中,我們可以總結(jié)出:面向?qū)ο缶幊陶Z言比起面向過程編程語言,更加人性化、更加高級、更加智能。
它的副作用,在掌控范圍內(nèi)為我們所用,我們就大可不用避諱在面向?qū)ο缶幊讨袑懨嫦蜻^程風(fēng)格的代碼。
設(shè)計模式原則
經(jīng)典的設(shè)計原則,其中包括,SOLID、KISS、DRY、LOD 等。
SOLID 原則并非單純的 1 個原則,而是由 5個設(shè)計原則組成的,它們分別是:單一職責(zé)原則、開閉原則、里式替換原則、接口隔離原則和依賴反轉(zhuǎn)原則,依次對應(yīng) SOLID 中的 S、O、L、I、D 這 5 個英文字母。
| 設(shè)計原則名稱 | 定 義 | 補充 |
|---|---|---|
| 單一職責(zé)原則(Single Responsibility Principle, SRP) | 一個類或模塊只負責(zé)一個功能領(lǐng)域中的相應(yīng)職責(zé),不要設(shè)計大而全的類,要設(shè)計粒度小、功能單一的類。 | 這也是靈活的前提,如果我們把類拆分成最小的職能單位,那組合與復(fù)用就簡單的多了,如果一個類做的事情太多,在組合的時候,必然會產(chǎn)生不必要的方法出現(xiàn),這實際上是一種污染。單一職責(zé)的潛臺詞是:拆分到最小單位,解決復(fù)用和組合問題。在現(xiàn)實的業(yè)務(wù)開發(fā)中,我們通常先寫一個粗粒度的類,滿足業(yè)務(wù)需求,隨著業(yè)務(wù)的發(fā)展,如果粗粒度的類越來越龐大,代碼越來越多,這個時候,我們就可以將這個粗粒度的類,拆分成幾個更細粒度的類。這就是所謂的持續(xù)重構(gòu)。 |
| 開閉原則(Open-Closed Principle, OCP) | 軟件實體應(yīng)對擴展開放,而對修改關(guān)閉。添加一個新的功能應(yīng)該是,在已有代碼基礎(chǔ)上擴展代碼(新增模塊、類、方法等),而非修改已有代碼(修改模塊、類、方法等) | 如果每次需求變動都去修改原有的代碼,那原有的代碼就存在被修改錯誤的風(fēng)險,當(dāng)然這其中存在有意和無意的修改,都會導(dǎo)致原有正常運行的功能失效的風(fēng)險,這樣很有可能會展開可怕的蝴蝶效應(yīng),使維護工作劇增。我們寫完的代碼,不能因為需求變化就修改。我們可以通過新增代碼的方式來解決變化的需求。開閉原則是設(shè)計模式的第一大原則,它的潛臺詞是:控制需求變動風(fēng)險,縮小維護成本。 |
| 里氏代換原則(Liskov Substitution Principle, LSP) | 所有引用基類對象的地方能夠透明地使用其子類的對象,并且保證原來程序的邏輯行為(behavior)不變及正確性不被破壞。 | 此原則的含義是子類可以在任何地方替換它的父類。解釋一下,這是多態(tài)的前提,我們后面很多所謂的靈活,都是不改變聲明類型的情況下,改變實例化類來完成的需求變更。從定義描述和代碼實現(xiàn)上來看,多態(tài)和里式替換有點類似,但它們關(guān)注的角度是不一樣的。多態(tài)是面向?qū)ο缶幊痰囊淮筇匦?,也是面向?qū)ο缶幊陶Z言的一種語法。它是一種代碼實現(xiàn)的思路。而里式替換是一種設(shè)計原則,是用來指導(dǎo)繼承關(guān)系中子類該如何設(shè)計的,子類的設(shè)計要保證在替換父類的時候,不改變原有程序的邏輯以及不破壞原有程序的正確性。我們必須保證我們的子類和父類劃分是精準(zhǔn)的。里氏替換原則的潛臺詞是:盡量使用精準(zhǔn)的抽象類或者接口。 |
| 接口隔離原則(Interface Segregation Principle, ISP) | 使用多個專門的接口,而不使用單一的總接口,函數(shù)的設(shè)計要功能單一,不要將多個不同的功能邏輯在一個函數(shù)中實現(xiàn)。 | 接口隔離原則可以說是單一職責(zé)的必要手段,它的含義是盡量使用職能單一的接口,而不使用職能復(fù)雜、全面的接口。很好理解,接口是為了讓子類實現(xiàn)的,如果子類想達到職能單一,那么接口也必須滿足職能單一。相反,如果接口融合了多個不相關(guān)的方法,那它的子類就被迫要實現(xiàn)所有方法,盡管有些方法是根本用不到的。這就是接口污染。接口隔離原則的潛臺詞是:拆分,從接口開始。 |
| 依賴倒轉(zhuǎn)原則(Dependence Inversion Principle, DIP) | 抽象不應(yīng)該依賴于細節(jié),細節(jié)應(yīng)該依賴于抽象。高層模塊和低層模塊應(yīng)該通過抽象來互相依賴。 | 依賴倒置原則就是要求調(diào)用者和被調(diào)用者都依賴抽象,這樣兩者沒有直接的關(guān)聯(lián)和接觸,在變動的時候,一方的變動不會影響另一方的變動。其實,依賴倒置和前面的原則是相輔相成的,都強調(diào)了抽象的重要性。依賴倒置的潛臺詞是:面向抽象編程,解耦調(diào)用和被調(diào)用者。 |
| 合成復(fù)用原則(Composite Reuse Principle, CRP) | 盡量使用對象組合,而不是繼承來達到復(fù)用的目的 | 如果只是達到代碼復(fù)用的目的,盡量使用組合與聚合,而不是繼承。這里需要解釋一下,組合聚合只是引用其他的類的方法,而不會受引用的類的繼承而改變血統(tǒng)。繼承的耦合性更大,比如一個父類后來添加實現(xiàn)一個接口或者去掉一個接口,那子類可能會遭到毀滅性的編譯錯誤,但如果只是組合聚合,只是引用類的方法,就不會有這種巨大的風(fēng)險,同時也實現(xiàn)了復(fù)用。組合聚合復(fù)用原則的潛臺詞是:我只是用你的方法,我們不一定是同類。 |
| 迪米特法則(Law of Demeter, LoD) | 一個軟件實體應(yīng)當(dāng)盡可能少地與其他實體發(fā)生相互作用 | 迪米特原則要求盡量的封裝,盡量的獨立,盡量的使用低級別的訪問修飾符。這是封裝特性的典型體現(xiàn)。一個類如果暴露太多私用的方法和字段,會讓調(diào)用者很茫然。并且會給類造成不必要的判斷代碼。所以,我們使用盡量低的訪問修飾符,讓外界不知道我們的內(nèi)部。這也是面向?qū)ο蟮幕舅悸?。另外,迪米特原則要求類之間的直接聯(lián)系盡量的少,兩個類的訪問,通過第三個中介類來實現(xiàn)。迪米特原則的潛臺詞是:不和陌生人說話,有事找中介。 |
| KISS(Keep It Short and Simple) | 無論代碼、模塊設(shè)計,應(yīng)盡量保持簡單 | 代碼的可讀性和可維護性是衡量代碼質(zhì)量非常重要的兩個標(biāo)準(zhǔn)。而 KISS 原則就是保持代碼可讀和可維護的重要手段。代碼足夠簡單,也就意味著很容易讀懂,bug 比較難隱藏。即便出現(xiàn) bug,修復(fù)起來也比較簡單。 |
| DRY 原則(Don’t Repeat Yourself) | 不要寫重復(fù)的代碼 | 提高代碼可復(fù)用性的手段有很多,包括不限于 減少代碼耦合、滿足單一職責(zé)原則、模塊化、業(yè)務(wù)與非業(yè)務(wù)邏輯分離、通用代碼下沉、面向?qū)ο?大特性、應(yīng)用模板等設(shè)計模式 |
在學(xué)習(xí)設(shè)計原則時需要注意以下幾點:
a) 高內(nèi)聚、低耦合和單一職能的“沖突”
實際上,這兩者是一回事。內(nèi)聚,要求一個類把所有相關(guān)的方法放在一起,初看是職能多,但有個“高”,就是要求把聯(lián)系非常緊密的功能放在一起,也就是說,從整體看,是一個職能的才能放在一起,所以,兩者是不同的表述而已。
這里很多人理解成復(fù)合類,但復(fù)合類不是高內(nèi)聚,而是雜亂的放在一起,是一種設(shè)計失誤而已。
b)多個單一職能接口的靈活性和聲明類型問題
如果一個類實現(xiàn)多個接口,那么這個類應(yīng)該用哪個接口類型聲明呢?應(yīng)該是用一個抽象類來繼承多個接口,而實現(xiàn)類來繼承抽象類。聲明的時候,類型是抽象類。
c) 迪米特原則和中介類泛濫兩種極端情況
這是另一種設(shè)計的失誤。迪米特原則第一要義:從被依賴者的角度來說,只暴露應(yīng)該暴露的方法或者屬性,即在編寫相關(guān)的類的時候確定方法/屬性的權(quán)限。迪米特原則第二要義:從依賴者的角度來說,只依賴應(yīng)該依賴的對象。迪米特原則要求不直接相關(guān)類之間要用中介來通訊,但類多了以后,會造成中介類泛濫的情況,這種情況,我們可以考慮中介模式,用一個總的中介類來實現(xiàn)。當(dāng)然,設(shè)計模式都有自己的缺陷,迪米特原則也不是十全十美,交互類非常繁多的情況下,要適當(dāng)?shù)臓奚O(shè)計原則。
d) 繼承和組合聚合復(fù)用原則的“沖突”
繼承也能實現(xiàn)復(fù)用,那這個原則是不是要拋棄繼承了?不是的。
繼承更注重的是“血統(tǒng)”,也就是什么類型的。而組合聚合更注重的是借用“技能”。并且,組合聚合中,兩個類是部分與整體的關(guān)系,組合聚合可以由多個類的技能組成。這個原則不是告訴我們不用繼承了,都用組合聚合,而是在“復(fù)用”這個點上,我們優(yōu)先使用組合聚合。
工程結(jié)構(gòu)原則
貧血模型是指使用的領(lǐng)域?qū)ο笾兄挥衧etter和getter方法(POJO),所有的業(yè)務(wù)邏輯都不包含在領(lǐng)域?qū)ο笾卸欠旁跇I(yè)務(wù)邏輯層。有人將我們這里說的貧血模型進一步劃分成失血模型(領(lǐng)域?qū)ο笸耆珱]有業(yè)務(wù)邏輯)和貧血模型(領(lǐng)域?qū)ο笥猩倭康臉I(yè)務(wù)邏輯),我們這里就不對此加以區(qū)分了。充血模型將大多數(shù)業(yè)務(wù)邏輯和持久化放在領(lǐng)域?qū)ο笾?,業(yè)務(wù)邏輯(業(yè)務(wù)門面)只是完成對業(yè)務(wù)邏輯的封裝、事務(wù)和權(quán)限等的處理。
充血模型的層次結(jié)構(gòu)和上面的差不多,不過大多業(yè)務(wù)邏輯和持久化放在Domain Object里面,Business Logic只是簡單封裝部分業(yè)務(wù)邏輯以及控制事務(wù)、權(quán)限等,這樣層次結(jié)構(gòu)就變成Client->(Business Facade)->Business Logic->Domain Object->Data Access。
優(yōu)點是面向?qū)ο?,Business Logic符合單一職責(zé),不像在貧血模型里面那樣包含所有的業(yè)務(wù)邏輯太過沉重。
脹血模型是基于充血模型上取消Service層,只剩下domain object和DAO兩層,在domain object的domain logic上面封裝事務(wù)。
在這四種模型當(dāng)中,失血模型和脹血模型應(yīng)該是不被提倡的。而貧血模型和充血模型從技術(shù)上來說,都已經(jīng)是可行的了。事務(wù)封裝還是盡量放在Service層(我們的manage層)。脹血模型將對象的序列化行為封裝到領(lǐng)域?qū)?,即domain object會調(diào)用domain acess層,同時domain access層又依賴domain object的結(jié)構(gòu),所以脹血模型中domain object層會和domain access層雙向依賴。
我們平時做 Web 項目的業(yè)務(wù)開發(fā),大部分都是基于貧血模型的 MVC 三層架構(gòu),稱為傳統(tǒng)的開發(fā)模式。之所以稱之為“傳統(tǒng)”,是相對于新興的基于充血模型的DDD 開發(fā)模式來說的?;谪氀P偷膫鹘y(tǒng)開發(fā)模式,是典型的面向過程的編程風(fēng)格。相反,基于充血模型的 DDD 開發(fā)模式,是典型的面向?qū)ο蟮木幊田L(fēng)格。不過,DDD 也并非銀彈。對于業(yè)務(wù)不復(fù)雜的系統(tǒng)開發(fā)來說,基于貧血模型的傳統(tǒng)開發(fā)模式簡單夠用,基于充血模型的 DDD 開發(fā)模式有點大材小用,無法發(fā)揮作用。相反,對于業(yè)務(wù)復(fù)雜的系統(tǒng)開發(fā)來說,基于充血模型的 DDD 開發(fā)模式,因為前期需要在設(shè)計上投入更多時間和精力,來提高代碼的復(fù)用性和可維護性,所以相比基于貧血模型的開發(fā)模式,更加有優(yōu)勢。基于充血模型的 DDD 開發(fā)模式跟基于貧血模型的傳統(tǒng)開發(fā)模式相比,主要區(qū)別在 Service層。在基于充血模型的開發(fā)模式下,我們將部分原來在 Service 類中的業(yè)務(wù)邏輯移動到了一個充血的 Domain 領(lǐng)域模型中,讓 Service 類的實現(xiàn)依賴這個 Domain 類。不過,Service 類并不會完全移除,而是負責(zé)一些不適合放在 Domain 類中的功能。比如,負責(zé)與 Repository 層打交道、跨領(lǐng)域模型的業(yè)務(wù)聚合功能、冪等事務(wù)等非功能性的工作?;诔溲P偷?DDD 開發(fā)模式跟基于貧血模型的傳統(tǒng)開發(fā)模式相比,Controller 層和Repository 層的代碼基本上相同。這是因為,Repository 層的 Entity 生命周期有限,Controller 層的 VO 只是單純作為一種 DTO。兩部分的業(yè)務(wù)邏輯都不會太復(fù)雜。業(yè)務(wù)邏輯主要集中在 Service 層。所以,Repository 層和 Controller 層繼續(xù)沿用貧血模型的設(shè)計思路是沒有問題的。
事務(wù)腳本VS領(lǐng)域建模模式
單業(yè)務(wù)邏輯比較簡單時,失血模型和貧血模型基本一樣,所有的業(yè)務(wù)邏輯集中在service層,編寫一個稱為事務(wù)腳本的方法來處理來自表示層的每個請求,這種設(shè)計風(fēng)格是高度面向過程的,這種方法適用于簡單的業(yè)務(wù)邏輯。
采用事務(wù)腳本會隨著業(yè)務(wù)邏輯變得復(fù)雜,代碼也會難以維護。就像單體應(yīng)用程序不斷增長的趨勢一樣,事務(wù)腳本也存在同樣的問題。很多類同時包含狀態(tài)和行為,通過將用戶的狀態(tài)和行為收斂到對象領(lǐng)域模型上,實現(xiàn)邏輯上的高內(nèi)聚,同時代碼邏輯也會更高復(fù)用。