面向對象編程的思惟方式

我是從學習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();
}

這樣就能夠徹底不用考慮程序代碼實現了,從高層次上把功能抽象出來,定義成爲接口,同時又能夠把系統設計的很合理,徹底根據業務的需求來進行設計。

結語

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

於是一旦緊緊的抓住了這一點,你就會發如今軟件設計和開發過程當中,你本身老是會不知不覺的運用面向對象的思惟方法來設計和編寫程序,而且程序的設計和開發也變得再也不那麼枯燥,而一個合理運用面向對象技術進行設計和架構的軟件,更是具有了思惟的藝術美感。

最後,願面向對象的思惟方法也能給您的程序設計之路帶來創做的樂趣。程序員

相關文章
相關標籤/搜索