“Design is there to enable you to keep changing the software easily in the long term” —— Kent Beck
序言
20世紀(jì)60年代以前,計(jì)算機(jī)剛剛投入實(shí)際使用,軟件設(shè)計(jì)往往只是為了一個(gè)特定的應(yīng)用而在指定的計(jì)算機(jī)上設(shè)計(jì)和編制,采用密切依賴于計(jì)算機(jī)的機(jī)器代碼或匯編語言,軟件的規(guī)模比較小,文檔資料通常也沒有,很少使用系統(tǒng)化的開發(fā)方法,設(shè)計(jì)軟件往往等同于編制程序,基本上是自給自足的私人化的軟件生產(chǎn)方式。
20世紀(jì)60年代中期,大容量、高速度計(jì)算機(jī)的出現(xiàn),使得計(jì)算機(jī)的應(yīng)用范圍迅速擴(kuò)大,軟件開發(fā)急劇增長。高級(jí)語言逐漸流行(FORTRAN 66),操作系統(tǒng)開始發(fā)展(IBMSYS),第一代數(shù)據(jù)庫管理系統(tǒng)慢慢誕生(IMS),軟件系統(tǒng)的規(guī)模越來越大,復(fù)雜程度越來越高,軟件可靠性問題也越來越突出。既有自給自足的私人化的軟件生產(chǎn)方式不能再滿足要求,迫切需要改變,于是軟件危機(jī)開始爆發(fā),即落后的軟件生產(chǎn)方式無法滿足迅速增長的計(jì)算機(jī)軟件需求,導(dǎo)致軟件的開發(fā)與維護(hù)出現(xiàn)一系列嚴(yán)重的問題:
- 軟件開發(fā)費(fèi)用和進(jìn)度失控
- 軟件的可靠性差
- 生產(chǎn)出來的軟件難以維護(hù)
1968年北大西洋公約組織的計(jì)算機(jī)科學(xué)家在聯(lián)邦德國召開國際會(huì)議,第一次討論軟件危機(jī)問題,并正式提出“軟件工程”一詞,從此一門新興的工程學(xué)科應(yīng)運(yùn)而生。
結(jié)構(gòu)化程序設(shè)計(jì)
結(jié)構(gòu)化程序設(shè)計(jì)由迪克斯特拉(E.W.dijkstra)在1969年提出,是以模塊化設(shè)計(jì)為中心,將待開發(fā)的軟件系統(tǒng)劃分為若干個(gè)相互獨(dú)立的模塊,這樣使完成每一個(gè)模塊的工作變單純而明確,為設(shè)計(jì)一些較大的軟件打下了良好的基礎(chǔ)。
由于模塊相互獨(dú)立,因此在設(shè)計(jì)其中一個(gè)模塊時(shí),不會(huì)受到其它模塊的牽連,因而可將原來較為復(fù)雜的問題化簡為一系列簡單模塊的設(shè)計(jì)。模塊的獨(dú)立性還為擴(kuò)充已有的系統(tǒng)和建立新系統(tǒng)帶來了不少的方便,因?yàn)槲覀兛梢猿浞掷矛F(xiàn)有的模塊作積木式的擴(kuò)展。
按照結(jié)構(gòu)化程序設(shè)計(jì)的觀點(diǎn),任何算法功能都可以通過由程序模塊組成的三種基本程序結(jié)構(gòu)的組合: 順序結(jié)構(gòu)、選擇結(jié)構(gòu)和循環(huán)結(jié)構(gòu)來實(shí)現(xiàn)。
結(jié)構(gòu)化程序設(shè)計(jì)主要表現(xiàn)在一下三個(gè)方面:
- 自頂向下,逐步求精。將編寫程序看成是一個(gè)逐步演化的過程,將分析問題的過程劃分成若干個(gè)層次,每一個(gè)新的層次都是上一個(gè)層次的細(xì)化。
- 模塊化。將系統(tǒng)分解成若干個(gè)模塊,每個(gè)模塊實(shí)現(xiàn)特定的功能,最終的系統(tǒng)由這些模塊組裝而成,模塊之間通過接口傳遞信息。
- 語句結(jié)構(gòu)化。在每個(gè)模塊中只允許出現(xiàn)順序、分支和循環(huán)三種流程結(jié)構(gòu)的語句。
結(jié)構(gòu)化程序設(shè)計(jì)的概念、方法和支持這些方法的一整套軟件工具,構(gòu)成了結(jié)構(gòu)化革命。這是計(jì)算機(jī)問世以來對(duì)計(jì)算機(jī)界影響最大的一個(gè)軟件概念,被稱為軟件發(fā)展中的第三個(gè)里程碑,其影響比前兩個(gè)里程碑(子程序、高級(jí)語言)更為深遠(yuǎn)。
1972年,美國貝爾實(shí)驗(yàn)室的D.M.Ritchie在B語言的基礎(chǔ)上最終設(shè)計(jì)出了一種新的語言,他取了BCPL的第二個(gè)字母作為這種語言的名字,這就是C語言。1973年初,C語言的主體開發(fā)完成,并逐步成為結(jié)構(gòu)化編程語言中最流行的語言。
尼古拉斯沃思(Nicklaus Wirth)教授在編程界提出了一個(gè)著名的公式:程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法
結(jié)構(gòu)化程序設(shè)計(jì)是用計(jì)算機(jī)的思維方式去處理問題,將數(shù)據(jù)結(jié)構(gòu)和算法分離。數(shù)據(jù)結(jié)構(gòu)描述待處理數(shù)據(jù)的組織形式,而算法描述具體的操作過程。我們用函數(shù)把這些算法一步一步的實(shí)現(xiàn),使用的時(shí)候一個(gè)一個(gè)的依次調(diào)用就可以了。
說明:“面向過程”這個(gè)詞是在“面向?qū)ο蟆背霈F(xiàn)之后為與之相對(duì)而提出的,它可以看作是“結(jié)構(gòu)化”的別名。
面向?qū)ο蟪绦蛟O(shè)計(jì)
面對(duì)日趨復(fù)雜的軟件系統(tǒng),結(jié)構(gòu)化程序設(shè)計(jì)在下面幾個(gè)方面逐漸暴露出了一些弱點(diǎn):
審視問題域的視角。在現(xiàn)實(shí)世界中存在的客體是問題域中的主角,所謂客體是指客觀存在的對(duì)象實(shí)體和主觀抽象的概念,它是人類觀察問題和解決問題的主要目標(biāo)。例如,對(duì)于一個(gè)學(xué)校學(xué)生管理系統(tǒng)來說,無論是簡單還是復(fù)雜,始終是圍繞學(xué)生和老師這兩個(gè)客體實(shí)施。結(jié)構(gòu)化設(shè)計(jì)方法所采用的設(shè)計(jì)思路不是將客體作為一個(gè)整體,而是將依附于客體之上的行為抽取出來,以功能為目標(biāo)來設(shè)計(jì)構(gòu)造應(yīng)用系統(tǒng)。這種做法導(dǎo)致在進(jìn)行程序設(shè)計(jì)的時(shí)候,不得不將客體所構(gòu)成的現(xiàn)實(shí)世界映射到由功能模塊組成的解空間中,這種變換過程,不僅增加了程序設(shè)計(jì)的復(fù)雜程度,而且背離了人們觀察問題和解決問題的基本思路。另外,再仔細(xì)思考會(huì)發(fā)現(xiàn),在任何一個(gè)問題域中,客體是穩(wěn)定的,而行為是不穩(wěn)定的。例如,不管是國家圖書館,還是學(xué)校圖書館,還是國際圖書館,都會(huì)含有圖書這個(gè)客體,但管理圖書的方法可能是截然不同的。結(jié)構(gòu)化設(shè)計(jì)方法將審視問題的視角定位于不穩(wěn)定的操作之上,并將描述客體的屬性和行為分開,使得應(yīng)用程序的日后維護(hù)和擴(kuò)展相當(dāng)困難,甚至一個(gè)微小的變動(dòng),都會(huì)波及到整個(gè)系統(tǒng)。面對(duì)問題規(guī)模的日趨擴(kuò)大、環(huán)境的日趨復(fù)雜、需求變化的日趨加快,將利用計(jì)算機(jī)解決問題的基本方法統(tǒng)一到人類解決問題的習(xí)慣方法之上,徹底改變軟件設(shè)計(jì)方法與人類解決問題的常規(guī)方式扭曲的現(xiàn)象迫在眉睫,這是提出面向?qū)ο蟮氖滓颉?/p>
抽象級(jí)別。抽象是人類解決問題的基本法寶。良好的抽象策略可以控制問題的復(fù)雜程度,增強(qiáng)系統(tǒng)的通用性和可擴(kuò)展性。抽象主要包括過程抽象和數(shù)據(jù)抽象。結(jié)構(gòu)化設(shè)計(jì)方法應(yīng)用的是過程抽象。所謂過程抽象是將問題域中具有明確功能定義的操作抽取出來,并將其作為一個(gè)實(shí)體看待。這種抽象級(jí)別對(duì)于軟件系統(tǒng)結(jié)構(gòu)的設(shè)計(jì)顯得有些武斷,并且穩(wěn)定性差,導(dǎo)致很難準(zhǔn)確無誤地設(shè)計(jì)出系統(tǒng)的每一個(gè)操作環(huán)節(jié)。一旦某個(gè)客體屬性的表示方式發(fā)生了變化,就有可能牽扯到已有系統(tǒng)的很多部分。而數(shù)據(jù)抽象是較過程抽象更高級(jí)別的抽象方式,將描述客體的屬性和行為綁定在一起,實(shí)現(xiàn)統(tǒng)一的抽象,從而達(dá)到對(duì)現(xiàn)實(shí)世界客體的真正模擬。
封裝體。封裝是指將現(xiàn)實(shí)世界中存在的某個(gè)客體的屬性與行為綁定在一起,并放置在一個(gè)邏輯單元內(nèi)。結(jié)構(gòu)化設(shè)計(jì)方法沒有做到客體的整體封裝,只是封裝了各個(gè)功能模塊,而每個(gè)功能模塊可以隨意地對(duì)沒有保護(hù)能力客體屬性實(shí)施操作,并且由于描述屬性的數(shù)據(jù)與行為被分割開來,所以一旦某個(gè)客體屬性的表達(dá)方式發(fā)生了變化,就有可能對(duì)整個(gè)系統(tǒng)產(chǎn)生影響。
可重用性??芍赜眯詷?biāo)識(shí)著軟件產(chǎn)品的可復(fù)用能力,是衡量一個(gè)軟件產(chǎn)品成功與否的重要標(biāo)志。結(jié)構(gòu)化程序設(shè)計(jì)方法的基本單位是模塊,每個(gè)模塊只是實(shí)現(xiàn)特定功能的過程描述,因此,它的可重用單位只能是模塊。但對(duì)于當(dāng)前的軟件開發(fā)來說,這樣的重用力度顯得微不足道,而且當(dāng)參與操作的某些數(shù)據(jù)類型發(fā)生變化時(shí),就不能夠再使用那些函數(shù)了。因此,渴望更大力度的可重用構(gòu)件是如今應(yīng)用領(lǐng)域?qū)浖_發(fā)提出的新需求。
上述的三個(gè)弱點(diǎn)驅(qū)使人們尋求一種新的程序設(shè)計(jì)方法,以適應(yīng)當(dāng)代社會(huì)對(duì)軟件開發(fā)的更高要求,面向?qū)ο笥纱水a(chǎn)生。面向?qū)ο蠹夹g(shù)強(qiáng)調(diào)在軟件開發(fā)過程中面向客觀世界或問題域中的事物,采用人類在認(rèn)識(shí)客觀世界的過程中普遍運(yùn)用的思維方法,直觀、自然地描述客觀世界中的有關(guān)事物。面向?qū)ο蠹夹g(shù)的基本特征主要有抽象性、封裝性、繼承性和多態(tài)性。
20世紀(jì)80年代,面向?qū)ο蟮某绦蛟O(shè)計(jì)思想開始在業(yè)界大行其道,逐漸成為主流。而C++(1983)恰好在這個(gè)時(shí)期誕生,自然而然地,C++就選擇了支持面向?qū)ο蟪绦蛟O(shè)計(jì)的思想。
面向?qū)ο笫且环N思想,它讓我們?cè)诜治龊徒鉀Q問題時(shí),把思維和重點(diǎn)轉(zhuǎn)向現(xiàn)實(shí)中的客體中來,然后通過UML等工具理清這些客體之間的聯(lián)系,最后用面向?qū)ο蟮恼Z言實(shí)現(xiàn)這種客體以及客體之間的聯(lián)系。它分為面向?qū)ο蟮姆治?OOA)、面向?qū)ο蟮脑O(shè)計(jì)(OOD)和面向?qū)ο蟮木幊虒?shí)現(xiàn)(OOP)三個(gè)大的步驟:
- 首先是分析需求,先不要思考怎么用程序?qū)崿F(xiàn)它,先分析需求中穩(wěn)定不變的客體都是些什么,這些客體之間的關(guān)系是什么;
- 把第一步分析出來的需求,通過進(jìn)一步擴(kuò)充模型,變成可實(shí)現(xiàn)的、符合成本的、模塊化的、低耦合高內(nèi)聚的模型;
- 使用面向?qū)ο蟮膶?shí)現(xiàn)模型。
當(dāng)我們習(xí)慣了面向過程(結(jié)構(gòu)化)編程時(shí),發(fā)現(xiàn)在程序過程中到處找不到需要面向?qū)ο蟮牡胤?,最主要的原因,是思維沒有轉(zhuǎn)變。程序員通常在拿到一個(gè)需求的時(shí)候,第一個(gè)反應(yīng)就是如何實(shí)現(xiàn)這個(gè)需求,這是典型的面向過程的思維方式,而且可能很快就實(shí)現(xiàn)了它。而面向?qū)ο螅鎸?duì)的卻是客體,第一步不是考慮如何實(shí)現(xiàn)需求,而是進(jìn)行需求分析,就是根據(jù)需求找到其中的客體,再找到這些客體之間的聯(lián)系。因此面向過程和面向?qū)ο蟮乃季S轉(zhuǎn)變的關(guān)鍵點(diǎn),就是在第一步設(shè)計(jì),拿到需求后,一定先不要考慮如何實(shí)現(xiàn)它,而是通過UML建模,然后按照UML模型去實(shí)現(xiàn)它。這種思路的轉(zhuǎn)變,可能需要個(gè)過程。
設(shè)計(jì)模式
設(shè)計(jì)面向?qū)ο蟮能浖容^困難,而設(shè)計(jì)可復(fù)用的面向?qū)ο蟮能浖透永щy。必須找到相關(guān)的對(duì)象,以適當(dāng)?shù)牧6葘⑺鼈儦w類,再定義類的接口和繼承層次,建立對(duì)象之間的基本關(guān)系。有經(jīng)驗(yàn)的面向?qū)ο笤O(shè)計(jì)者的確能做出良好的設(shè)計(jì),而新手則面對(duì)眾多選擇無從下手,總是求助于以前使用過的非面向?qū)ο蠹夹g(shù)。新手需要花費(fèi)較長時(shí)間領(lǐng)會(huì)良好的面向?qū)ο笤O(shè)計(jì)是怎么回事,而有經(jīng)驗(yàn)的設(shè)計(jì)者顯然知道一些新手不知道的東西,這又是什么呢?
內(nèi)行的設(shè)計(jì)者知道,不是解決任何問題都要從頭做起,他們更愿意復(fù)用以前使用過的解決方案。當(dāng)找到一個(gè)好的解決方案,他們會(huì)一遍又一遍地使用。這些經(jīng)驗(yàn)是他們成為內(nèi)行的部分原因。
GoF將模式的概念引入軟件工程領(lǐng)域,這標(biāo)志著軟件模式的誕生。軟件模式并非僅限于設(shè)計(jì)模式,還包括架構(gòu)模式、分析模式和過程模式等。實(shí)際上,在軟件開發(fā)生命周期的每一個(gè)階段都存在著一些被認(rèn)同的模式。軟件模式與具體的應(yīng)用領(lǐng)域無關(guān),也就是說無論從事的是移動(dòng)開發(fā)、桌面開發(fā)、Web開發(fā)還是嵌入式軟件的開發(fā),都可以使用軟件模式。
在軟件模式中,設(shè)計(jì)模式是研究最為深入的分支,它融合了眾多專家的設(shè)計(jì)經(jīng)驗(yàn),已經(jīng)在成千上萬的軟件中得以應(yīng)用。1995年,GoF將收集和整理好的23種設(shè)計(jì)模式匯編成了一本名叫《設(shè)計(jì)模式》的書,該書的出版也標(biāo)志著設(shè)計(jì)模式時(shí)代的到來。這些模式解決特定的設(shè)計(jì)問題,使面向?qū)ο笤O(shè)計(jì)更靈活和優(yōu)雅,最終復(fù)用性更好。他們幫助設(shè)計(jì)者將新的設(shè)計(jì)建立在以往工作的基礎(chǔ)上,復(fù)用以往成功的設(shè)計(jì)方案。一個(gè)熟悉這些模式的設(shè)計(jì)者不需要再去發(fā)現(xiàn)它們,而能夠立即將它們應(yīng)用于設(shè)計(jì)問題中。
設(shè)計(jì)模式使人們可以更加簡單方便地復(fù)用成功的設(shè)計(jì)和體系結(jié)構(gòu),將已證實(shí)的技術(shù)表述成設(shè)計(jì)模式也會(huì)使新系統(tǒng)開發(fā)者更加容易理解其設(shè)計(jì)思路。設(shè)計(jì)模式幫助你做出有利于系統(tǒng)復(fù)用的選擇,避免設(shè)計(jì)損害了系統(tǒng)的復(fù)用性。簡而言之,設(shè)計(jì)模式可以幫助設(shè)計(jì)者更快更好地完成系統(tǒng)設(shè)計(jì)。
守破離是武術(shù)中一種漸進(jìn)的學(xué)習(xí)方法:
- 第一步——守,遵守規(guī)則直到充分理解規(guī)則并將其視為習(xí)慣性的事。
- 第二步——破,對(duì)規(guī)則進(jìn)行反思,尋找規(guī)則的例外并“打破”規(guī)則。
- 第三步——離,在精通規(guī)則之后就會(huì)基本脫離規(guī)則,抓住其精髓和深層能量。
設(shè)計(jì)模式的學(xué)習(xí)也是一個(gè)守破離的過程:
- 第一步——守,在設(shè)計(jì)和應(yīng)用中模仿既有設(shè)計(jì)模式,在模仿中要學(xué)會(huì)思考。
- 第二步——破,熟練使用基本設(shè)計(jì)模式后,創(chuàng)造新的設(shè)計(jì)模式。
- 第三步——離,忘記所有設(shè)計(jì)模式,在設(shè)計(jì)和應(yīng)用中潛移默化的使用。
當(dāng)然,如果你不學(xué)設(shè)計(jì)模式,你可能也在無意識(shí)的使用一些設(shè)計(jì)模式,但是這個(gè)在跟學(xué)過以后再無意識(shí)的使用設(shè)計(jì)模式,應(yīng)該隔著兩重境界吧?
設(shè)計(jì)原則
我們生活在一個(gè)充滿規(guī)則的世界里,在復(fù)雜多變的外表下,萬事萬物都被永恒的真理支配并有規(guī)律的運(yùn)行著。設(shè)計(jì)模式也是一樣,不論那種設(shè)計(jì)模式,其背后都潛藏著一些“永恒的真理”,這個(gè)真理就是設(shè)計(jì)原則。的確,還有什么比原則更重要呢?就像人的世界觀和人生觀一樣,那才是支配你一切行為的根本。對(duì)于設(shè)計(jì)模式來說,為什么這個(gè)模式是這樣解決這個(gè)問題,而另一個(gè)模式卻是那樣解決這個(gè)問題,它們背后都遵循的就是設(shè)計(jì)原則??梢哉f,設(shè)計(jì)原則是設(shè)計(jì)模式的靈魂。
對(duì)于面向?qū)ο筌浖到y(tǒng)的設(shè)計(jì)而言,在支持可維護(hù)性的同時(shí),提高系統(tǒng)的可復(fù)用性是一個(gè)至關(guān)重要的問題,如何同時(shí)提高一個(gè)軟件系統(tǒng)的可維護(hù)性和可復(fù)用性是面向?qū)ο笤O(shè)計(jì)需要解決的核心問題之一。在面向?qū)ο笤O(shè)計(jì)中,可維護(hù)性的復(fù)用是以設(shè)計(jì)原則為基礎(chǔ)的。每一個(gè)原則都蘊(yùn)含一些面向?qū)ο笤O(shè)計(jì)的思想,可以從不同的角度提升一個(gè)軟件結(jié)構(gòu)的設(shè)計(jì)水平。
面向?qū)ο笤O(shè)計(jì)原則是對(duì)面向?qū)ο笏枷氲奶釤挘让嫦驅(qū)ο笏枷氲暮诵囊兀ǚ庋b、繼承和多態(tài))更具可操作性,但與設(shè)計(jì)模式相比,卻又更加的抽象。形象的講,面向?qū)ο笏枷腩愃品ɡ淼木?,設(shè)計(jì)原則類似基本憲法,而設(shè)計(jì)模式就好比各式各樣的具體法律條文。面向?qū)ο笤O(shè)計(jì)原則是我們用于評(píng)價(jià)一個(gè)設(shè)計(jì)模式的使用效果的重要指標(biāo)之一,比如我們?cè)谠O(shè)計(jì)模式的學(xué)習(xí)中,經(jīng)常會(huì)看到諸如“XXX模式符合YYY原則”、“XXX模式違反了ZZZ原則”這樣的語句。
對(duì)于設(shè)計(jì)原則,比如SOLID原則和迪米特法則,大家都能耳熟能詳,但大多數(shù)人對(duì)它們的理解都不太深入,筆者建議初學(xué)者精讀Robert C. Martin在2002年的經(jīng)典著作《敏捷軟件開發(fā)—原則、模式與實(shí)踐》。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
一直以來,我們按照傳統(tǒng)的方式開發(fā)軟件,如下圖所示:
分析模型和設(shè)計(jì)模型的分離,會(huì)導(dǎo)致分析師頭腦中的業(yè)務(wù)模型和設(shè)計(jì)師頭腦中的業(yè)務(wù)模型不一致,通常要映射一下。伴隨著重構(gòu)和bug fix的進(jìn)行,設(shè)計(jì)模型不斷演進(jìn),和分析模型的差異越來越大。有些時(shí)候,分析師站在分析模型的角度認(rèn)為某個(gè)需求較容易實(shí)現(xiàn),而設(shè)計(jì)師站在設(shè)計(jì)模型的角度認(rèn)為該需求較難實(shí)現(xiàn),那么雙方都很難理解對(duì)方的模型。長此以往,在分析模型和設(shè)計(jì)模型之間就會(huì)存在致命的隔閡,從任何活動(dòng)中獲得的知識(shí)都無法提供給另一方。
Eric Evans在2004年出版了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD, Domain-Driven Design)的開山之作《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)——軟件核心復(fù)雜性應(yīng)對(duì)之道》,拋棄將分析模型與設(shè)計(jì)模型分離的做法,尋找單個(gè)模型來滿足兩方面的要求,這就是領(lǐng)域模型。許多系統(tǒng)的真正復(fù)雜之處不在于技術(shù),而在于領(lǐng)域本身,在于業(yè)務(wù)用戶及其執(zhí)行的業(yè)務(wù)活動(dòng)。如果在設(shè)計(jì)時(shí)沒有獲得對(duì)領(lǐng)域的深刻理解,沒有通過模型將復(fù)雜的領(lǐng)域邏輯以模型概念和模型元素的形式清晰地表達(dá)出來,那么無論我們使用多么先進(jìn)、多么流行的平臺(tái)和設(shè)施,都難以保證項(xiàng)目的真正成功。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)分為兩個(gè)階段:
- 以一種領(lǐng)域?qū)<?、設(shè)計(jì)人員和開發(fā)人員都能理解的通用語言作為相互交流的工具,在交流的過程中發(fā)現(xiàn)領(lǐng)域概念,然后將這些概念設(shè)計(jì)成一個(gè)領(lǐng)域模型;
- 由領(lǐng)域模型驅(qū)動(dòng)軟件設(shè)計(jì),用代碼來表達(dá)該領(lǐng)域模型。
由此可見,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的核心是建立正確的領(lǐng)域模型。
領(lǐng)域?qū)<摇⒃O(shè)計(jì)人員和開發(fā)人員一起創(chuàng)建一套適用于領(lǐng)域建模的通用語言,通用語言必須在團(tuán)隊(duì)范圍內(nèi)達(dá)成一致。所有成員都使用通用語言進(jìn)行交流,每個(gè)人都能聽懂別人在說什么,通用語言也是對(duì)軟件模型的直接反映。領(lǐng)域?qū)<摇⒃O(shè)計(jì)人員和開發(fā)人員一起工作,這樣開發(fā)出來的軟件能夠準(zhǔn)確的表達(dá)業(yè)務(wù)規(guī)則。領(lǐng)域模型基于通用語言,是關(guān)于某個(gè)特定業(yè)務(wù)領(lǐng)域的軟件模型,如下圖所示:
一個(gè)通用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的架構(gòu)性解決方案包含四個(gè)概念層,就是經(jīng)典的四層模型,如下圖所示:
- User Interface為用戶界面/展現(xiàn)層,負(fù)責(zé)向用戶展現(xiàn)信息以及解釋用戶命令。
- Application為應(yīng)用層,是很薄的一層,定義軟件要完成的所有任務(wù)。對(duì)外為展現(xiàn)層提供各種應(yīng)用功能(包括查詢或命令),對(duì)內(nèi)調(diào)用領(lǐng)域?qū)樱I(lǐng)域?qū)ο蠡蝾I(lǐng)域服務(wù))完成各種業(yè)務(wù)邏輯,應(yīng)用層不包含業(yè)務(wù)邏輯。
- Domain為領(lǐng)域?qū)?,?fù)責(zé)表達(dá)業(yè)務(wù)概念,業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則,領(lǐng)域模型處于這一層,是業(yè)務(wù)軟件的核心。
- Infrastructure層為基礎(chǔ)實(shí)施層,向其他層提供通用的技術(shù)能力;提供了層間的通信;為領(lǐng)域?qū)訉?shí)現(xiàn)持久化機(jī)制;總之,基礎(chǔ)設(shè)施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求。
DCI架構(gòu)模式
James O. Coplien和Trygve Reenskaug在2009年發(fā)表了一篇論文《DCI架構(gòu):面向?qū)ο缶幊痰男聵?gòu)想》,標(biāo)志著DCI架構(gòu)模式的誕生。有趣的是James O. Coplien也是MVC架構(gòu)模式的創(chuàng)造者,這個(gè)大叔一輩子就干了兩件事,即年輕時(shí)創(chuàng)造了MVC和年老時(shí)創(chuàng)造了DCI,其他時(shí)間都在思考,讓我輩望塵莫及。
面向?qū)ο缶幊痰谋疽馐菍⒊绦騿T與用戶的視角統(tǒng)一于計(jì)算機(jī)代碼之中:對(duì)提高可用性和降低程序的理解難度來說,都是一種恩賜。可是雖然對(duì)象很好地反映了結(jié)構(gòu),但在反映系統(tǒng)的動(dòng)作方面卻失敗了,DCI的構(gòu)想是期望反映出最終用戶的認(rèn)知模型中的角色以及角色之間的交互。
傳統(tǒng)上,面向?qū)ο缶幊陶Z言拿不出辦法去捕捉對(duì)象之間的協(xié)作,反映不了協(xié)作中往來的算法。就像對(duì)象的實(shí)例反映出領(lǐng)域結(jié)構(gòu)一樣,對(duì)象的協(xié)作與交互同樣是有結(jié)構(gòu)的。協(xié)作與交互也是最終用戶心智模型的組成部分,但你在代碼中找不到一個(gè)內(nèi)聚的表現(xiàn)形式去代表它們。在本質(zhì)上,角色體現(xiàn)的是一般化的、抽象的算法。角色
沒有血肉,并不能做實(shí)際的事情,歸根結(jié)底工作還是落在對(duì)象的頭上,而對(duì)象本身還擔(dān)負(fù)著體現(xiàn)領(lǐng)域模型的責(zé)任。
人們心目中對(duì)“對(duì)象”這個(gè)統(tǒng)一的整體卻有兩種不同的模型,即“系統(tǒng)是什么”和“系統(tǒng)做什么”,這就是DCI要解決的根本問題。用戶認(rèn)知一個(gè)個(gè)對(duì)象和它們所代表的領(lǐng)域,而每個(gè)對(duì)象還必須按照用戶心目中的交互模型去實(shí)現(xiàn)一些行為,通過它在用例中所扮演的角色與其他對(duì)象聯(lián)結(jié)在一起。正因?yàn)樽罱K用戶能把兩種視角合為一體,類的對(duì)象除了支持所屬類的成員函數(shù),還可以執(zhí)行所扮演角色的成員函數(shù),就好像那些函數(shù)屬于對(duì)象本身一樣。換句話說,我們希望把角色的邏輯注入到對(duì)象,讓這些邏輯成為對(duì)象的一部分,而其地位卻絲毫不弱于對(duì)象初始化時(shí)從類所得到的方法。我們?cè)诰幾g時(shí)就為對(duì)象安排好了扮演角色時(shí)可能需要的所有邏輯。如果我們?cè)俾斆饕稽c(diǎn),在運(yùn)行時(shí)知道了被分配的角色,才注入剛好要用到的邏輯,也是可以做到的。
算法及角色-對(duì)象映射由Context擁有。Context“知道”在當(dāng)前用例中應(yīng)該找哪個(gè)對(duì)象去充當(dāng)實(shí)際的演員,然后負(fù)責(zé)把對(duì)象“cast”成場(chǎng)景中的相應(yīng)角色。(cast 這個(gè)詞在戲劇界是選角的意思,此處的用詞至少符合該詞義,另一方面的用意是聯(lián)想到cast 在某些編程語言類型系統(tǒng)中的含義。)在典型的實(shí)現(xiàn)里,每個(gè)用例都有其對(duì)應(yīng)的一個(gè)Context 對(duì)象,而用例涉及到的每個(gè)角色在對(duì)應(yīng)的Context 里也都有一個(gè)標(biāo)識(shí)符。Context 要做的只是將角色標(biāo)識(shí)符與正確的對(duì)象綁定到一起。然后我們只要觸發(fā)Context里的“開場(chǎng)”角色,代碼就會(huì)運(yùn)行下去。
于是我們有了完整的DCI架構(gòu)(Data、Context和Interactive三層架構(gòu)):
Data層描述系統(tǒng)有哪些領(lǐng)域概念及其之間的關(guān)系,該層專注于領(lǐng)域?qū)ο蠛椭g關(guān)系的確立,讓程序員站在對(duì)象的角度思考系統(tǒng),從而讓“系統(tǒng)是什么”更容易被理解。
Context層:是盡可能薄的一層。Context往往被實(shí)現(xiàn)得無狀態(tài),只是找到合適的role,讓role交互起來完成業(yè)務(wù)邏輯即可。但是簡單并不代表不重要,顯示化context層正是為人去理解軟件業(yè)務(wù)流程提供切入點(diǎn)和主線。
Interactive層主要體現(xiàn)在對(duì)role的建模,role是每個(gè)context中復(fù)雜的業(yè)務(wù)邏輯的真正執(zhí)行者,體現(xiàn)“系統(tǒng)做什么”。Role所做的是對(duì)行為進(jìn)行建模,它聯(lián)接了context和領(lǐng)域?qū)ο?。由于系統(tǒng)的行為是復(fù)雜且多變的,role使得系統(tǒng)將穩(wěn)定的領(lǐng)域模型層和多變的系統(tǒng)行為層進(jìn)行了分離,由role專注于對(duì)系統(tǒng)行為進(jìn)行建模。該層往往關(guān)注于系統(tǒng)的可擴(kuò)展性,更加貼近于軟件工程實(shí)踐,在面向?qū)ο笾懈嗟氖且灶惖囊暯沁M(jìn)行思考設(shè)計(jì)。
DCI目前廣泛被作為對(duì)DDD的一種發(fā)展和補(bǔ)充,用于基于面向?qū)ο蟮念I(lǐng)域建模。顯示的對(duì)role進(jìn)行建模,解決了面向?qū)ο蠼V谐溲拓氀P椭疇帯CI通過顯示的用role對(duì)行為進(jìn)行建模,同時(shí)讓role在context中可以和對(duì)應(yīng)的領(lǐng)域?qū)ο筮M(jìn)行綁定(cast),從而既解決了數(shù)據(jù)邊界和行為邊界不一致的問題,也解決了領(lǐng)域?qū)ο笾袛?shù)據(jù)和行為高內(nèi)聚低耦合的問題。
面向?qū)ο蠼C媾R的一個(gè)棘手問題是數(shù)據(jù)邊界和行為邊界往往不一致。遵循模塊化的思想,我們通過類將行為和其緊密耦合的數(shù)據(jù)封裝在一起。但是在復(fù)雜的業(yè)務(wù)場(chǎng)景下,行為往往跨越多個(gè)領(lǐng)域?qū)ο?,這樣的行為放在某一個(gè)對(duì)象中必然導(dǎo)致別的對(duì)象需要向該對(duì)象暴漏其內(nèi)部狀態(tài)。所以面向?qū)ο蟀l(fā)展的后來,領(lǐng)域建模出現(xiàn)兩種派別之爭,一種傾向于將跨越多個(gè)領(lǐng)域?qū)ο蟮男袨榻T陬I(lǐng)域服務(wù)中。這種做法使用過度經(jīng)常導(dǎo)致領(lǐng)域?qū)ο笞兂芍惶峁┮欢裧et方法的啞對(duì)象,這種建模導(dǎo)致的結(jié)果被稱之為貧血模型。而另一派則堅(jiān)定的認(rèn)為方法應(yīng)該屬于領(lǐng)域?qū)ο?,所以所有的業(yè)務(wù)行為仍然被放在領(lǐng)域?qū)ο笾?,這樣導(dǎo)致領(lǐng)域?qū)ο箅S著支持的業(yè)務(wù)場(chǎng)景變多而變成上帝類,而且類內(nèi)部方法的抽象層次很難一致。另外由于行為邊界很難恰當(dāng),導(dǎo)致對(duì)象之間數(shù)據(jù)訪問關(guān)系也比較復(fù)雜。這種建模導(dǎo)致的結(jié)果被稱之為充血模型。
DCI和袁英杰大師提出的“小類大對(duì)象”殊途同歸,即類應(yīng)該是小的,對(duì)象應(yīng)該是大的。上帝類是糟糕的,但上帝對(duì)象卻恰恰是我們所期盼的。而從類到對(duì)象,是一種多對(duì)一的關(guān)系:最終一個(gè)對(duì)象是由諸多單一職責(zé)的小類——它們分別都可以有自己的數(shù)據(jù)和行為——所構(gòu)成。而將類映射到對(duì)象的過程,在Ruby中通過Mixin;在Scala中則通過Traits;而C++則通過多重繼承。
舉個(gè)生活中的例子:
人有多重角色,不同的角色履行的職責(zé)不同:
- 作為父母:我們要給孩子講故事,陪他們玩游戲,哄它們睡覺;
- 作為子女:我們則要孝敬父母,聽取他們的人生建議;
- 作為下屬:在老板面前,我們需要聽從其工作安排;
- 作為上司:需要安排下屬工作,并進(jìn)行培養(yǎng)和激勵(lì);
- ...
這里人(大對(duì)象)聚合了多個(gè)角色(小類),在某種場(chǎng)景下,只能扮演特定的角色:
- 在孩子面前,我們是父母;
- 在父母面前,我們是子女;
- 職場(chǎng)上,在上司面前,我們是下屬;
- 在下屬面前,你是上司
- ...
對(duì)于通信系統(tǒng)軟件,沒有UI層,應(yīng)用層也很薄,所以傳統(tǒng)的DDD的四層模型并不適用。DCI提出后,針對(duì)通信系統(tǒng)軟件,我們將DDD的分層架構(gòu)重新定義一下,如下圖所示:
- Schedule是調(diào)度層,維護(hù)UE的狀態(tài)模型,除過業(yè)務(wù)本質(zhì)狀態(tài),還有實(shí)現(xiàn)狀態(tài)。當(dāng)調(diào)度層收到消息后,將委托Context層的Action進(jìn)行處理。
- Context是環(huán)境層(對(duì)應(yīng)DCI中的Context),以Action為單位,處理一條同步消息或異步消息,將Domain層的領(lǐng)域?qū)ο骳ast成合適的role,讓role交互起來完成業(yè)務(wù)邏輯。
- Domain層定義領(lǐng)域模型,不僅包括領(lǐng)域?qū)ο蠹捌渲g關(guān)系的建模(對(duì)應(yīng)DCI中的Data),還包括對(duì)象的角色role的顯式建模(對(duì)應(yīng)DCI中的Interaction)。
- Infrastructure層為基礎(chǔ)實(shí)施層,為其他層提供通用的技術(shù)能力;提供了層間的通信;為領(lǐng)域?qū)訉?shí)現(xiàn)持久化機(jī)制;總之,基礎(chǔ)設(shè)施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求。
領(lǐng)域?qū)S谜Z言
DSL(Domain Specific Language)一般譯作領(lǐng)域?qū)S谜Z言或領(lǐng)域特定語言,故名思義,是針對(duì)某個(gè)特定領(lǐng)域而開發(fā)的語言。像我們平時(shí)接觸到的C、C++和Java等都屬于通用語言,可以為各個(gè)領(lǐng)域編程,雖然通用性有余,但針對(duì)性不強(qiáng),所以DSL是為了彌補(bǔ)通用語言的這個(gè)劣勢(shì)而出現(xiàn)的。
軟件開發(fā)“教父”Martin Fowler在2010出版的《領(lǐng)域特定語言》是DSL領(lǐng)域的豐碑之作,掀起來DSL編程的熱潮。DSL其實(shí)并沒有那么神秘。實(shí)際上,在平時(shí)的面向?qū)ο蟮木幊讨?,大家?huì)自覺不自覺的使用DSL的一些方法和技巧。比如,如果我們定義了一些非常面向業(yè)務(wù)的函數(shù),然后這些函數(shù)的集合就可以被看作一種DSL了。雖然DSL和面向業(yè)務(wù)的函數(shù)之間是有一些類似之處,但這只是問題的一個(gè)方面,DSL更多是從客戶的角度出發(fā)看待代碼,定義函數(shù)則更多的從解決問題的方案的角度看待代碼。誠然兩者都有交集,但是出發(fā)點(diǎn)卻截然不同。
按照Martin Fowler的看法,DSL可以分為兩種基本類型,即內(nèi)部DSL和外部DSL。顧名思義,外部DSL就相當(dāng)于實(shí)現(xiàn)一種編程語言,也許不如實(shí)現(xiàn)一門通用語言那么復(fù)雜,但是工作量不??;內(nèi)部DSL就是在一種通用編程語言的基礎(chǔ)上進(jìn)行關(guān)鍵字的定義封裝來達(dá)到DSL的目的,這種DSL的擴(kuò)展性可能會(huì)受到母語言的影響,對(duì)于不熟悉母語言的人來說可能不是那么好理解,不過好處就是你可以利用母語言本身的功能。
袁英杰大師原創(chuàng)的transaction DSL是一種內(nèi)部DSL(it is C++),用于降低業(yè)務(wù)的實(shí)現(xiàn)復(fù)雜度,使得調(diào)度層只需處理業(yè)務(wù)的本質(zhì)狀態(tài),而所有非穩(wěn)態(tài)都是原子的事務(wù)過程,如下圖所示:
有了transaction DSL之后,針對(duì)通信系統(tǒng)軟件的DDD四層模型可以演進(jìn)為五層模型,如下圖所示:
- Schedule是調(diào)度層,維護(hù)UE的狀態(tài)模型,只包括業(yè)務(wù)的本質(zhì)狀態(tài),將接收到的消息派發(fā)給transaction DSL層。
- transaction DSL是事務(wù)層,對(duì)應(yīng)一個(gè)業(yè)務(wù)流程,比如UE Attach,將各個(gè)同步消息或異步消息的處理組合成一個(gè)事務(wù),當(dāng)事務(wù)失敗時(shí),進(jìn)行回滾。當(dāng)事務(wù)層收到調(diào)度層的消息后,委托環(huán)境層的Action進(jìn)行處理。
- Context是環(huán)境層(對(duì)應(yīng)DCI中的Context),以Action為單位,處理一條同步消息或異步消息,將Domain層的領(lǐng)域?qū)ο骳ast成合適的role,讓role交互起來完成業(yè)務(wù)邏輯。
- Domain層定義領(lǐng)域模型,不僅包括領(lǐng)域?qū)ο蠹捌渲g關(guān)系的建模(對(duì)應(yīng)DCI中的Data),還包括對(duì)象的角色role的顯式建模(對(duì)應(yīng)DCI中的Interaction)。
- Infrastructure層為基礎(chǔ)實(shí)施層,為其他層提供通用的技術(shù)能力;提供了層間的通信;為領(lǐng)域?qū)訉?shí)現(xiàn)持久化機(jī)制;總之,基礎(chǔ)設(shè)施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求。
微服務(wù)架構(gòu)模式
軟件“教父”Martin Fowler在2012年提出微服務(wù)這一概念,于是出現(xiàn)了兩種服務(wù)架構(gòu)模式,即單體架構(gòu)模式和微服務(wù)架構(gòu)模式,如下圖所示:
微服務(wù)是指開發(fā)一個(gè)單個(gè)小型的但有業(yè)務(wù)功能的服務(wù),可以選擇自己的技術(shù)棧和數(shù)據(jù)庫,可以選擇自己的通訊機(jī)制,可以部署在單個(gè)或多個(gè)服務(wù)器上。這里的“微”不是針對(duì)代碼行數(shù)而言,而是說服務(wù)的范圍不能大于DDD中的一個(gè)BC(Bounded Context,限界上下文)。
微服務(wù)架構(gòu)模式的優(yōu)點(diǎn):
- 微服務(wù)只關(guān)注一個(gè)BC,業(yè)務(wù)簡單
- 不同微服務(wù)可由不同團(tuán)隊(duì)開發(fā)
- 微服務(wù)是松散耦合的
- 每個(gè)微服務(wù)可選擇不同的編程語言和工具開發(fā)
- 每個(gè)微服務(wù)可根據(jù)業(yè)務(wù)邏輯和負(fù)荷選擇一個(gè)最合適的數(shù)據(jù)庫
微服務(wù)架構(gòu)模式的挑戰(zhàn):
- 分布式系統(tǒng)的復(fù)雜性,比如事務(wù)一致性、網(wǎng)絡(luò)延遲、容錯(cuò)、對(duì)象持久化、消息序列化、異步、版本控制和負(fù)載等
- 更多的服務(wù)意味著更高水平的DevOps和自動(dòng)化技術(shù)
- 服務(wù)接口修改會(huì)波及相關(guān)的所有服務(wù)
- 服務(wù)間可能存在重復(fù)的功能點(diǎn)
- 測(cè)試更加困難
盡管微服務(wù)架構(gòu)模式對(duì)“個(gè)子”的要求比較高,但隨著容器云技術(shù)的不斷成熟,微服務(wù)架構(gòu)模式卻越來越火,似乎所有系統(tǒng)的架構(gòu)都在盡情擁抱微服務(wù),這是不是意味著單體架構(gòu)模式不再是我們的選擇了呢?筆者認(rèn)為需要根據(jù)具體情況而定,我們看看下面這張圖:
上圖直觀的說明了單體架構(gòu)和微服務(wù)架構(gòu)在不同系統(tǒng)復(fù)雜度下不同的生產(chǎn)力,以及兩者的對(duì)比關(guān)系。對(duì)于那種需要快速為商業(yè)模式提供驗(yàn)證的系統(tǒng),在其功能較少和用戶量較低的情況下,單體架構(gòu)模式是更好的選擇,但在單體架構(gòu)內(nèi)部,需要清晰的劃分功能模塊,盡量做到高內(nèi)聚低耦合。
總而言之,微服務(wù)架構(gòu)有很多吸引人的地方,不過在擁抱微服務(wù)之前要認(rèn)清它所帶來的挑戰(zhàn)。每一種架構(gòu)模式都有其優(yōu)缺點(diǎn),我們需要根據(jù)項(xiàng)目和團(tuán)隊(duì)的實(shí)際情況來選擇最合適的架構(gòu)模式。
小結(jié)
本文較為詳細(xì)的闡述了軟件設(shè)計(jì)的演變過程,包括結(jié)構(gòu)化程序設(shè)計(jì)、面向?qū)ο蟪绦蛟O(shè)計(jì)、設(shè)計(jì)模式、設(shè)計(jì)原則、DDD、DCI、DSL和微服務(wù)架構(gòu)模式,通過對(duì)這些設(shè)計(jì)思想的全面梳理,可以幫助我們做出更好的設(shè)計(jì)決策。