使用設(shè)計(jì)模式改善程序結(jié)構(gòu)(一)
發(fā)表時(shí)間:2024-05-27 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]設(shè)計(jì)模式是對(duì)特定問題經(jīng)過無數(shù)次經(jīng)驗(yàn)總結(jié)后提出的能夠解決它的優(yōu)雅的方案。但是,如果想要真正使設(shè)計(jì)模式發(fā)揮最大作用,僅僅知道設(shè)計(jì)模式是什么,以及它是如何實(shí)現(xiàn)的是很不夠的,因?yàn)槟菢泳筒荒苁鼓銓?duì)于設(shè)計(jì)模式有真正的理解,也就不能夠在自己的設(shè)計(jì)中正確、恰當(dāng)?shù)氖褂迷O(shè)計(jì)模式。本文試圖從另一個(gè)角度(設(shè)計(jì)模式的意圖、...
設(shè)計(jì)模式是對(duì)特定問題經(jīng)過無數(shù)次經(jīng)驗(yàn)總結(jié)后提出的能夠解決它的優(yōu)雅的方案。但是,如果想要真正使設(shè)計(jì)模式發(fā)揮最大作用,僅僅知道設(shè)計(jì)模式是什么,以及它是如何實(shí)現(xiàn)的是很不夠的,因?yàn)槟菢泳筒荒苁鼓銓?duì)于設(shè)計(jì)模式有真正的理解,也就不能夠在自己的設(shè)計(jì)中正確、恰當(dāng)?shù)氖褂迷O(shè)計(jì)模式。本文試圖從另一個(gè)角度(設(shè)計(jì)模式的意圖、動(dòng)機(jī))來看待設(shè)計(jì)模式,通過這種新的思路,設(shè)計(jì)模式會(huì)變得非常貼近你的設(shè)計(jì)過程,并且能夠指導(dǎo)、簡(jiǎn)化你的設(shè)計(jì),最終將會(huì)導(dǎo)出一個(gè)優(yōu)秀的解決方案。
1、介紹
在進(jìn)行項(xiàng)目的開發(fā)活動(dòng)中,有一些設(shè)計(jì)在項(xiàng)目剛剛開始工作的很好,但是隨著項(xiàng)目的進(jìn)展,發(fā)現(xiàn)需要對(duì)已有的代碼進(jìn)行修改或者擴(kuò)展,導(dǎo)致這樣做的原因主要有:新的功能需求的需要以及對(duì)系統(tǒng)進(jìn)一步理解。在這個(gè)時(shí)候,我們往往會(huì)發(fā)現(xiàn)進(jìn)行這項(xiàng)工作比較困難,即使能完成也要付出很大的代價(jià)。此時(shí),一個(gè)必須要做的工作就是要對(duì)現(xiàn)有的代碼進(jìn)行重構(gòu)(refactoring),通過重構(gòu)使得我們接下來的工作變得相對(duì)容易。
重構(gòu)就是在不改變軟件系統(tǒng)代碼的外部行為的前提下,改善它的內(nèi)部結(jié)構(gòu)。重構(gòu)的目標(biāo)就是使代碼結(jié)構(gòu)更加合理,富有彈性,能夠適應(yīng)新的需求、新的變化。對(duì)于特定問題給出優(yōu)美解決方案的設(shè)計(jì)模式往往會(huì)成為重構(gòu)的目標(biāo),而且一旦我們能夠識(shí)別出能夠解決我們問題的設(shè)計(jì)模式,將會(huì)大大簡(jiǎn)化我們的工作,因?yàn)槲覀兛梢灾赜脛e人已經(jīng)做過的工作。但是在我們的原始設(shè)計(jì)和最終可能會(huì)適用于我們的設(shè)計(jì)模式間的過渡并不是平滑的,而是有一個(gè)間隙。這樣的結(jié)果就是:即使我們已經(jīng)知道了很多的設(shè)計(jì)模式,面對(duì)我們的實(shí)際問題,我們也沒有一個(gè)有效的方法去判斷哪一個(gè)設(shè)計(jì)模式適用于我們的系統(tǒng),我們應(yīng)該去怎樣應(yīng)用它。
造成上述問題的原因往往是由于過于注重設(shè)計(jì)模式所給出的解決方案這個(gè)結(jié)果,而對(duì)于設(shè)計(jì)模式的意圖,以及它產(chǎn)生的動(dòng)機(jī)卻忽略了。然而,正是設(shè)計(jì)模式的意圖、動(dòng)機(jī)促使人們給出了一個(gè)解決一類問題的方案這個(gè)結(jié)果,設(shè)計(jì)模式的動(dòng)機(jī)、意圖體現(xiàn)了該模式的形成思路,所以更加貼近我們的實(shí)際問題,從而會(huì)有效的指導(dǎo)我們的重構(gòu)歷程。本文將通過一個(gè)實(shí)例來展示這個(gè)過程。
在本文中對(duì)例子進(jìn)行了簡(jiǎn)化,這樣做是為了突出問題的實(shí)質(zhì)并且會(huì)使我們的思路更加清晰。思路本身才是最重要、最根本的,簡(jiǎn)化了的例子不會(huì)降低我們所展示的思路、方法的適用性。
2、問題描述
一個(gè)完善的軟件系統(tǒng),必須要對(duì)出現(xiàn)的錯(cuò)誤進(jìn)行相應(yīng)的處理,只有這樣才能使系統(tǒng)足夠的健壯,我準(zhǔn)備以軟件系統(tǒng)中對(duì)于錯(cuò)誤的處理為例,來展示我所使用的思路、方法。
在一個(gè)分布式的網(wǎng)管系統(tǒng)中,一個(gè)操作往往不會(huì)一定成功,常常會(huì)因?yàn)檫@樣或者那樣的原因失敗,此時(shí)我們就要根據(jù)失敗的原因相應(yīng)的處理,使錯(cuò)誤的影響局限在最小的范圍內(nèi),最好能夠恢復(fù)而不影響系統(tǒng)的正常運(yùn)行,還有一點(diǎn)很重要,那就是在對(duì)錯(cuò)誤進(jìn)行處理的同時(shí),一定不要忘記通知系統(tǒng)的管理者,因?yàn)橹挥泄芾碚卟庞心芰?duì)錯(cuò)誤進(jìn)行進(jìn)一步的分析,從而查找出錯(cuò)誤的根源,從根本上解決錯(cuò)誤。
下面我就從錯(cuò)誤處理的通告管理者部分入手,開始我們的旅程。假定一個(gè)在一個(gè)分布式環(huán)境中訪問數(shù)據(jù)庫的操作,那么就有可能因?yàn)橥ㄐ诺脑蚧蛘邤?shù)據(jù)庫本身的原因失敗,此時(shí)我們要通過用戶界面來通知管理者發(fā)生的錯(cuò)誤。簡(jiǎn)化了的代碼示例如下:
/* 錯(cuò)誤碼定義 */
class ErrorConstant
{
public static final int ERROR_DBACCESS = 100;
public static final int ERROR_COMMUNICATION = 101;
}
/* 省略了用戶界面中的其他的功能 */
class GUISys
{
public void announceError(int errCode) {
switch(errCode) {
case ErrorConstant.ERROR_DBACCESS:
/* 通告管理者數(shù)據(jù)庫訪問錯(cuò)誤的發(fā)生*/
break;
case ErrorConstant.ERROR_COMMUNICATION:
/* 通告管理者通信錯(cuò)誤的發(fā)生*/
break;
}
}
}
開始,這段代碼工作的很好,能夠完成我們需要的功能。但是這段代碼缺少相應(yīng)的彈性,很難適應(yīng)需求的變化。
3、問題分析
熟悉面向?qū)ο蟮淖x者很快就會(huì)發(fā)現(xiàn)上面的代碼是典型的結(jié)構(gòu)化的方法,結(jié)構(gòu)化的方法是以具體的功能為核心來組織程序的結(jié)構(gòu),它的封裝度僅為1級(jí),即僅有對(duì)于特定的功能的封裝(函數(shù))。這使得結(jié)構(gòu)化的方法很難適應(yīng)需求的變化,面向?qū)ο蟮姆椒ㄕ窃谶@一點(diǎn)上優(yōu)于結(jié)構(gòu)化的方法。在面向?qū)ο箢I(lǐng)域,是以對(duì)象來組成程序結(jié)構(gòu)的,一個(gè)對(duì)象有自己的職責(zé),通過對(duì)象間的交互來完成系統(tǒng)的功能,這使得它的封裝度至少為2級(jí),即封裝了為完成自己職責(zé)的方法和數(shù)據(jù)。另外面向?qū)ο蟮姆椒ㄟ支持更高層次的封裝,比如:通過對(duì)于不同的具體對(duì)象的共同的概念行為進(jìn)行描述,我們可以達(dá)到3級(jí)的封裝度- 抽象的類(在Java中就是接口)。封裝的層次越高,抽象的層次就越高,使得設(shè)計(jì)、代碼有越高的彈性,越容易適應(yīng)變化。
考慮對(duì)上一節(jié)中的代碼,如果在系統(tǒng)的開發(fā)過程中發(fā)現(xiàn)需要對(duì)一種新的錯(cuò)誤進(jìn)行處理,比如:用戶認(rèn)證錯(cuò)誤,我們?cè)撊绾巫鍪沟梦覀兊南到y(tǒng)能夠增加對(duì)于此項(xiàng)功能的需求呢?一種比較簡(jiǎn)單、直接的做法就是在增加一條用來處理此項(xiàng)錯(cuò)誤的case語句。是的,這種方法的確能夠工作,但是這樣做是要付出代價(jià)的。
首先,隨著系統(tǒng)的進(jìn)一步開發(fā),可能會(huì)出現(xiàn)更多的錯(cuò)誤類型,那么就會(huì)導(dǎo)致對(duì)于錯(cuò)誤的處理部分代碼冗長(zhǎng),不利于維護(hù)。其次,也是最根本的一點(diǎn),修改已經(jīng)能夠工作的代碼,很容易引入錯(cuò)誤,并且在很多的情況下,錯(cuò)誤都是在不經(jīng)意下引入的,對(duì)于這種類型的錯(cuò)誤很難定位。有調(diào)查表明,我們?cè)陂_發(fā)過程中,用于修正錯(cuò)誤的時(shí)間并不多,大部分的時(shí)間是在調(diào)試、發(fā)現(xiàn)錯(cuò)誤。在面向?qū)ο箢I(lǐng)域,有一個(gè)很著名的原則:OCP(Open-Closed Principle),它的核心含意是:一個(gè)好的設(shè)計(jì)應(yīng)該能夠容納新的功能需求的增加,但是增加的方式不是通過修改又有的模塊(類),而是通過增加新的模塊(類)來完成的。如果一個(gè)設(shè)計(jì)能夠遵循OCP,那么就能夠有效的避免上述的問題。
要是一個(gè)設(shè)計(jì)能夠符合OCP原則,就要求我們?cè)谶M(jìn)行設(shè)計(jì)時(shí)不能簡(jiǎn)單的以功能為核心。要實(shí)現(xiàn)OCP的關(guān)鍵是抽象,抽象表征了一個(gè)固定的行為,但是對(duì)于這個(gè)行為可以有很多個(gè)不同的具體實(shí)現(xiàn)方法。通過抽象,我們就可以用一個(gè)固定的抽象的概念來代替哪些容易變化的數(shù)量眾多的具體的概念,并且使得原來依賴于哪些容易變化的概念的模塊,依賴于這個(gè)固定的抽象的概念,這樣的結(jié)果就是:系統(tǒng)新的需求的增加,僅僅會(huì)引起具體的概念的增加,而不會(huì)影響依賴于具體概念的抽象體的其他模塊。在實(shí)現(xiàn)的層面上,抽象體是通過抽象類來描述的,在Java中是接口(interface)。
既然知道了問題的本質(zhì)以及相應(yīng)的解決方法,下面就來改善我們的代碼結(jié)構(gòu)。
4、初步方案
讓我們重新審視代碼,看看該如何進(jìn)行抽象。在錯(cuò)誤處理中,需要處理不同類型的錯(cuò)誤,每個(gè)具體的錯(cuò)誤具有特定于自己本身的一些信息,但是它們?cè)诟拍顚用嫔嫌质且恢碌,比如:都可以通過特定的方法接口獲取自已內(nèi)部的錯(cuò)誤信息,每一個(gè)錯(cuò)誤都有自己的處理方法。由此可以得到一個(gè)初步的方案:可以定義一個(gè)抽象的錯(cuò)誤基類,在這個(gè)基類里面定義一些在概念上適用于所有不同的具體錯(cuò)誤的方法,每個(gè)具體的錯(cuò)誤可以有自己的不同的對(duì)于這些方法的實(shí)現(xiàn)。代碼示例如下:
interface ErrorBase
{
public void handle()
public String getInfo();
}
class DBAccessError implements ErrorBase
{
public void handle() {
/* 進(jìn)行關(guān)于數(shù)據(jù)庫訪問錯(cuò)誤的處理 */
}
public String getInfo() {
/* 構(gòu)造返回關(guān)于數(shù)據(jù)庫訪問錯(cuò)誤的信息 */
}
}
class CommunicationError implements ErrorBase
{
public void handle() {
/* 進(jìn)行關(guān)于通信錯(cuò)誤的處理 */
}
public String getInfo() {
/* 構(gòu)造返回關(guān)于通信錯(cuò)誤的信息 */
}
}