改變自己編程中的思維方法(知難行易)

來源:互聯網
上載者:User
我是從學習Java編程開始接觸OOP(物件導向編程),剛開始使用Java編寫程式的時候感覺很彆扭,因為我早以習慣用C來編寫程式,很欣賞C的簡潔性和高效性,喜歡C簡練而表達能力豐富的風格,特別忍受不了Java運行起來慢吞吞的速度,相對冗長的代碼,而且一個很簡單的事情,要寫好多類,一個類調用一個類,心裡的抵觸情緒很強。

我對Java的物件導向的特性琢磨良久,自認為有所領悟,也開始有意識的運用OOP風格來寫程式,然而還是經常會覺得不知道應該怎樣提煉類,面對一個具體的問題的時候,會覺得腦子裡千頭萬緒的,不知道怎麼下手,一不小心,又會回到原來的思路上去。

舉個例子,要發廣告郵件,廣告郵件清單存在資料庫裡面。倘若用C來寫的話,一般會這樣思考,先把郵件內容讀入,然後串連資料庫,迴圈取郵件地址,調用原生qmail的sendmail命令發送。

然後考慮用Java來實現,既然是OOP,就不能什麼代碼都塞到main過程裡面,於是就設計了三個類:

一個類是負責讀取資料庫,取郵件地址,調用qmail的sendmail命令發送;
一個類是讀郵件內容,MIME編碼成HTML格式的,再加上郵件標頭;
一個主類負責從命令讀參數,處理命令列參數,調用發email的類。

把一件工作按照功能劃分為3個模組分別處理,每個類完成一件模組任務。

仔細的分析一下,就會發現這樣的設計完全是從程式員實現程式功能的角度來設計的,或者說,設計類的時候,是自低向上的,從機器的角度到現實世界的角度來分析問題的。因此在設計的時候,就已經把程式編程實現的細節都考慮進去了,企圖從底層實現程式這樣的出發點來達到滿足現實世界的軟體需求的目標。

這樣的分析方法其實是不適用於Java這樣物件導向的程式設計語言,因為,如果改用C語言,封裝兩個C函數,都會比Java實現起來輕鬆的多,邏輯上也清楚的多。

我覺得物件導向的精髓在於考慮問題的思路是從現實世界的人類思維習慣出發的,只要領會了這一點,就領會了物件導向的思維方法。

舉一個非常簡單的例子:假使現在需要寫一個網頁計數器,客戶訪問一次頁面,網頁計數器加1,計數器是這樣來訪問的

http://hostname/count.cgi?id=xxx

後台有一個資料庫表,儲存每個id(一個id對應一個被統計訪問次數的頁面)的計數器當前值,請求頁面一次,對應id的計數器的欄位加1(這裡我們忽略並發更新資料庫表,出現的表鎖定的問題)。

如果按照一般從程式實現的角度來分析,我們會這樣考慮:首先是從HTTP GET請求取到id,然後按照id查資料庫表,獲得某id對應的訪問計數值,然後加1,更新資料庫,最後向頁面顯示訪問計數。

現在假設一個沒有程式設計經驗的人,他會怎樣來思考這個問題的呢?他會提出什麼樣的需求呢?他很可能會這樣想:

我需要有一個計數器,這個計數器應該有這樣的功能,重新整理一次頁面,訪問量就會加1,另外最好還有一個計數器清0的功能,當然計數器如果有一個可以設為任意值的功能的話,我就可以作弊了。

做為一個沒有程式設計經驗的人來說,他完全不會想到對資料庫應該如何操作,對於HTTP變數該如何傳遞,他考慮問題的角度就是我有什麼需求,我的商務邏輯是什麼,軟體應該有什麼功能。

按照這樣的思路(請注意,他的思路其實就是我們平時在生活中習慣的思維方式),我們知道需要有一個計數器類 Counter,有一個必須的和兩個可選的方法:

