.NET 高級架構師0002 架構師之路(1)---面向過程和麪向對象

一、引言
     機算機科學是一門應用科學,它的知識體系是典型的倒三角結構,所用的基礎知識並很少,只是隨着應用領域和方向的不一樣,產生了不少的分支,因此說編程並非一件很困難的事情,一個高中生通過特定的訓練就能夠作獲得。可是,會編程和編好程絕對是兩碼事,一樣的程序員,有的人幾年以後成爲了架構師,有的人卻還在不停地coding,只不過ctrl-c、ctrl-v用得更加純熟了。在中國,編程人員最終的歸途無外乎兩條:一是轉向技術管理,它的終點是CTO;二是繼續深刻,它的終點是首席架構師,成爲CEO的人畢竟是少數。若是你如今仍是個普通的程序員,但願繼續在技術這條路上前進的話,我想你仍是應該先補充一點軟件工程的思想,學習一點有關設計模式的知識,只有具有這些能力,你才能從總體和宏觀層面來考慮問題、分析問題和解決問題。本人Coding了不少年,中間走了很多彎路,雖然最終沒什麼大成就,但總算有一些心得,很願意把本身的一些經驗拿出來跟你們分享,這或許對你的發展有所幫助。
由程序員轉爲架構師,最繞不開的概念就算是面向對象(OO)了。記得在大學的時候,咱們專業開了一門課叫《面向對象的編程》。那個時候,咱們剛剛學了一門C語言,開發環境用的仍是DOS下的Turbo C,半點項目開發的經驗都沒有,純粹的空對空。因此,一學期下來,我始終處於一種懵懂狀態,既沒領會面向過程和麪向對象到底有什麼區別,也沒搞懂面向對象能帶來什麼好處。

二、面向過程(OP)和麪向對象(OO)
 

2.1 蛋炒飯和蓋澆飯
      有人這麼形容OP和OO的不一樣:用面向過程的方法寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什麼菜,你就澆上什麼菜。我以爲這個比喻仍是比較貼切的。
蛋炒飯製做的細節,我不太清楚,由於我沒當過廚師,也不會作飯,但最後的一道工序確定是把米飯和雞蛋混在一塊兒炒勻。蓋澆飯呢,則是把米飯和蓋菜分別作好,你若是要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;若是要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。

      蛋炒飯的好處就是入味均勻,吃起來香。若是恰巧你不愛吃雞蛋,只愛吃青菜的話,那麼惟一的辦法就是所有倒掉,從新作一份青菜炒飯了。蓋澆飯就沒這麼多麻煩,你只須要把上面的蓋菜撥掉,更換一份蓋菜就能夠了。蓋澆飯的缺點是入味不均,可能沒有蛋炒飯那麼香。
究竟是蛋炒飯好仍是蓋澆飯好呢?其實這類問題都很難回答,非要比個上下高低的話,就必須設定一個場景,不然只能說是各有所長。若是你們都不是美食家,沒那麼多講究,那麼從飯館角度來說的話,作蓋澆飯顯然比蛋炒飯更有優點,他能夠組合出來任意多的組合,並且不會浪費。
 
2.2 軟件工程
      蓋澆飯的好處就是「菜」「飯」分離,從而提升了製做蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業術語就是「可維護性」比較好,「飯」和「菜」的耦合度比較低。蛋炒飯將「蛋」「飯」攪和在一塊兒,想換「蛋」「飯」中任何一種都很困難,耦合度很高,以致於「可維護性」比較差。軟件工程追求的目標之一就是可維護性,可維護性主要表如今3個方面:可理解性、可測試性和可修改性。面向對象的主要好處就是顯著的改善了軟件的可維護性。

      面向過程(OP)和麪向對象(OO)是否是就是指編碼的兩種方式呢?不是!你拿到了一個用戶需求,好比有人要找你編個軟件,你是否是須要通過需求分析,而後進行整體/詳細設計,最後編碼,才能最終寫出軟件,交付給用戶。這個過程是符合人類基本行爲方式的:先想作什麼,再想如何去作,最後纔是作事情。有的同窗說:「我沒按照你說的步驟作啊,我是直接編碼的」。其實,你必定會經歷了這三個階段,只不過你潛意識裏沒有分得那麼清楚。對於拿到需求就編碼的人,可能編着編着,又得倒回去從新琢磨,仍是免不了這些過程,

      以OO爲例,對應於軟件開發的過程,OO衍生出3個概念:OOA、OOD和OOP。採用面向對象進行分析的方式稱爲OOA,採用面向對象進行設計的方式稱爲OOD,採用面向對象進行編碼的方式稱爲OOP。面向過程(OP)和麪向對象(OO)本質的區別在於分析方式的不一樣,最終致使了編碼方式的不一樣。
 
