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