getCount() // 取計數器值方法
resetCounter() // 計數器清0方法
setCount() // 設計數器為相應的值方法

把Counter類完整的定義如下:

public class Counter {
public int getCount(int id) {}
public void resetCounter(int id) {}
public void setCount(int id, int currentCount) {}
}

解決問題的架構已經有了,來看一下如何使用Counter。 在count.cgi裡面調用Counter來計數,程式片斷如下:

// 這裡從HTTP環境裡面取id值
...
Counter myCounter = new Counter(); // 獲得計數器
int currentCount = myCounter.getCount(id); // 從計數器中取計數
// 這裡向客戶瀏覽器輸出
...

程式的架構全都寫好了,剩下的就是實現Counter類方法裡面具體的代碼了,此時才去考慮具體的程式語言實現的細節,比如,在getCount()方法裡面訪問資料庫,更新計數值。

從上面的例子中看到,物件導向的思維方法其實就是我們在現實生活中習慣的思維方式,是從人類考慮問題的角度出發,把人類解決問題的思維方式逐步翻譯成程式能夠理解的思維方式的過程,在這個翻譯的過程中,軟體也就逐步被設計好了。

在運用物件導向的思維方法進行軟體設計的過程中,最容易犯的錯誤就是開始分析的時候,就想到了程式碼實現的細節,因此封裝的類完全是基於程式實現邏輯,而不是基於解決問題的商務邏輯。

學習JDBC編程的經典錯誤問法是:“我怎樣封裝對資料庫的select操作?”

物件導向的設計是基於解決業務問題的設計,而不是基於具體編程技術的設計。我不會去封裝select語句的,我只封裝解決問題的商務邏輯,對資料庫的讀取是在商務邏輯的編碼實現階段才去考慮的問題。

回過頭看上面那個發廣告郵件的例子,應該如何應用物件導向的思維方法呢?

對於一個郵件來說,有郵件標頭,郵件體,和郵件地址這三個屬性,發送郵件,需要一個發送的方法,另外還需要一個能把所有郵件地址列出來的方法。所以應該如下設計:

類JunkMail

屬性:
head
body
address
方法:
sendMail() // 發送郵件
listAllMail() // 列郵件地址

用Java來表示:

public class JunkMail {
private String head;
private String body;
private String address;
public JunkMain() { // 預設的類構造器
// 從外部設定檔讀郵件標頭和郵件體
this.head=...;
this.body=...;
}

public static boolean sendMail(String address) {
// 調用qmail,發送email
}

public static Collection listAllMail() {
// 訪問資料庫,返回一個郵件地址集合
}
}

當把JunkMail設計好了以後,再調用JunkMail類完成郵件的發送,將是非常輕鬆的事情。

如果說傳統的面向過程的編程是符合機器運行指令的流程的話,那麼物件導向的思維方法就是符合現實生活中人類解決問題的思維過程。

在物件導向的軟體分析和設計的時候,要提醒自己,不要一上來就去想程式碼的實現,應該拋開具體程式設計語言的束縛,集中精力分析我們要實現的軟體的商務邏輯,分析軟體的商務程序,思考應該如何去描述和實現軟體的業務。畢竟軟體只是一個載體,業務才是我們真正要實現的目標。

但是在設計過程中,心裡卻往往在擔心,如果我完全不去考慮程式碼的實現的話,那麼我怎麼知道我的設計一定合理呢?我怎麼知道我設計的類、介面一定可以實現呢?所以經常可以看到的現象就是:

在設計過程中,雖然知道不能過早考慮代碼實現,但是每設計一個類,一個介面,心裡都要不知不覺的用自己熟悉的程式設計語言大概的評估一下,看看能否編出來,因此,一不小心,就會又回到按照程式功能實現的思路進行設計的老路上去了。