2.3 面向過程(OP)和麪向對象(OO)
      關於面向過程的編程(OPP)和麪向對象的編程(OOP),給出這它們的定義的人不少,您能夠從任何資料中找到很專業的解釋,但以個人經驗來看,講的相對枯燥一點,不是很直觀。除非您已經有了至關的積累,不然提及來仍是比較費勁。
      我是個老程序員出身,雖然如今的平常工做更多傾向了管理,但至今依然保持編碼的習慣,這句話什麼意思呢?我跟你們溝通應該沒有問題。不管你是在重複我走過的路,或者已經走在了個人前面,你們都會有那麼一段相同的經歷,都會在思想層面上有一種理解和默契,因此我仍是會盡可能按照大多數人的常規思惟寫下去。
      面向過程的編程(OPP)產生在前,面向對象的編程(OOP)產生在後,因此面向對象的編程(OOP)必定會繼承前者的一些優勢,並摒棄前者存在的一些缺點,這是符合人類進步的天然規律。二者在各自的發展和演變過程當中,也必定會相互借鑑,互相融合,來吸取對方優勢,從而出如今某些方面的趨同性,這些是必然的結果。即便二者有更多的類似點,也不會改變它們本質上的不一樣,由於它們的出發點就徹底是兩種大相徑庭的思惟方式。關於二者的關係,個人觀點是這樣的:面向對象編程(OOP)在局部上必定是面向過程(OP)的,面向過程的編程(OPP)在總體上應該借鑑面向對象(OO)的思想。這一段說的的確很空洞,並且也必定會有引來爭議,不過,我勸您仍是在閱讀了後面的內容以後,再來評判我觀點的正確與否。
      象C++、C#、Java等都是面向對象的語言,c,php(暫且這麼說,由於php4之後就支持OO)都是面向過程的語言,那麼是否是我用C++寫的程序必定就是面向對象,用c寫的程序必定就是面向過程呢?這種觀點顯然是沒有真正吃透二者的區別。語言永遠是一種工具,前輩們每創造出來的一種語言,都是你用來實現想法的利器。我以爲好多人用C#,Java寫出來的代碼,要是仔細看看,那實際就是用面向對象(OO)的語言寫的面向過程(OP)的程序。
      因此,即便給關羽一根木棍,給你一杆青龍偃月刀,他照樣能夠打得你滿頭是包。你就是扛着個偃月刀,也成不了關羽,由於你缺少關羽最本質的東西---絕世武功。一樣的道理,若是你沒有領會OO思想,怎麼可能寫得出真正的OO程序呢?面向對象(OO)和麪向過程(OP)絕對是兩種大相徑庭的思惟方式。
      那是否是面向過程就很差,也沒有存在的必要了?我歷來沒有這樣說過。事實上,面向過程的編程(OPP)已經存在了幾十年了,如今依然有不少人在使用。它的優勢就是邏輯不復雜的狀況下很容易理解,並且運行效率遠高於面向對象(OO)編寫的程序。因此,系統級的應用或準實時系統中,依然採用面向過程的編程(OPP)。固然,不少編程高手以及大師級的人物,他們因爲對於系統總體的掌控能力很強,也喜歡使用面向過程的編程(OPP),好比像Apache,QMail,PostFix,ICE等等這些比較經典的系統都是OPP的產物。
      象php這些腳本語言,主要用於web開發,對於一些業務邏輯相對簡單的系統,也常使用面向過程的編程(OPP),這也是php沒法跨入到企業級應用開發的緣由之一,不過php5目前已經可以很好的支持OO了。
 
2.4 詳解面向過程的編程(OPP)
 
      在面向對象出現以前,咱們採用的開發方法都是面向過程的編程(OPP)。面向過程的編程中最經常使用的一個分析方法是「功能分解」。咱們會把用戶需求先分解成模塊,而後把模塊分解成大的功能,再把大的功能分解成小的功能,整個需求就是按照這樣的方式,最終分解成一個一個的函數。這種解決問題的方式稱爲「自頂向下」,原則是「先總體後局部」,「先大後小」,也有人喜歡使用「自下向上」的分析方式,先解決局部難點,逐步擴大開來,最後組合出來整個程序。其實,這兩種方式殊路同歸,最終都能解決問題,但通常狀況下采用「自頂向下」的方式仍是較爲常見,由於這種方式最容易看清問題的本質。
      我舉個例子來講明面向過程的編程方式:
      用戶需求:老闆讓我寫個通用計算器。
      最終用戶就是老闆,我做爲程序員,任務就是寫一個計算器程序。OK,很簡單,如下就是用C語言完成的計算器:
      假定程序的文件名爲:main.c。
