3. 可變更性
前面我提到了,軟件的變更性是所有軟件理論的核心,那么什么是軟件的可變更性呢?按照現(xiàn)在的軟件理論,客戶對軟件的需求時時刻刻在發(fā)生著變化。當軟件設(shè)計好以后,為應對客戶需求的變更而進行的代碼修改,其所需要付出的代價,就是軟件設(shè)計的可變更性。由于軟件合理地設(shè)計,修改所付出的代價越小,則軟件的可變更性越好,即代碼設(shè)計的質(zhì)量越高。一種非常理想的狀態(tài)是,無論客戶需求怎樣變化,軟件只需進行適當?shù)匦薷木湍軌蜻m應。但這之所以稱之為理想狀態(tài),因為客戶需求變化是有大有小的。如果客戶需求變化非常大,即使再好的設(shè)計也無法應付,甚至重新開發(fā)。然而,客戶需求的適當變化,一個合理地設(shè)計可以使得變更代價最小化,延續(xù)我們設(shè)計的軟件的生命力。
?
?
1 )通過提高代碼復用提高可維護性
我曾經(jīng)遇到過這樣一件事,我要維護的一個系統(tǒng)因為應用范圍的擴大,它對機關(guān)級次的計算方式需要改變一種策略。如果這個項目統(tǒng)一采用一段公用方法來計算機關(guān)級次,這樣一個修改實在太簡單了,就是修改這個公用方法即可。但是,事實卻不一樣,對機關(guān)級次計算的代碼遍布整個項目,甚至有些還寫入到了那些復雜的 SQL 語句中。在這樣一種情況下,這樣一個需求的修改無異于需要遍歷這個項目代碼。這樣一個實例顯示了一個項目代碼復用的重要,然而不幸的是,代碼無法很好復用的情況遍布我們所有的項目。代碼復用的道理十分簡單,但要具體運作起來非常復雜,它除了需要很好的代碼規(guī)劃,還需要持續(xù)地代碼重構(gòu)。
對整個系統(tǒng)的整體分析與合理規(guī)劃可以根本地保證代碼復用。系統(tǒng)分析師通過用例模型、領(lǐng)域模型、分析模型的一步一步分析,最后通過正向工程,生成系統(tǒng)需要設(shè)計的各種類及其各自的屬性和方法。采用這種方法,功能被合理地劃分到這個類中,可以很好地保證代碼復用。
采用以上方法雖然好,但技術(shù)難度較高,需要有高深的系統(tǒng)分析師,并不是所有項目都能普遍采用的,特別是時間比較緊張的項目。通過開發(fā)人員在設(shè)計過程中的重構(gòu),也許更加實用。當某個開發(fā)人員在開發(fā)一段代碼時,發(fā)現(xiàn)該功能與前面已經(jīng)開發(fā)功能相同,或者部分相同。這時,這個開發(fā)人員可以對前面已經(jīng)開發(fā)的功能進行重構(gòu),將可以通用的代碼提取出來,進行相應地改造,使其具有一定的通用性,便于各個地方可以使用。
一些比較成功的項目組會指定一個專門管理通用代碼的人,負責收集和整理項目組中各個成員編寫的,可以通用的代碼。這個負責人同時也應當具有一定的代碼編寫功力,因為將專用代碼提升為通用代碼,或者以前使用該通用代碼的某個功能,由于業(yè)務(wù)變更,而對這個通用代碼的變更要求,都對這個負責人提出了很高的能力要求。
雖然后一種方式非常實用,但是它有些亡羊補牢的味道,不能從整體上對項目代碼進行有效規(guī)劃。正因為兩種方法各有利弊,因此在項目中應當配合使用。
?
?
2 )利用設(shè)計模式提高可變更性
對于初學者,軟件設(shè)計理論常常感覺晦澀難懂。一個快速提高軟件質(zhì)量的捷徑就是利用設(shè)計模式。這里說的設(shè)計模式,不僅僅指經(jīng)典的 32 個模式,是一切前人總結(jié)的,我們可以利用的、更加廣泛的設(shè)計模式。
?
a. if...else...
這個我也不知道叫什么名字,最早是哪位大師總結(jié)的,它出現(xiàn)在 Larman 的《 UML 與模式應用》,也出現(xiàn)在出現(xiàn)在 Mardin 的《敏捷軟件開發(fā)》。它是這樣描述的:當你發(fā)現(xiàn)你必須要設(shè)計這樣的代碼:“ if...elseif...elseif...else... ”時,你應當想到你的代碼應當重構(gòu)一下了。我們先看看這樣的代碼有怎樣的特點。
if(var.equals("A")){ doA(); } else if(var.equals("B")){ doB(); } else if(var.equals("C")){ doC(); } else{ doD(); }
?
?
?
這樣的代碼很常見,也非常平常,我們大家都寫過。但正是這樣平常才隱藏著我們永遠沒有注意的問題。問題就在于,如果某一天這個選項不再僅僅是 A 、 B 、 C ,而是增加了新的選項,會怎樣呢?你也許會說,那沒有關(guān)系,我把代碼改改就行。然而事實上并非如此,在大型軟件研發(fā)與維護中有一個原則,每次的變更盡量不要去修改原有的代碼。如果我們重構(gòu)一下,能保證不修改原有代碼,僅僅增加新的代碼就能應付選項的增加,這就增加了這段代碼的可維護性和可變更性,提高了代碼質(zhì)量。那么,我們應當如何去做呢?
?
經(jīng)過深入分析你會發(fā)現(xiàn),這里存在一個對應關(guān)系,即 A 對應 doA() , B 對應 doB() ...如果將 doA() 、 doB() 、 doC() ...與原有代碼解耦,問題就解決了。如何解耦呢?設(shè)計一個接口 X 以及它的實現(xiàn) A 、 B 、 C ...每個類都包含一個方法 doX() ,并且將 doA() 的代碼放到 A.doX() 中,將 doB() 的代碼放到 B.doX() 中...經(jīng)過以上的重構(gòu),代碼還是這些代碼,效果卻完全不一樣了。我們只需要這樣寫:
X x = factory.getBean(var); x.doX();
?
?
?
這樣就可以實現(xiàn)以上的功能了。我們看到這里有一個工廠,放著所有的 A 、 B 、 C ...并且與它們的 key 對應起來,并且寫在配置文件中。如果出現(xiàn)新的選項時,通過修改配置文件就可以無限制的增加下去。
這個模式雖然有效提高了代碼質(zhì)量,但是不能濫用,并非只要出現(xiàn) if...else... 就需要使用。由于它使用了工廠,一定程度上增加了代碼復雜度,因此僅僅在選項較多,并且增加選項的可能性很大的情況下才可以使用。另外,要使用這個模式,繼承我在附件中提供的抽象類 XmlBuildFactoryFacade 就可以快速建立一個工廠。如果你的項目放在 spring 或其它可配置框架中,也可以快速建立工廠。設(shè)計一個 Map 靜態(tài)屬性并使其 V 為這些 A 、 B 、 C ...這個工廠就建立起來了。
?
b. 策略模式
也許你看過策略模式( strategy model )的相關(guān)資料但沒有留下太多的印象。一個簡單的例子可以讓你快速理解它。如果一個員工系統(tǒng)中,員工被分為臨時工和正式工并且在不同的地方相應的行為不一樣。在設(shè)計它們的時候,你肯定設(shè)計一個抽象的員工類,并且設(shè)計兩個繼承類:臨時工和正式工。這樣,通過下塑類型,可以在不同的地方表現(xiàn)出臨時工和正式工的各自行為。在另一個系統(tǒng)中,員工被分為了銷售人員、技術(shù)人員、管理人員并且也在不同的地方相應的行為不一樣。同樣,我們在設(shè)計時也是設(shè)計一個抽象的員工類,并且設(shè)計數(shù)個繼承類:銷售人員、技術(shù)人員、管理人員。現(xiàn)在,我們要把這兩個系統(tǒng)合并起來,也就是說,在新的系統(tǒng)中,員工既被分為臨時工和正式工,又被分為了銷售人員、技術(shù)人員、管理人員,這時候如何設(shè)計。如果我們還是使用以往的設(shè)計,我們將不得不設(shè)計很多繼承類:銷售臨時工、銷售正式工、技術(shù)臨時工、技術(shù)正式工...如此的設(shè)計,在隨著劃分的類型,以及每種類型的選項的增多,呈笛卡爾增長。通過以上一個系統(tǒng)的設(shè)計,我們不得不發(fā)現(xiàn),我們以往學習的關(guān)于繼承的設(shè)計遇到了挑戰(zhàn)。
解決繼承出現(xiàn)的問題,有一個最好的辦法,就是采用策略模式。在這個應用中,員工之所以要分為臨時工和正式工,無非是因為它們的一些行為不一樣,比如,發(fā)工資時的計算方式不同。如果我們在設(shè)計時不將員工類分為臨時工類和正式工類,而僅僅只有員工類,只是在類中增加“工資發(fā)放策略”。當我們創(chuàng)建員工對象時,根據(jù)員工的類型,將“工資發(fā)放策略”設(shè)定為“臨時工策略”或“正式工策略”,在計算工資時,只需要調(diào)用策略類中的“計算工資”方法,其行為的表現(xiàn),也設(shè)計臨時工類和正式工類是一樣的。同樣的設(shè)計可以放到銷售人員策略、技術(shù)人員策略、管理人員策略中。一個通常的設(shè)計是,我們將某一個影響更大的、或者選項更少的屬性設(shè)計成繼承類,而將其它屬性設(shè)計成策略類,就可以很好的解決以上問題。
?
?
?
使用策略模式,你同樣把代碼寫活了,因為你可以無限制地增加策略。但是,使用策略模式你同樣需要設(shè)計一個工廠——策略工廠。以上實例中,你需要設(shè)計一個發(fā)放工資策略工廠,并且在工廠中將“臨時工”與“臨時工策略”對應起來,將“正式工”與“正式工策略”對應起來。
?
c. 適配器模式
我的筆記本是港貨,它的插頭與我們常用的插座不一樣,所有我出差的時候我必須帶一個適配器,才能使用不同地方的插座。這是一個對適配器模式最經(jīng)典的描述。當我們設(shè)計的系統(tǒng)要與其它系統(tǒng)交互,或者我們設(shè)計的模塊要與其它模塊交互時,這種交互可能是調(diào)用一個接口,或者交換一段數(shù)據(jù),接受方常常因發(fā)送方對協(xié)議的變更而頻繁變更。這種變更,可能是接受方來源的變更,比如原來是 A 系統(tǒng),現(xiàn)在變成 B 系統(tǒng)了;也可能是接受方自身的代碼變更,如原來的接口現(xiàn)在增加了一個參數(shù)。由于發(fā)送方的變更常常導致接受方代碼的不穩(wěn)定,即頻繁跟著修改,為接受方的維護帶來困難。
遇到這樣的問題,一個有經(jīng)驗的程序員馬上想到的就是采用適配器模式。在設(shè)計時,我方的接口按照某個協(xié)議編寫,并且保持固定不變。然后,在與真正對方接口時,在前段設(shè)計一個適配器類,一旦對方協(xié)議發(fā)生變更,我可以換個適配器,將新協(xié)議轉(zhuǎn)換成原協(xié)議,問題就解決了。適配器模式應當包含一個接口和它的實現(xiàn)類。接口應當包含一個本系統(tǒng)要調(diào)用的方法,而它的實現(xiàn)類分別是與 A 系統(tǒng)接口的適配器、與 B 系統(tǒng)接口的適配器...
?
?
?
我曾經(jīng)在一個項目中需要與另一個系統(tǒng)接口,起初那個系統(tǒng)通過一個數(shù)據(jù)集的方式為我提供數(shù)據(jù),我寫了一個接收數(shù)據(jù)集的適配器;后來改為用一個 XML 數(shù)據(jù)流的形式,我又寫了一個接收 XML 的適配器。雖然為我提供數(shù)據(jù)的方式不同,但是經(jīng)過適配器轉(zhuǎn)換后,輸出的數(shù)據(jù)是一樣的。通過在 spring 中的配置,我可以靈活地切換到底是使用哪個適配器。
?
d. 模板模式
32 個經(jīng)典模式中的模板模式,對開發(fā)者的代碼規(guī)劃能力提出了更高的要求,它要求開發(fā)者對自己開發(fā)的所有代碼有一個相互聯(lián)系和從中抽象的能力,從各個不同的模塊和各個不同的功能中,抽象出其過程比較一致的通用流程,最終形成模板。譬如說,讀取 XML 并形成工廠,是許多模塊常常要使用的功能。它們雖然有各自的不同,但是總體流程都是一樣的:讀取 XML 文件、解析 XML 數(shù)據(jù)流、形成工廠。正因為有這樣的特征,它們可以使用共同的模板,那么,什么是模板模式呢?
模板模式( Template Model )通常有一個抽象類。在這個抽象類中,通常有一個主函數(shù),按照一定地順序去調(diào)用其它函數(shù)。而其它函數(shù)往往是某這個連續(xù)過程中的各個步驟,如以上實例中的讀取 XML 文件、解析 XML 數(shù)據(jù)流、形成工廠等步驟。由于這是一個抽象類,這些步驟函數(shù)可以是抽象函數(shù)。抽象類僅僅定義了整個過程的執(zhí)行順序,以及一些可以通用的步驟(如讀取 XML 文件和解析 XML 數(shù)據(jù)流),而另一些比較個性的步驟,則由它的繼承類自己去完成(如上例中的“形成工廠”,由于各個工廠各不一樣,因此由各自的繼承類自己去決定它的工廠是怎樣形成的)。
?
?
?
各個繼承類可以根據(jù)自己的需要,通過重載重新定義各個步驟函數(shù)。但是,模板模式要求不能重載主函數(shù),因此正規(guī)的模板模式其主函數(shù)應當是 final (雖然我們常常不這么寫)。另外,模板模式還允許你定義的這個步驟中,有些步驟是可選步驟。對與可選步驟,我們通常稱為“鉤子( hood )”。它在編寫時,在抽象類中并不是一個抽象函數(shù),但卻是一個什么都不寫的空函數(shù)。繼承類在編寫時,如果需要這個步驟則重載這個函數(shù),否則就什么也不寫,進而在執(zhí)行的時候也如同什么都沒有執(zhí)行。
通過以上對模板模式的描述可以發(fā)現(xiàn),模板模式可以大大地提高我們的代碼復用程度。
以上一些常用設(shè)計模式,都能使我們快速提高代碼質(zhì)量。還是那句話,設(shè)計模式不是什么高深的東西,恰恰相反,它是初學者快速提高的捷徑。然而,如果說提高代碼復用是提高代碼質(zhì)量的初階,使用設(shè)計模式也只能是提高代碼質(zhì)量的中階。那么,什么是高階呢?我認為是那些分析設(shè)計理論,更具體地說,就是職責驅(qū)動設(shè)計和領(lǐng)域驅(qū)動設(shè)計。
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
