我是從學習Java編程開始接觸OOP(面向?qū)ο缶幊?,剛開始使用Java編寫程序的時候感覺很別扭,,因為我早以習慣用C來編寫程序,,很欣賞C的簡潔性和高效性,喜歡C簡練而表達能力豐富的風格,,特別忍受不了Java運行起來慢吞吞的速度,,相對冗長的代碼,而且一個很簡單的事情,,要寫好多類,,一個類調(diào)用一個類,心里的抵觸情緒很強,。
我對Java的面向?qū)ο蟮奶匦宰聊チ季?,自認為有所領(lǐng)悟,也開始有意識的運用OOP風格來寫程序,,然而還是經(jīng)常會覺得不知道應該怎樣提煉類,,面對一個具體的問題的時候,會覺得腦子里千頭萬緒的,,不知道怎么下手,,一不小心,又會回到原來的思路上去,。 舉個例子,,要發(fā)廣告郵件,廣告郵件列表存在數(shù)據(jù)庫里面,。倘若用C來寫的話,,一般會這樣思考,先把郵件內(nèi)容讀入,,然后連接數(shù)據(jù)庫,,循環(huán)取郵件地址,調(diào)用本機的qmail的sendmail命令發(fā)送,。 然后考慮用Java來實現(xiàn),,既然是OOP,就不能什么代碼都塞到main過程里面,,于是就設計了三個類:
把一件工作按照功能劃分為3個模塊分別處理,,每個類完成一件模塊任務。 仔細的分析一下,,就會發(fā)現(xiàn)這樣的設計完全是從程序員實現(xiàn)程序功能的角度來設計的,,或者說,設計類的時候,,是自低向上的,,從機器的角度到現(xiàn)實世界的角度來分析問題的。因此在設計的時候,,就已經(jīng)把程序編程實現(xiàn)的細節(jié)都考慮進去了,,企圖從底層實現(xiàn)程序這樣的出發(fā)點來達到滿足現(xiàn)實世界的軟件需求的目標。 這樣的分析方法其實是不適用于Java這樣面向?qū)ο蟮木幊陶Z言,,因為,,如果改用C語言,封裝兩個C函數(shù),,都會比Java實現(xiàn)起來輕松的多,,邏輯上也清楚的多。 我覺得面向?qū)ο蟮木柙谟诳紤]問題的思路是從現(xiàn)實世界的人類思維習慣出發(fā)的,,只要領(lǐng)會了這一點,,就領(lǐng)會了面向?qū)ο蟮乃季S方法。 舉一個非常簡單的例子:假使現(xiàn)在需要寫一個網(wǎng)頁計數(shù)器,,客戶訪問一次頁面,,網(wǎng)頁計數(shù)器加1,計數(shù)器是這樣來訪問的 如:http://hostname/count.cgi?id=xxx 后臺有一個數(shù)據(jù)庫表,,保存每個id(一個id對應一個被統(tǒng)計訪問次數(shù)的頁面)的計數(shù)器當前值,,請求頁面一次,對應id的計數(shù)器的字段加1(這里我們忽略并發(fā)更新數(shù)據(jù)庫表,,出現(xiàn)的表鎖定的問題),。 如果按照一般從程序?qū)崿F(xiàn)的角度來分析,我們會這樣考慮:首先是從HTTP GET請求取到id,,然后按照id查數(shù)據(jù)庫表,,獲得某id對應的訪問計數(shù)值,然后加1,,更新數(shù)據(jù)庫,,最后向頁面顯示訪問計數(shù)。 現(xiàn)在假設一個沒有程序設計經(jīng)驗的人,,他會怎樣來思考這個問題的呢,?他會提出什么樣的需求呢,?他很可能會這樣想:我需要有一個計數(shù)器,這個計數(shù)器應該有這樣的功能,,刷新一次頁面,,訪問量就會加1,另外最好還有一個計數(shù)器清0的功能,,當然計數(shù)器如果有一個可以設為任意值的功能的話,,我就可以作弊了。 做為一個沒有程序設計經(jīng)驗的人來說,,他完全不會想到對數(shù)據(jù)庫應該如何操作,,對于HTTP變量該如何傳遞,他考慮問題的角度就是我有什么需求,,我的業(yè)務邏輯是什么,,軟件應該有什么功能。 按照這樣的思路(請注意,,他的思路其實就是我們平時在生活中習慣的思維方式),,我們知道需要有一個計數(shù)器類 Counter,有一個必須的和兩個可選的方法: getCount() // 取計數(shù)器值方法 把Counter類完整的定義如下: public class Counter { 解決問題的框架已經(jīng)有了,,來看一下如何使用Counter,。 在count.cgi里面調(diào)用Counter來計數(shù),程序片斷如下: // 這里從HTTP環(huán)境里面取id值 程序的框架全都寫好了,,剩下的就是實現(xiàn)Counter類方法里面具體的代碼了,,此時才去考慮具體的程序語言實現(xiàn)的細節(jié),比如,,在getCount()方法里面訪問數(shù)據(jù)庫,,更新計數(shù)值。 從上面的例子中看到,,面向?qū)ο蟮乃季S方法其實就是我們在現(xiàn)實生活中習慣的思維方式,,是從人類考慮問題的角度出發(fā),把人類解決問題的思維方式逐步翻譯成程序能夠理解的思維方式的過程,,在這個翻譯的過程中,,軟件也就逐步被設計好了。 在運用面向?qū)ο蟮乃季S方法進行軟件設計的過程中,,最容易犯的錯誤就是開始分析的時候,,就想到了程序代碼實現(xiàn)的細節(jié),因此封裝的類完全是基于程序?qū)崿F(xiàn)邏輯,,而不是基于解決問題的業(yè)務邏輯,。 學習JDBC編程的經(jīng)典錯誤問法是:“我怎樣封裝對數(shù)據(jù)庫的select操作?” 面向?qū)ο蟮脑O計是基于解決業(yè)務問題的設計,,而不是基于具體編程技術(shù)的設計,。我不會去封裝select語句的,我只封裝解決問題的業(yè)務邏輯,,對數(shù)據(jù)庫的讀取是在業(yè)務邏輯的編碼實現(xiàn)階段才去考慮的問題,。 回過頭看上面那個發(fā)廣告郵件的例子,應該如何應用面向?qū)ο蟮乃季S方法呢,? 對于一個郵件來說,,有郵件頭,郵件體,,和郵件地址這三個屬性,,發(fā)送郵件,需要一個發(fā)送的方法,,另外還需要一個能把所有郵件地址列出來的方法,。所以應該如下設計: 類JunkMail 屬性: 用Java來表示: public class JunkMail { public static boolean sendMail(String address) { public static Collection listAllMail() { 當把JunkMail設計好了以后,,再調(diào)用JunkMail類完成郵件的發(fā)送,將是非常輕松的事情,。 如果說傳統(tǒng)的面向過程的編程是符合機器運行指令的流程的話,,那么面向?qū)ο蟮乃季S方法就是符合現(xiàn)實生活中人類解決問題的思維過程。 在面向?qū)ο蟮能浖治龊驮O計的時候,,要提醒自己,,不要一上來就去想程序代碼的實現(xiàn),應該拋開具體編程語言的束縛,,集中精力分析我們要實現(xiàn)的軟件的業(yè)務邏輯,,分析軟件的業(yè)務流程,思考應該如何去描述和實現(xiàn)軟件的業(yè)務,。畢竟軟件只是一個載體,,業(yè)務才是我們真正要實現(xiàn)的目標。 但是在設計過程中,,心里卻往往在擔心,,如果我完全不去考慮程序代碼的實現(xiàn)的話,那么我怎么知道我的設計一定合理呢,?我怎么知道我設計的類,、接口一定可以實現(xiàn)呢?所以經(jīng)??梢钥吹降默F(xiàn)象就是: 在設計過程中,,雖然知道不能過早考慮代碼實現(xiàn),但是每設計一個類,,一個接口,,心里都要不知不覺的用自己熟悉的編程語言大概的評估一下,,看看能否編出來,因此,,一不小心,,就會又回到按照程序功能實現(xiàn)的思路進行設計的老路上去了。 舉個例子來說明,,在做Web程序設計的時候,,經(jīng)常要遇到分頁顯示數(shù)據(jù)的情況。比如說需要把系統(tǒng)中所有的用戶都列出來這樣的功能,。假設使用User類來表示用戶,,增加用戶addUser(),刪除用戶deleteUser(),,查詢所有用戶listUsers()方法,。而數(shù)據(jù)庫中有一個user表,一條記錄是 一個用戶的信息,。下面考慮一下User類的方法的實現(xiàn): addUser()和deleteUser()方法都好實現(xiàn),,就是對數(shù)據(jù)庫增加記錄和刪除記錄。對于listUsers()方法,,其實就是對user表的select,,取出一個記錄集。但是該怎么從listUsers()方法中得到所有用戶的列表呢,? 一個方法調(diào)用的返回值只有一個,,沒有多個,所以很多情況下采用的辦法就是返回值定義為集合類型,,比如Vector,。這樣就可以在listUsers()方法的具體代碼實現(xiàn)的時候,從數(shù)據(jù)庫依次取出一個個記錄,,插入到Vector里面來,。在主程序里面,調(diào)用listUsers()方法可以返回一個Vector,,然后再對Vector遍歷操作,,就可以得到用戶列表了。 public class User { public static void addUser(...) { public static void deleteUser(...) { public Vector listUsers(...) { 這樣的設計基本合理,,但是仍然有點小問題,。因為在設計的時候,就考慮到了用Java的集合類Vector來實現(xiàn)對不定長數(shù)據(jù)集的存放,,因而違反了面向?qū)ο笤O計的一個原則:在設計的時候不應過早的考慮具體程序語言的實現(xiàn),。所以必須用抽象的方法,和具體實現(xiàn)無關(guān)的方法來表達業(yè)務邏輯。 我們知道,,通常對具有集合特征的數(shù)據(jù)結(jié)構(gòu)進行遍歷通??梢允褂胣ext和hasNext方法,next實現(xiàn)取下一個用戶,,hasNext判斷是否還有元素,。 因此我們定義一個接口Iterator,這個接口中定義兩個方法next和hasNext: public interface Iterator { 而User類的listUses方法返回值改為Iterator接口的實現(xiàn)類: public class User { 這樣就把User類的設計和具體的實現(xiàn)方法分離開了,,因為此時任何實現(xiàn)了next()和hasNext()方法的類都可以做為listUsers的返回值,,都可以被用來表達“用戶列表”,,而不僅僅可以使用Vector而已,。比如,我可以用ArrayList來表達用戶列表,,因為ArrayList也實現(xiàn)了Iterator,,當然我也可以自己專門寫一個類來存放用戶列表,只要實現(xiàn)next()和hasNext()方法就行了,。 這樣在具體的編寫代碼的時候,,程序員具有了最大的靈活性,可以根據(jù)具體的情況,,采用不同的編程方法來存放用戶列表,。特別是降低了程序的耦合度,提高了程序的可移植性,。對于上面那個JunkMail的listAllMail()方法也同樣應該改為接口類型。 然后,在主程序里面就這樣來使用User類的listUsers方法: User myUser = new User(); 這樣就可以完全不用考慮程序代碼實現(xiàn)了,,從高層次上把功能抽象出來,,定義成為接口,同時又可以把系統(tǒng)設計的很合理,,完全根據(jù)業(yè)務的需求來進行設計,。 結(jié)語 通過上面的幾個例子的設計說明,使用面向?qū)ο蟮乃季S方法,,其實是一個把業(yè)務邏輯從具體的編程技術(shù)當中抽象出來的過程,,而這個抽象的過程是自上而下的,非常符合人類的思維習慣,,也就是先不考慮問題解決的細節(jié),,把問題的最主要的方面抽象成為一個簡單的框架,集中精力思考如何解決主要矛盾,,然后在解決問題的過程中,,再把問題的細節(jié)分割成一個一個小問題,再專門去解決細節(jié)問題,。 因而一旦牢牢的抓住了這一點,,你就會發(fā)現(xiàn)在軟件設計和開發(fā)過程中,,你自己總是會不知不覺的運用面向?qū)ο蟮乃季S方法來設計和編寫程序,并且程序的設計和開發(fā)也變得不再那么枯燥,,而一個合理運用面向?qū)ο蠹夹g(shù)進行設計和架構(gòu)的軟件,,更是具備了思維的藝術(shù)美感。 |
|