int main(int argc, char *argv[]){
    //變量初始化
    int nNum1,nNum2;
    char cOpr;
    int nResult;
    nNum1 = nNum2 = 0;
    cOpr = 0;
    nResult = 0;
    //輸入數據
    printf("Please input the first number:\r\n");
    scanf("%d",&nNum1);
    printf("Please input the operator:\r\n");
    scanf("%s",&cOpr);
    printf("Please input the second number:\r\n");
    scanf("%d",&nNum2); 
    //計算結果 
    if( cOpr == '+' ){
    nResult = nNum1 + nNum2;
    }else if( cOpr == '-' ){
    nResult = nNum1 - nNum2;
    }else{
    printf("Unknown operator!");
    return -1;
    }
    //輸出結果
    printf("The result is %d!",nResult);
    return 0;
}
 
      拋開細節不講,我想大多數人差很少都會這麼實現吧,很清晰,很簡單,充分體現了「簡單就是美」的原則,面向過程的編程就是這樣有條理的按照順序來逐步實現用戶需求。
凡是作過程序的人都知道,用戶需求歷來都不會是穩定的,最多隻可以作到「相對穩定」。用戶可能會隨時提出加個功能,減個功能的要求,也可能會要求改動一下流程,程序員最煩的就是頻繁地變更需求,尤爲是程序已經寫了大半了,但這種狀況是永遠沒法避免的,也不能徹底歸罪到客戶或者需求分析師。
      以咱們上面的代碼爲例,用戶可能會提出相似的要求:
      首先,你程序中實現了「加法」和「減法」,我還想讓它也能計算「乘法」、「除法」。
      其次,你如今的人機界面太簡單了,我還想要個Windows計算器的界面或者Mac計算器的界面。
      用戶需求開始多了,我得琢磨琢磨該如何去寫這段代碼了。我今天加了「乘」「除」的運算,明天保不齊又得讓我加個「平方」、「立方」的運算,這要是把全部的運算都窮盡了,怎麼也得寫個千八百行代碼吧。還有,用戶要求界面可以更換,還得寫一大堆界面生成的代碼,又得來個千八百行。之後,這麼多代碼堆在一塊兒,怎麼去維護,找個變量得半天,看懂了代碼得半天,萬一不當心改錯了,還得調半天。另外,界面設計我也不擅長,得找個更專業的人來作,作完了以後再加進來吧。這個過程也就是「軟件危機」產生的過程。伴隨着軟件普遍地應用於各個領域,軟件開發的規模變得愈來愈大,複雜度愈來愈高,而其用戶的需求愈來愈不穩定。
      根據用戶提出的兩個需求,面向過程的編程該如何去應對呢?我想你們都很清楚怎麼去改。Very easy,把「計算」和「界面」分開作成兩個獨立的函數,封裝到不一樣的文件中。
      假定程序的文件名爲:main.c。
#include "interface.h"
#include "calculate.h"
int main(int argc, char *argv[]){
    //變量初始化
    int nNum1,nNum2;
    char cOpr;
    int nResult;
    nNum1 = nNum2 = 0;
    cOpr = 0;
    nResult = 0;
    //輸入數據
    if( getParameters(&nNum1,&nNum2,&cOpr) == -1 )
    return -1;
    //計算結果 
    if( calcMachine(nNum1,nNum2,cOpr,&nResult) == -1 )
    return -1;
    //輸出結果
    printf("The result is %d!",nResult);
    return 0;
}
interface.h:
int getParameters(int *nNum1,int * nNum2,char *cOpr);
interface.c:
int getParameters(int *nNum1,int * nNum2,char *cOpr){
    printf("Please input the first number:\r\n");
    scanf("%d",nNum1);
    printf("Please input the operator:\r\n");
    scanf("%s",cOpr);
    printf("Please input the second number:\r\n");
    scanf("%d",nNum2);
    return 0;
}
calculate.h:
int calcMachine(int nNum1,int nNum2,char cOpr, int *nResult);
calculate.c:
int calcMachine(int nNum1,int nNum2,char cOpr,int *nResult){
    if( cOpr == '+' ){
        *nResult = nNum1 + nNum2;
    }else if( cOpr == '-' ){
        *nResult = nNum1 - nNum2;
    }else{
        printf("Unknown operator!");
        return -1;
    };
    return 0;
}
      面向過程的編程(OPP)就是將用戶需求進行「功能分解」。把用戶需求先分解成模塊(.h,.c),再把模塊(.h,.c)分解成大的功能(function),而後把大的功能(function)分解成小的功能(function),如此類推。
      功能分解是一項頗有技術含量的工做,它不只須要分解者具備豐富的實戰經驗,並且須要科學的理論做爲指導。如何分解,分解原則是什麼,模塊粒度多大合適?這些都是架構師的要考慮的問題,也是咱們後面要着重講的內容。
面向過程的編程(OPP)優勢是程序順序執行,流程清晰明瞭。它的缺點是主控程序承擔了太多的任務,各個模塊都須要主控程序進行控制和調度,主控和模塊之間的承擔的任務不均衡。       有的人把面向過程定義爲:算法 + 數據結構,我以爲也很準確。面向過程的編程中算法是核心,數據處於從屬地位,數據隨算法而流動。因此採用面向過程的方式進行編程,通常在動手以前,都要編寫一份流程圖或是數據流圖。
相關文章
相關標籤/搜索