程序設(shè)計原則_第1頁
程序設(shè)計原則_第2頁
程序設(shè)計原則_第3頁
程序設(shè)計原則_第4頁
程序設(shè)計原則_第5頁
已閱讀5頁,還剩36頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

軟件設(shè)計模式第二章面向?qū)ο蟪绦蛟O(shè)計原則提綱二.一單一職責原則(SRP)二.二"開/閉"原則(OCP)二.三接口隔離原則(ISP)二.四依賴倒置原則(DIP)二.五Liskov替換原則(LSP)二.一單一職責原則(SRP)單一職責面向?qū)ο笤O(shè)計原則強調(diào):一個類或模塊應(yīng)僅有一個引起其變化地因素(職責)。通俗地講,一個類或模塊應(yīng)只承擔一個(或一種類型地)業(yè)務(wù)職責當類承擔地業(yè)務(wù)職責較多時,該類任何職責需求地變化都會引起靜態(tài)實現(xiàn)地變化,與導(dǎo)致其代碼不穩(wěn)定無論是向目地類添加新地代碼,或修改已有地代碼,都是對原有代碼設(shè)計地破壞,導(dǎo)致程序開發(fā)者花費巨大成本行代碼修復(fù)圖二.一多個業(yè)務(wù)行為地Patron類例如,COS系統(tǒng)客戶角色具有地業(yè)務(wù)行為有:登錄系統(tǒng),支付訂單,查看菜單等,如圖二.一。如果將所有地業(yè)務(wù)行為實現(xiàn)在客戶類Patron,當這些業(yè)務(wù)行為地任何一個需求發(fā)生變化時,如支付或登錄行為需求發(fā)生變化,都需要修改Patron類。即,影響Patron類變化地因素多于一個時,會導(dǎo)致Patron類地代碼變得不穩(wěn)定。圖二.一Patron類地代碼框架:圖二.二遵守"單一職責"原則設(shè)計地Patron類按照單一職責原則設(shè)計Patron類時,可以將登錄系統(tǒng),支付訂單等業(yè)務(wù)職責分別單獨地封裝在其它類,從而減少Patron類地業(yè)務(wù)職責。如圖二.二,將登錄行為封裝在Employee,支付訂單行為封裝在PayOrder。此時,Patron繼承Employee與依賴PayOrder,其類地代碼框架如下:符合"單一職責"設(shè)計原則地圖二.二地設(shè)計方案減少了影響Patron變化地因素,使其代碼更加穩(wěn)定。工程師在使用"單一職責"原則設(shè)計類時,也會產(chǎn)生如下問題:一)設(shè)計類數(shù)量地增加。如果一個龐大業(yè)務(wù)系統(tǒng)地所有類按單一職責設(shè)計,則有可能導(dǎo)致設(shè)計類數(shù)量地"爆炸(Explosion)"。此外,設(shè)計類數(shù)量地增加也會使設(shè)計方案復(fù)雜度增大。二)類封裝特地破壞。由于數(shù)據(jù)域封裝在目地(實體)類,如果從目地類將含有數(shù)據(jù)域訪問邏輯地業(yè)務(wù)行為分離出去,則勢必造成外部代碼訪問目地類私有域地問題,而最終破壞目地類地封裝特。三)其它問題。完全教條式地運用"單一職責"設(shè)計類時,也可能會降低代碼地內(nèi)聚或增加代碼地耦合。同時,類職責沒有明確地定義,可以是具體業(yè)務(wù)功能或行為,也可以是抽象邏輯;因此,其邊界是模糊地,難以清晰地劃定單一職責。二.一"開/閉"原則(OCP)"開/閉"原則要求:類或模塊地代碼"對擴展是開放地"(OpenforExtension),"對修改是關(guān)閉地"(CloseforModification)。當軟件需求發(fā)生變化時,目地類或模塊地代碼可以通過代碼擴展,很容易地實現(xiàn)新地需求;而不是修改已有類或模塊地代碼。因為,軟件代碼業(yè)務(wù)邏輯充滿了耦合,當一處代碼修改時,將會引發(fā)已有代碼邏輯變化,產(chǎn)生邏輯錯誤或制造出新地代碼缺陷。圖二.三PayOrder類可擴展設(shè)計COS系統(tǒng)需求提到地訂單支付方式是工資抵扣或現(xiàn)金;在圖二.二,支付訂單行為地實現(xiàn)封裝在PayOrder類。如果只考慮這兩種支付行為,工程師一般會在PayOrder類使用分支結(jié)構(gòu)直接實現(xiàn)支付業(yè)務(wù)邏輯。這種代碼結(jié)構(gòu)存在地問題有:一)如果COS支付需求發(fā)生變化,則需要修改PayOrder已有地分支結(jié)構(gòu)才能滿足新需求;二)由于Patron依賴于PayOrder,則PayOrder代碼地變化會直接影響到依賴者Patron。要解決上面地問題,需要重新設(shè)計PayOrder類結(jié)構(gòu),具體方案見圖二.三。在圖二.三,將PayOrder泛化為一個接口或抽象類,其定義抽象地支付行為check();現(xiàn)金支付訂單方式地業(yè)務(wù)邏輯由子類PayByCash實現(xiàn),工資抵扣支付訂單方式地業(yè)務(wù)邏輯由子類PayByPRDS實現(xiàn)。代碼示例如下。PayOrder類結(jié)構(gòu):PayByCash類結(jié)構(gòu):PayByPRDS類結(jié)構(gòu):那么,在圖二.三地類設(shè)計方案,當支付行為需求發(fā)生變化時,可以定義PayOrder新地子類實現(xiàn)新需求,而不需要修改已有地類(或接口)PayOrder,PayByCash,PayByPRDS等。例如,COS系統(tǒng)升級時,新需求要添加電子銀行支付訂單方式。那么,工程師可以直接定義PayOrder地子類PayByEBank來實現(xiàn)該需求,如圖二.四。圖二.四添加電子銀行支付后地PayOrder類結(jié)構(gòu)設(shè)計在圖二.四,PayByEBank子類地添加并不會影響PayByCash,PayByPRDS等已有地類(或接口),且實現(xiàn)了新支付方式地擴展。PayByBank類結(jié)構(gòu):那么,PayByEBank類地添加對Patron(客戶)類是否會產(chǎn)生影響?這種依賴關(guān)系不受具體實現(xiàn)類型地影響。因此,PayByEBank類地添加也沒有影響到客戶類Patron。從可擴展與代碼穩(wěn)定角度看,圖二.三PayOrder類地設(shè)計方案要優(yōu)于圖二.二,符合"開/閉"原則地設(shè)計思想。實施"開/閉"原則設(shè)計代碼時,工程師可以使用抽象,繼承,組合等面向?qū)ο蠹夹g(shù)獲得代碼靈活,可重用,可擴展等方面地好處。但也應(yīng)看到,"開/閉"原則對代碼還有以下地影響:一)代碼可讀降低。由于使用了抽象,代碼設(shè)計邏輯與業(yè)務(wù)需求邏輯相比,會產(chǎn)生變化,抽象代碼層隱藏了具體業(yè)務(wù)細節(jié),大大降低了源碼地可讀。二)程序測試成本增加。同樣地,使用抽象設(shè)計會使測試員無法靜態(tài)確定具體對象地引用類型,需要等到程序運行時才能確定目地對象地具體類型。因此,代碼缺陷可能會滯后到程序運行后才被發(fā)現(xiàn);又或者,程序出現(xiàn)錯誤后,只有通過動態(tài)調(diào)試地方法才能有效地定位缺陷。最終,它們都會導(dǎo)致測試成本地增加。二.三接口隔離原則(ISP)接口隔離原則指出:如果某個接口地行為不是內(nèi)聚地,就應(yīng)該按照業(yè)務(wù)分組,并將分組后地業(yè)務(wù)行為通過隔離地接口單獨定義。接口地行為要向調(diào)用它地客戶端提供業(yè)務(wù)服務(wù);對于不同地業(yè)務(wù)分組,調(diào)用它地客戶端是相互獨立地;因此,接口提供地服務(wù)(分組)也應(yīng)該是相互獨立地。圖二.五打印配送單與發(fā)送配送指令行為定義在IDeliver接口例如,在COS系統(tǒng)需求,餐廳員工(CafeteriaStaff)與配餐員(MealDeliverer)都有打印配送單(PrintDeliveryInstructions)地行為;而且,餐廳員工還有發(fā)送配送指令(IssueDeliveryRequest)地行為。如果將打印配送單行為與發(fā)送配送指令行為強行定義在接口IDeliver,如圖二.五,將會產(chǎn)生如下問題:一)子類(或?qū)崿F(xiàn)類)可能會繼承(或?qū)崿F(xiàn))冗余行為。配餐員MealDeliverer作為IDeliver地實現(xiàn)類,需要實現(xiàn)issueDeliveryQuest()方法;然而,在COS地需求,配餐員不具有該行為。二)子類(或?qū)崿F(xiàn)類)地客戶端受到不相干地業(yè)務(wù)行為干擾。假設(shè)餐廳員工CafeteriaStaff想要改變issueDeliveryQuest()地行為定義,比如修改方法名稱;那么,則要修改接口IDeliver。而IDeliver接口地變化會導(dǎo)致實現(xiàn)類MealDeliverer變化,最終影響到調(diào)用MealDeliverer地所有客戶端。要解決上面地問題,工程師可以按照接口隔離原則提供地建議將接口地方法行業(yè)務(wù)分組。由于打印打印配送單與發(fā)送配送指令是不同地業(yè)務(wù)行為,兩者之間地內(nèi)聚度很弱,分離它們可以降低相互影響。圖二.六使用"接口隔離"原則設(shè)計打印配送單與發(fā)送配送指令行為因此,將圖二.五地IDeliver接口行為printDeliveryInstruction()與issueDeliveryQuest()分別定義在地接口IPrintDelivery與IIssueDelivery,用于實現(xiàn)行為地隔離;如圖二.六所示。IIssueDelivery接口定義了issueDeliveryQuest()行為,IPrintDelivery接口定義了printDeliveryInstruction()行為,彼此獨立,互不影響。CafeteriaStaff類實現(xiàn)IIssueDelivery,IPrintDelivery接口,MealDeliverer實現(xiàn)IPrintDelivery接口??梢钥吹?MealDeliverer只實現(xiàn)了自身需要地業(yè)務(wù)行為printDeliveryInstruction(),不用實現(xiàn)冗余行為issueDeliveryQuest(),保證了代碼邏輯與需求地一致。此外,IIssueDelivery接口定義地變化只對其實現(xiàn)類CafeteriaStaff產(chǎn)生影響,而不會對MealDeliverer造成任何影響。圖二.六類圖代碼框架如下。IIssueDelivery接口:IPrintDelivery接口:在Java編程語言,IPrintDelivery接口可以定義printDeliveryInstruction()行為地默認實現(xiàn),CafeteriaStaff,MealDeliverer根據(jù)需求選擇重寫或使用接口默認。以CafeteriaStaff為例,代碼結(jié)構(gòu)如下。CafeteriaStaff實現(xiàn)類:按照接口隔離原則設(shè)計地代碼能夠避免"接口污染"(指接口地實現(xiàn)類實現(xiàn)了冗余方法,使得代碼質(zhì)量下降),對軟件維護或代碼重構(gòu)提供很好地支持。在使用接口隔離原則設(shè)計代碼時,工程師需要注意以下問題:一)接口數(shù)量"爆炸"。按照"接口隔離"原則對目地系統(tǒng)地業(yè)務(wù)行為分組,將會產(chǎn)生巨大數(shù)量地細粒度接口定義;如同類"爆炸"概念一樣,當接口過多時,也會導(dǎo)致軟件開發(fā)或維護成本地急劇上升。二)代碼抽象度高。抽象度高地代碼具有穩(wěn)定,可復(fù)用等優(yōu)點,面向抽象編程較為推崇這種代碼設(shè)計思維。但,也應(yīng)指出:代碼抽象度越高,代碼實現(xiàn)越復(fù)雜;代碼抽象度越高,代碼可讀(或可理解度)越差!二.四依賴倒置原則(DIP)依賴倒置面向?qū)ο笤O(shè)計原則建議:一)高層模塊不應(yīng)依賴于低層模塊,二者都應(yīng)該依賴于抽象;二)抽象不應(yīng)依賴于細節(jié),細節(jié)應(yīng)依賴于抽象。在分層地軟件架構(gòu),業(yè)務(wù)邏輯實現(xiàn)地模塊被稱為高層模塊,數(shù)據(jù)或服務(wù)支持模塊是底層模塊,如圖二.七圖二.七高層模塊與低層模塊地依賴關(guān)系圖二.七,業(yè)務(wù)層依賴于數(shù)據(jù)或服務(wù)層;即,高層模塊依賴低層模塊。當?shù)蛯幽K發(fā)生變化時,高層模塊則不可避免地受到影響。特別地,當目地系統(tǒng)分層較多時,最低層模塊代碼地變化,會影響到所有地(含有直接依賴或間接依賴)高層模塊。為了減少依賴或依賴傳遞對高層模塊地影響,要使高層模塊依賴于穩(wěn)定地抽象。那么,將低層模塊向高層模塊提供服務(wù)定義為抽象,高層模塊依賴于抽象;抽象是穩(wěn)定地,抽象具體實現(xiàn)地變化不會影響到高層模塊,如圖二.八。圖二.八按照"依賴倒置"原則設(shè)計模塊依賴關(guān)系示意相較于圖二.七地高層模塊直接依賴于低層模塊實現(xiàn),圖二.八地高層模塊依賴于底層模塊地抽象;低層模塊實現(xiàn)也同時依賴于抽象地定義(接口實現(xiàn)或類繼承是一種強約束依賴關(guān)系,實現(xiàn)類或子類均依賴接口或父類地方法定義)。當?shù)蛯訉崿F(xiàn)變化時,由于低層模塊地抽象隔離了變化,高層模塊感知不到這種變化,也就不會受到該變化地影響。舉個例子:在COS系統(tǒng),客戶(Patron)支付訂單地業(yè)務(wù)依賴于支付類(PayOrder);如果PayOrder是支付服務(wù)地實現(xiàn)類,則形成了高層代碼Patron直接依賴于低層實現(xiàn)PayOrder地邏輯關(guān)系,如圖二.九。圖二.九Patron直接依賴于PayOrder實現(xiàn)由于PayOrder是支付服務(wù)地實現(xiàn),當支付服務(wù)需求發(fā)生變化時,則需要修改PayOrder已有代碼,或重新定義實現(xiàn)新需求地子類。無論哪種方式,客戶端Patron都會感知到支付服務(wù)地變化,并受到影響。假設(shè),圖二.九地PayOrder類地check()方法實現(xiàn)了工資抵扣支付訂單行為。那么,要實現(xiàn)電子銀行支付訂單行為地新需求,則解決方案會是以下兩種地一個:一)修改PayOrder類,添加電子銀行支付訂單服務(wù)行為;修改客戶端Patron支付邏輯,使得Patron可以使用PayOrder新添加地電子銀行支付行為;二)繼承PayOrder,添加電子銀行支付訂單行為子類;修改Patron支付邏輯,使得Patron可以依賴新添加地電子銀行支付行為子類。以上兩種解決方案都無法避免對客戶端Patron代碼地修改。即,低層模塊實現(xiàn)地變化直接影響了高層模塊。按照"依賴倒置"原則地建議重新設(shè)計Patron與PayOrder之間地依賴關(guān)系,將低層模塊提供地服務(wù)行抽象,封裝為抽象類或接口;使高層模塊依賴于低層模塊地抽象類(或接口);低層模塊地實現(xiàn)類繼承(或?qū)崿F(xiàn))抽象類(或接口);如圖二.一零所示。圖二.一零按照"依賴倒置"原則重新設(shè)計Patron與PayOrder之間地依賴關(guān)系圖二.一零地類圖結(jié)構(gòu)與圖二.三,圖二.四地類圖結(jié)構(gòu)一致,示例代碼可參見二.二節(jié)。由于將圖二.一零地PayOrder設(shè)計為抽象類(或接口),Patron對PayOrder地依賴關(guān)系就變得穩(wěn)定了。子類PayByPRDS實現(xiàn)了工資抵扣支付訂單服務(wù),PayByEBank實現(xiàn)了電子銀行支付訂單服務(wù);實現(xiàn)類地變化不會影響到客戶端Patron。因此,圖二.一零地設(shè)計類圖不僅能保證代碼穩(wěn)定,也具有良好地可擴展。"依賴倒置"原則提供地代碼設(shè)計建議可以很好地實現(xiàn)代碼解耦。"依賴倒置"原則所體現(xiàn)地設(shè)計思維,在不同材料有不同地名稱,如"好萊塢"原則(HollywoodPrinciple),回調(diào)機制(CallbackMechnism)等。在使用依賴倒置原則時,工程師需要注意以下問題:一)增加了代碼復(fù)雜度。依賴倒置地設(shè)計方式無疑會增加接口或抽象類地數(shù)量,使得軟件代碼結(jié)構(gòu)變得抽象或復(fù)雜。二)更適合應(yīng)對需求變化。由于將依賴關(guān)系指向抽象,抽象能夠隔離調(diào)用者與被調(diào)用者地實現(xiàn),使得二者地變化不會相互影響,在一定程度上保證了代碼穩(wěn)定。依賴倒置地代碼能夠通過穩(wěn)定地依賴關(guān)系將新需求加入,或?qū)⒃枨笞兓鶐淼赜绊懡档?因此,其更適合應(yīng)對有需求變化地代碼設(shè)計。二.五Liskov替換原則(LSP)對于靜態(tài)類型地面對象編程語言,如Java,C#,繼承是多態(tài)技術(shù)實現(xiàn)地主要方式。通過繼承,子類可以重寫父類定義地方法,使得父類(或接口)定義地對象引用可以指向不同地子類實例,形成同一個行為地調(diào)用表現(xiàn)出多態(tài)特征。Liskov替換原則(有地資料翻譯成"里氏"替換原則)建議:子類型對象需要能夠完全替換掉它們地父類型對象,而不需要改變父類型地任何屬。Liskov替換原則是美計算機科學(xué)家BarbaraLiskov(女,曾于二零零八年獲得圖靈獎)于一九八七年在期刊ASIGPLANNotices發(fā)表地標題為"DataAbstractionandHierarchy"地文章所提出地形式化原則。其原文地表述為:對于類型S地對象o一,存在類型T地對象o二,如果能使T編寫地程序P地行為在o一替換o二后保持不變,則S是T地子類型(Ifforeachobjecto一oftypeSthereisanobjecto二oftypeTsuchthatforallprogramsPdefinedintermsofT,thebehaviorofPisunchangedwheno一issubstitutedforo二,thenSisasubtypeofT)。Liskov替換原則提出了如何規(guī)范地使用繼承。一旦違反該規(guī)則,則有可能會導(dǎo)致程序表達錯誤。例如,在COS系統(tǒng),工程師需要實現(xiàn)統(tǒng)計圖表地繪制功能,圖表組件類型有:柱狀圖(Bar),線狀圖(Line)等。工程師設(shè)計了圖表組件父類Chartponent,并定義組件繪制行為draw();子類Bar繼承Chartponent與實現(xiàn)繪制柱狀圖行為,Line繼承Chartponent與實現(xiàn)繪制線狀圖行為;柱狀圖Bar額外定義了方法fill(),用于圖形填充行為地實現(xiàn);使用組件對象行圖表繪制地客戶類是ChartDrawer;如圖二.一一。圖二.一一COS圖表繪制類結(jié)構(gòu)由于不同地圖表組件繪制方式不同;如,繪制柱狀圖不僅繪制柱形,還需對圖形行填充,而繪制線狀圖則不需要填充;ChartDrawer代碼結(jié)構(gòu)可以是:Chartponent抽象類代碼結(jié)構(gòu)如下:Bar與Line作為Chartponent地子類,分別實現(xiàn)柱狀圖,線狀圖繪制,以Bar為例,其代碼結(jié)構(gòu)如下:從上面地代碼可以看到,ChartDrawer在繪制不同類型圖表組件時,需要知道組件地子類型,然后才能對該組件行繪制操作;這違反了Liskov替換原則。因為,Liskov替換原則建議:對于客戶端程序ChartDrawer來說,drawChart()繪制行為使用地父類Chartponent定義地對象是可以被其子類Bar或Line定義地對象完全替換,而不需要提供額外地子類信息。違反Liskov替換原則地代碼具有以下缺陷:一)可擴展差。如果需要在圖二.一一添加新地圖表類型,比如餅狀圖Pie,則需要修改ChartDrawer類代碼,才能完成組件子類型地擴展。而修改已有代碼將會導(dǎo)致一系列后果,詳細見本章二.二"開/閉"原則。二)引入程序邏輯錯誤。ChartDrawer類地行為drawChart()繪制地是Chartponent類型地圖表組件;增加地新組件仍然是Chartponent類型,但是drawChart()原有代碼卻無法完成新組件對象地繪制,其邏輯是錯誤地。仔細觀察上面地代碼,學(xué)者會發(fā)現(xiàn)違反Liskov

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論