舉個例子來說明,在做Web程式設計的時候,經常要遇到分頁顯示資料的情況。比如說需要把系統中所有的使用者都列出來這樣的功能。假設使用User類來表示使用者,增加使用者addUser(),刪除使用者deleteUser(),查詢所有使用者listUsers()方法。而資料庫中有一個user表,一條記錄是一個使用者的資訊。下面考慮一下User類的方法的實現:

addUser()和deleteUser()方法都好實現,就是對資料庫增加記錄和刪除記錄。對於listUsers()方法,其實就是對user表的select,取出一個記錄集。但是該怎麼從listUsers()方法中得到所有使用者的列表呢?

一個方法調用的傳回值只有一個,沒有多個,所以很多情況下採用的辦法就是傳回值定義為集合類型,比如Vector。這樣就可以在listUsers()方法的具體代碼實現的時候,從資料庫依次取出一個個記錄,插入到Vector裡面來。在主程式裡面,調用listUsers()方法可以返回一個Vector,然後再對Vector遍曆操作,就可以得到使用者列表了。

public class User {

public static void addUser(...) {
// 資料庫insert一條記錄
}

public static void deleteUser(...) {
// 資料庫delete一條記錄
}

public Vector listUsers(...) {
// 資料庫select結果放到一個集合裡面
}
}

這樣的設計基本合理,但是仍然有點小問題。因為在設計的時候,就考慮到了用Java的集合類Vector來實現對不定長資料集的存放,因而違反了物件導向設計的一個原則:在設計的時候不應過早的考慮具體程式語言的實現。所以必須用抽象的方法,和具體實現無關的方法來表達商務邏輯。

我們知道,通常對具有集合特徵的資料結構進行遍曆通常可以使用next和hasNext方法,next實現取下一個使用者,hasNext判斷是否還有元素。 因此我們定義一個介面Iterator,這個介面中定義兩個方法next和hasNext:

public interface Iterator {
public boolean hasNext() {}
public Object next() {}
}

而User類的listUses方法傳回值改為Iterator介面的實作類別:

public class User {
...
public Iterator listUsers() {
}
...
}

這樣就把User類的設計和具體的實現方法分離開了,因為此時任何實現了next()和hasNext()方法的類都可以做為listUsers的傳回值,都可以被用來表達“使用者列表”,而不僅僅可以使用Vector而已。比如,我可以用ArrayList來表達使用者列表,因為ArrayList也實現了Iterator,當然我也可以自己專門寫一個類來存放使用者列表,只要實現next()和hasNext()方法就行了。

這樣在具體的編寫代碼的時候,程式員具有了最大的靈活性,可以根據具體的情況,採用不同的編程方法來存放使用者列表。特別是降低了程式的耦合度,提高了程式的可移植性。對於上面那個JunkMail的listAllMail()方法也同樣應該改為介面類型。

然後,在主程式裡面就這樣來使用User類的listUsers方法:

User myUser = new User();
Iterator iterator = myUser.listUsers();
while (iterator.hasNext()) {
iterator.next();
}

這樣就可以完全不用考慮程式碼實現了,從高層次上把功能抽象出來,定義成為介面,同時又可以把系統設計的很合理,完全根據業務的需求來進行設計。

結語

通過上面的幾個例子的設計說明,使用物件導向的思維方法,其實是一個把商務邏輯從具體的編程技術當中抽象出來的過程,而這個抽象的過程是自上而下的,非常符合人類的思維習慣,也就是先不考慮問題解決的細節,把問題的最主要的方面抽象成為一個簡單的架構,集中精力思考如何解決主要矛盾,然後在解決問題的過程中,再把問題的細節分割成一個一個小問題,再專門去解決細節問題。

因而一旦牢牢的抓住了這一點,你就會發現在軟體設計和開發過程中,你自己總是會不知不覺的運用物件導向的思維方法來設計和編寫程式,並且程式的設計和開發也變得不再那麼枯燥,而一個合理運用物件導向技術進行設計和架構的軟體,更是具備了思維的藝術美感。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.