架構師之路

1. 架構師之路(1)---面向過程和面向對象

1、引言
???機算機科學是一門應用科學,它的知識體系是典型的倒三角結構,所用的基礎知識并不多,只是隨著應用領域和方向的不同,產生了很多的分支,所以說編程并不是一件很困難的事情,一個高中生經過特定的訓練就可以做得到。但是,會編程和編好程絕對是兩碼事,同樣的程序員,有的人幾年之后成為了架構師,有的人卻還在不停地coding,只不過ctrl-c、ctrl-v用得更加純熟了。在中國,編程人員最終的歸途無外乎兩條:一是轉向技術管理,它的終點是CTO;二是繼續深入,它的終點是首席架構師,成為CEO的人畢竟是少數。如果你現在還是個普通的程序員,希望繼續在技術這條路上前進的話,我想你還是應該先補充一點軟件工程的思想,學習一點有關設計模式的知識,只有具備這些能力,你才能從整體和宏觀層面來考慮問題、分析問題和解決問題。本人Coding了很多年,中間走了不少彎路,雖然最終沒什么大成就,但總算有一些心得,很愿意把自己的一些經驗拿出來跟大家分享,這或許對你的發展有所幫助。

由程序員轉為架構師,最繞不開的概念就算是面向對象(OO)了。記得在大學的時候,我們專業開了一門課叫《面向對象的編程》。那個時候,我們剛剛學了一門C語言,開發環境用的還是DOS下的TurboC,半點項目開發的經驗都沒有,純粹的空對空。所以,一學期下來,我始終處于一種懵懂狀態,既沒領會面向過程和面向對象到底有什么區別,也沒搞懂面向對象能帶來什么好處。


2、面向過程(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)
(未完待續)

2.3 面向過程編程(OPP) 和面向對象編程(OOP)的關系


??? 關于面向過程的編程(OPP)和面向對象的編程(OOP),給出這它們的定義的人很多,您可以從任何資料中找到很專業的解釋,但以我的經驗來看,講的相對枯燥一點,不是很直觀。除非您已經有了相當的積累,否則說起來還是比較費勁。

我是個老程序員出身,雖然現在的日常工作更多傾向了管理,但至今依然保持編碼的習慣,這句話什么意思呢?我跟大家溝通應該沒有問題。無論你是在重復我走過的路,或者已經走在了我的前面,大家都會有那么一段相同的經歷,都會在思想層面上有一種理解和默契,所以我還是會盡量按照大多數人的常規思維寫下去。

面向過程的編程(OPP)產生在前,面向對象的編程(OOP)產生在后,所以面向對象的編程(OOP)一定會繼承前者的一些優點,并摒棄前者存在的一些缺點,這是符合人類進步的自然規律。兩者在各自的發展和演變過程中,一定會相互借鑒,相互融合,吸收對方的優點,從而出現某些方面的趨同性。但是,即使兩者有更多的相似點,也不會改變它們本質上的不同,因為它們的出發點不同,完全是兩種截然不同的思維方式。關于兩者的關系,我的觀點是這樣的:面向對象編程(OOP)在局部上一定是面向過程(OP)的,面向過程的編程(OPP)在整體上應該借鑒面向對象(OO)的思想。這一段說的的確很空洞,而且也一定會有引來爭議,不過,我勸您還是在閱讀了后面的內容之后,再來評判我觀點的正確與否。

象C++、C#、Java等都是面向對象的語言,c,php(暫且這么說,因為php4以后就支持OO)都是面向過程的語言,那么是不是我用C++寫的程序一定就是面向對象,用c寫的程序一定就是面向過程呢?這種觀點顯然是沒有真正吃透兩者的區別。語言永遠是一種工具,前輩們每創造出來的一種語言,都是你用來實現想法的利器。我覺得好多人用C#,Java寫出來的代碼,要是仔細看看,那實際就是用面向對象(OO)的語言寫的面向過程(OP)的程序。

所以,即使給關羽一根木棍,給你一桿青龍偃月刀,他照樣可以打得你滿頭是包。你就是扛著個偃月刀,也成不了關羽,因為你缺乏關羽最本質的東西---絕世武功。同樣的道理,如果你沒有領會OO思想,怎么可能寫得出真正的OO程序呢?

那是不是面向過程就不好,也沒有存在的必要了?我從來沒有這樣說過。事實上,面向過程的編程(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;
}

“計算”和“界面”分開之后,添加新功能或者修改bug就方便多了,遇到與“計算”相關的需求就去修改calculate模塊,遇到與“界面”相關的需求就去修改interface模塊,因此,整個系統模塊之間的“耦合度”就被放松了,可維護性有了一定程度的改善。

面向過程的編程(OPP)就是將用戶需求進行“功能分解”。把用戶需求先分解成模塊(.h,.c),再把模塊(.h,.c)分解成大的功能(function),然后把大的功能(function)分解成小的功能(function),如此類推。

功能分解是一項很有技術含量的工作,它不僅需要分解者具有豐富的實戰經驗,而且需要科學的理論作為指導。如何分解,分解原則是什么,模塊粒度多大合適?這些都是架構師的要考慮的問題,也是我們后面要著重講的內容。

面向過程的編程(OPP)優點是程序順序執行,流程清晰明了。它的缺點是主控程序承擔了太多的任務,各個模塊都需要主控程序進行控制和調度,主控和模塊之間的承擔的任務不均衡。
有的人把面向過程定義為:算法 + 數據結構,我覺得也很準確。面向過程的編程中算法是核心,數據處于從屬地位,數據隨算法而流動。所以采用面向過程的方式進行編程,一般在動手之前,都要編寫一份流程圖或是數據流圖。

?

?--------------------------------------------------------

如果大家對本文有意見盡可爭論,歡迎大家隨時拍磚。我有著鋼鐵一樣的心臟和城墻一樣的臉皮,承受能力極強,所以無論是支持意見還是反對反對意見,我都會認真閱讀,只要不是色情淫穢和反動言論,我盡量不刪貼。如果碰到不能理解或者錯誤之處,請指出,我會進行驗證和修改。由于個人能力和時間有限,也只能寫到這樣的水平了,大家諒解。有的朋友找我的qq號,我的資料里就有,加為好友后就可以看到,我的qq對任何人開放。


3 架構師的職責


???近來看到CSDN上有個CTO俱樂部,里面聊得是不亦樂乎。我懷著無比崇敬的態度,拜讀了一下牛人們的發言。里面有個哥們發起一個話題:“CTO,你多久沒有寫程序了?”。有人回答:“不寫代碼的CTO,屬于......這公司問題大了!”。看到這里,我就趕緊撤了,怕忍不住反駁幾句,反而遭到牛人們的群毆。試想,一個上點規模的IT公司,還得靠CTO來寫程序的話,那是不是才叫問題大了呢。當然,我沒有做過CTO,所以我有我的不同看法,而且還愿意表達出來,無知者無畏。我情愿相信:我所理解的CTO跟這位CTO所理解的是兩回事。所以我想,如果有人能把CTO的職責給標準化了,也許就不會有這么多的爭論了。
??? 同樣的道理,關于架構師的定義,大家也有著不同的理解。什么是架構師?架構師有哪些職責?我覺得有必要提前明確一下,要不然大家溝通起來也會產生類似問題,子說子理,卯說卯理,但是壓根說得不是一碼子事。

?

3.1 什么是架構師


曾經有這么個段子:
甲:我已經應聘到一家中型軟件公司了,今天上班的時候,全公司的人都來歡迎我。
乙:羨慕ing,都什么人來了?
甲:CEO、COO、CTO、All of 程序員,還有會計、司機都來了。
乙:哇,他們太重視你了,人才啊,這么多人迎接你!
甲:沒有啊,就一個人!
乙:靠,#%¥$%...


???很多的創業公司,一人身兼數職的情形還是很常見的。至少,我是經歷過的,一個人包辦了所有的開發過程,連測試我都做了,絕對的一條龍,但是經常踩鋼絲、騎獨輪車總會有失足的時候,結果有一次,從我手里發出去的光盤母盤,含有病毒僵尸,以至于被迫收回已經推上市場的2萬張光盤,從那之后,我的心臟就開始變得無比堅強,現在就是整個后臺服務都癱瘓了,我也只是微微一笑。其實,一個人身兼架構師和程序員,甚至多種角色,沒什么不妥,后面還會講這個話題,這種現象不是中國特色,跟國外是完全接軌的。我曾經跟米國的一個工程師在msn中聊過類似的話題,發現他們的路子跟咱們沒什么不同,在IT這個行業,我們跟世界的差距只有1天,他們剛弄出來的新東西,我們這里第2天保準見得到。


??? 架構師這個稱呼不是拍腦袋想出來的,是有國際標準(ISO/IEC42010)可查的。架構師是軟件開發活動中的眾多角色之一,它可能是一個人、一個小組,也可能是一個團隊。微軟對架構師有一個分類參考,我們參考一下,他們把架構師分為4種:企業架構師EA(Enterprise Architect)、基礎結構架構師IA(InfrastructureArchitect)、特定技術架構TSA(Technology-Specific Architect)和解決方案架構師SA (SolutionArchitect)。微軟的這個分類是按照架構師專注的領域不同而劃分的。


???EA的職責是決定整個公司的技術路線和技術發展方向。蓋茨給自己的Title就是首席軟件架構師,網易丁磊也喜歡這么稱呼自己,實際上就是EA角色;IA的工作就是提煉和優化技術方面積累和沉淀形成的基礎性的、公共的、可復用的框架和組件,這些都是一個技術型公司傳承下來的最寶貴的財富之一;特定技術架構師TSA,他們主要從事類似安全架構、存儲架構等專項技術的規劃和設計工作;SA的工作則專于解決方案的規劃和設計,“解決方案”這個詞在中國已經到了嚴重泛濫的程度,大忽悠們最喜歡把它掛在嘴邊。所謂解決方案,就是把產品、技術或理論,不斷地進行組合,來創造出滿足用戶需求的選擇。售前工程師一般都是帶著它到客戶那里去發揮的。


??? 大公司會把各種類型的架構師分得很清楚,小公司一般就不那么講究了,架構師多數是是IA+TSA+SA,一人包打天下,所以說大公司出專才,小公司出全才。


???實際工作中,我們也經常會見到另一種比較簡單的分類方式,把架構師分為軟件架構師和系統架構師。軟件架構師基本上是TSA+IA,這也是程序員最容易突破,最可能走上的一條道路,比如JAVA架構師、DotNet架構師、LAPM架構師等等,我后面所講的內容都是與軟件架構師的相關的話題。系統架構師實際上是SA+TSA,更著力于綜合運用已有的產品和技術,來實現客戶期望的需求。系統架構師要求通曉軟、硬件兩方面的知識,所以它的知識體系相對龐雜。關于系統架構師的話題,我們可以稍后再作討論。

?

3.2 架構師的職責

架構師需要參與項目開發的全部過程,包括需求分析、架構設計、系統實現、集成、測試和部署各個階段,負責在整個項目中對技術活動和技術說明進行指導和協調。
架構師主要職責有4條:

1、確認需求
??? 在項目開發過程中,架構師是在需求規格說明書完成后介入的,需求規格說明書必須得到架構師的認可。架構師需要和分析人員反復交流,以保證自己完整并準確地理解用戶需求。

2、系統分解
??? 依據用戶需求,架構師將系統整體分解為更小的子系統和組件,從而形成不同的邏輯層或服務。隨后,架構師會確定各層的接口,層與層相互之間的關系。架構師不僅要對整個系統分層,進行“縱向”分解,還要對同一邏輯層分塊,進行“橫向”分解。
??? 軟件架構師的功力基本體現于此,這是一項相對復雜的工作。

3、技術選型
??? 架構師通過對系統的一系列的分解,最終形成了軟件的整體架構。技術選擇主要取決于軟件架構。
Web Server運行在Windows上還是Linux上?數據庫采用MSSql、Oracle還是Mysql?需要不需要采用MVC或者Spring等輕量級的框架?前端采用富客戶端還是瘦客戶端方式?類似的工作,都需要在這個階段提出,并進行評估。
架構師對產品和技術的選型僅僅限于評估,沒有決定權,最終的決定權歸項目經理。架構師提出的技術方案為項目經理提供了重要的參考信息,項目經理會從項目預算、人力資源、時間進度等實際情況進行權衡,最終進行確認。

4、制定技術規格說明
??? 架構師在項目開發過程中,是技術權威。他需要協調所有的開發人員,與開發人員一直保持溝通,始終保證開發者依照它的架構意圖去實現各項功能。
??? 架構師與開發者溝通的最重要的形式是技術規格說明書,它可以是UML視圖、Word文檔,Visio文件等各種表現形式。通過架構師提供的技術規格說明書,保證開發者可以從不同角度去觀察、理解各自承擔的子系統或者模塊。
架構師不僅要保持與開發者的溝通,也需要與項目經理、需求分析員,甚至與最終用戶保持溝通。所以,對于架構師來講,不僅有技術方面的要求,還有人際交流方面的要求。

3.3 架構師的誤區

1、架構師就是項目經理
??? 架構師不是項目經理。項目經理側重于預算控制、時間進度控制、人員管理、與外部聯系和協調等等工作,具備管理職能。一般小型項目中,常見項目經理兼架構師。

2、架構師負責需求分析
??? 架構師不是需求分析員。需求分析人員的工作是收集需求和分析需求,并與最終用戶、產品經理保持聯系。架構師只對最終的需求審核和確認,提出需求不清和不完整的部分,他會跟需求分析員時刻保持聯系。架構師是技術專家,不是業務專家。


3、架構師從來不寫代碼
??? 這是一個尚存爭論的問題。目前有兩種觀點:
觀點1:架構師不寫代碼,寫代碼純體力活,架構師寫代碼大材小用。架構師把UML的各種視圖交給開發人員,如果有不明確的地方,可以與架構師隨時溝通。
觀點2:架構師本來自于程序員,只是比程序員站的層面更高,比程序員唯一多的是經驗和知識,所以架構師也免不了寫代碼。
??? 我個人覺得這兩種說法是與架構師的出身和所處的環境有關。
???架構師首先是一個技術角色,所以一定是來自于技術人員這個群體,比如系統架構師,多是來自于運維人員,可能本身代碼寫得并不多,或者說寫不出來很漂亮的代碼。軟件架構師多是來自于程序員,有著程序員的血統和情懷,所以在項目開發過程中,可能會寫一些核心代碼。我們的理想是架構師不用寫代碼,但事實上有時候過于理想。架構師寫不寫代碼,可能取決于公司的規模、文化、開發人員的素質等現實情況。另外,架構師也不是跟程序員界限分得那么清楚,按照能力也有高中低之分,寫不寫代碼不是區分兩者的根本標準。

3.4 架構師的基本素質

周星馳有個片子《喜劇之王》,劇中的尹天仇整天揣著本《演員的自我修養》,一個好演員不僅需要天賦,也需要一定的理論指導,無師自通的人畢竟是少數。架構師的成長過程也是這樣。從普通程序員到高級程序員,再到架構師,是一個經驗積累和思想升華的過程。經驗積累是一個方面,素質培養是另一個方面,兩者相輔相成,所以我覺得有必要把架構師的所要具備的素質羅列一下,作為程序員努力的方向。

1、溝通能力
???為了提高效率,架構師必須贏得團隊成員、項目經理、客戶或用戶認同,這就需要架構師具有較強的溝通能力。溝通能力是人類最普遍性的素質要求,技術人員好像容易忽略,想成為架構師就不能忽略。千萬不要抱著這樣的觀念:懷才跟懷孕似的,時間久了總會被人發現的。還是天橋上賣大力丸的哥們說得對:光說不練假把式,光練不說傻把式。看看你周圍的頭頭腦腦們,哪一個不是此中高手,我們千萬不要鄙視,認為這是阿諛奉承、投機鉆營,凡事都要看到積極的一面,“溝通”的確是一種能力。我認為自己是一個略內向的人,因為我是農村出來的孩子,普通話都說不好,以前或多或少帶有點自卑感,幻想著是金子總會發光,所以在職業生涯中吃了不少虧。現在,我深深懂得了溝通的重要性,我會很主動地跟同事們,跟老大們不定時地溝通,感覺工作起來順暢多了。

??? 這一條我認為最為重要,所以排在首位。我甚至認為下面幾條都可以忽略,唯一這一條得牢記,而且要常常提醒自己。

2、領導能力
????? 架構師能夠推動整個團隊的技術進展,能在壓力下作出關鍵性的決策,并將其貫徹到底。架構師如何來保證這種執行力?這就需要架構師具有領導能力。

???架構師的領導能力的取得跟項目經理不太一樣。項目經理主要負責解決行政管理,這種能力與技術關系不大,他有人權和財權,再扯上一張“領導”的虎皮,采用“胡蘿卜加大棒”的方式,基本上可以保證執行力。架構師在項目里面可能更多地使用非正式的領導力,也就是我們常說的影響力,里面包括個人魅力、技術能力、知識傳遞等等。

3、抽象思維和分析能力
???架構師必須具備抽象思維和分析的能力,這是你進行系統分析和系統分解的基本素質。只有具備這樣的能力,架構師才能看清系統的整體,掌控全局,這也是架構師大局觀的形成基礎。你如何具備這種能力呢?一是來自于經驗,二是來自于學習。架構師不僅要具備在問題領域上的經驗,也需要具備在軟件工程領域內的經驗。也就是說,架構師必須能夠準確得理解需求,然后用軟件工程的思想,把需求轉化和分解成可用計算機語言實現的程度。經驗的積累是需要一個時間過程的,這個過程誰也幫不了你,是需要你去經歷的。但是,如果你有意識地去培養,不斷吸取前人的經驗的話,還是可以縮短這個周期的。這也是我寫作此系列的始動力之一。

4、技術深度和廣度

?? 架構師最好精通1-2個技術,具備這種技術能力可以更加深入的理解有關架構的工作原理,也可以拉近和開發人員的距離,并形成團隊中的影響力。

?? 架構師的技術知識廣度也很重要,需要了解盡可能多的技術,所謂見多識廣,只有這樣,才可能綜合各種技術,選擇更加適合項目的解決方案。有的人說,架構師技術廣度的要求高于技術深度的要求,這是很有道理的。
總而言之,一句話:架構師是項目團隊中的技術權威。

?

面向過程和面向對象這兩個基本概念,不僅架構師需要非常清楚,程序員、設計師也要非常清楚,這也是系統分析、設計和編碼最基本的常識。我接觸的程序員,很多人只停留在一種“似是而非”的程度,這是不行的,想要繼續前進,就得把基礎夯實,所以我覺得很有必要先回回爐,補補課。
---------------------------------------------------------------------------------------------------
后記:在講面向對象之前寫了這么一篇,主要就是要把前面漏下的功課補上。


架構師之路(4)---詳解面向對象

3.5 詳解面向對象的編程(OOP)

3.5.1 什么是面向對象
??? 剛接觸編程的時候,多數人本能的反映可能是面向過程(OP)的,而不是面向對象(OO)的。這種現象其實是很正常的,改變思維方式是需要一個過程的,我大體歸納了一下其形成的原因:


1、直接原因
??? 你還沒有養成面向對象分析問題和解決問題的習慣。建立面向對象的思維方式需要一定時間的訓練和揣摩才能形成,所以你可以在學習或具體項目中刻意地強化這種意識。一般情況下,經過一段時間之后,你會覺得這是自然而然的事情,只有心中OO,眼中自然OO了。


2、歷史原因
??? 我們從小接受的培訓都是采用面向過程(OP)的方式分析問題和解決問題,尤其是數學,多數是強調按部就班的解決問題,計算機軟件的發展一直就與數學是很有淵源,所以,順理成章的,把面向過程(OP)的方式帶入到軟件開發也是很自然的事情。


???什么是面向對象,或者談談你對面向對象的理解,這恐怕是軟件開發人員,尤其是程序員和設計師應聘的時候,面試官常最掛在嘴邊的問題吧。面向對象對應的英文是Object-Oriented,把Object-Oriented翻譯成“面向對象”,我一直覺得這個譯法不太確切,因為多數人第一次看到“面向對象”這四個字,都很難從字面上理解它到底是什么意思。后來,我又查閱了一些有關的資料,發現港澳臺的計算機書籍中是把它翻譯成了“物件導向”,這個譯法,我感覺不錯,于我心頗有些戚戚焉。“物件導向”比較準確地反映了面向對象認識和解決問題都是要圍繞對象展開的。


所以,面向對象的思維方式認為:軟件系統是一組交互的對象的集合。一組相關的對象組合為一個子系統,一組子系統繼續組合為更復雜的子系統,直至組合成整個系統。


??? 面向對象方式的出發點是盡可能模擬人類習慣的思維方式,將“問題域”中涉及的內容抽象為“對象”,使軟件開發的方法與過程盡可能接近人類認識世界解決問題的方法與過程。


??? 面向過程就是分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了。面向對象是把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。

??? 面向過程認識和解決問題的思維,可以稱為“流程論”,重點放在處理過程的步驟,流程是整個系統的核心。

??? 面向對象認識和解決問題的思維,可以稱為“組裝論”,重心放在對象的抽象和提取上,然后將對象組裝為整體。

所以OO和OP從思維方式來講,出發點還是完全不同的。


3.5.2 OP PK OO
??? 咱們用象棋對戰的例子,來比較OP和OO的不同:

?

?
紅方:功夫熊貓 黑方:悍嬌虎 裁判:龜仙人


??? 采用面向過程(OPP)的設計思路,首先分拆整個對戰過程,分析雙方對戰的步驟,得到如下流程:

?

?

?

??? 把上面每個步驟分別用函數進行實現,問題就解決了。

?

??? 我們再來看看面向對象是如何來解決問題,整個象棋游戲可以抽象出3種對象:
1、棋手,負責行棋,這兩者行為一致。
2、棋盤,負責繪制棋盤畫面。
3、裁判,負責判定諸如吃子、犯規和輸贏。


??? 三者之間的關系如下:

?

?
??? 第一類對象棋手負責行棋,并告知第二類對象棋盤中棋子布局的變化,棋盤接收到了棋子布局的變化后,負責在繪制屏幕,同時利用第三類對象裁判來對棋局進行判定。
從以上兩種的實現方式可以看出幾點:


1、可維護性
???面向對象是以數據和功能來劃分問題,而不是依據流程和步驟。同樣是繪制棋盤的行為,在面向過程的設計中分散在了很多的步驟中,很可能出現在不同的繪制版本中,只是不是很像一份“蛋炒飯”中的雞蛋?在面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一,這就是把雞蛋從“蛋炒飯”中分離出來的效果。


2、可擴展性
???假如我要加入悔棋的功能,如果要改動面向過程的設計,那么從行棋到顯示再到判定這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤對象保存了雙方的棋譜,簡單回溯,減一就可以了,而顯示和判定不涉及,同時整體對各個對象功能的調用順序都沒有變化,改動只限定在了局部。


3.5.3 OO的深層思考
??? OO認為:軟件系統是一組交互的對象的集合。
??? 因為人類對現實世界是非常熟悉的,所以OO就是通過抽象的方式,把問題域映射到現實世界,盡量模擬現實世界的萬事萬物。通過這種方式,就可以運用現實世界中解決問題的方法與過程,來解決軟件領域內的問題。
有人說:OO眼里一切皆對象,這句話還是很有道理的。
OO到底給軟件開發帶來了什么樣的好處?OO的抽象的尺度是如何把握的呢?這都是問題。

?

(未完待續)
---------------------------------------------------------------------------
后記:今天累了,寫到這兒吧。晚上回家的時候碰到兩個blond beauty,they are from USA。They want to goto 2nd foreign language colledge,but did not know how to get there. Itold them the correct route in English. 我學了這么多年的英語,終于幫了國際友人一個小忙。


架構師之路(5)---面向對象的設計原則

1?OO的設計原則
??? 采用面向對象的分析和設計思想,為我們分析和解決問題提供了一種全新的思維方式。我們在拿到需求之后(略去OOA,以后補全),接下來的問題就是:如何對系統進行面向對象的設計呢?
??? 按照軟件工程的理論,面向對象的設計要解決的核心問題就是可維護性和可復用性,尤其是可維護性,它是影響軟件生命周期重要因素。通常情況下,軟件的維護成本遠遠大于初期開發成本。
???一個可維護性很差的軟件設計,人們通常稱之為“臭味”的,形成的原因主要有這么幾個:過于僵硬、過于脆弱、復用率低或者黏度過高。相反,一個好的系統設計應該是靈活的、可擴展的、可復用的、可插拔的。在20世紀80到90年代,很多業內專家不斷探索面向對象的軟件設計方法,陸續提出了一些設計原則。這些設計原則能夠顯著地提高系統的可維護性和可復用性,成為了我們進行面向對象設計的指導原則:

1、單一職責原則SRP
??? 每一個類應該專注于做一件事情。

2、“開-閉”原則OCP
??? 每一個類應該是對擴展開放,對修改關閉。

3、?里氏代換原則LSP
??? 避免造成派生類的方法非法或退化,一個基類的用戶應當不需要知道這個派生類。

4、?依賴倒轉原則DIP
??? 用依賴于接口和抽象類來替代依賴容易變化的具體類。

5、?接口隔離原則ISP
??? 應當為客戶提供盡可能小的接口,而不是提供大的接口。

其中,“開-閉”原則是面向對象的可復用設計的基石,其他設計原則(里氏代換原則、依賴倒轉原則、合成/聚合復用原則、迪米特法則、接口隔離原則)是實現“開-閉”原則的手段和工具。
我會為大家一一進行講解。

2?單一職責原則SRP(Single-Responsibility Principle)


2.1?什么是單一職責
??? 單一職責就是指一個類應該專注于做一件事。現實生活中也存在諸如此類的問題:“一個人可能身兼數職,甚至于這些職責彼此關系不大,那么他可能無法做好所有職責內的事情,所以,還是專人專管比較好。”我們在設計類的時候,就應該遵循這個單一職責原則。

??? 記得有人比喻過軟件開發、設計原則、設計模式之間的關系就是戰爭、戰略和戰術的關系,關于設計模式實際上是設計原則的具體應用,以后我們還會講到這一點。另外,大家都很熟悉計算器的例子,很多的人都愿意以此為例,我們也以計算器編程為例說明單一職責原則:
??? 在有些人眼里,計算器就是一件東西,是一個整體,所以它把這個需求進行了抽象,最終設計為一個Calculator類,代碼如下:

class Calculator{
? public String calculate() {

??? Console.Write("Please input the first number:");
??? String strNum1 = Console.ReadLine();
?
??? Console.Write(Please input the operator:");
??? String strOpr= Console.ReadLine();

??? Console.Write("Please input the second number:");
??? String strNum2 = Console.ReadLine();

??? String strResult = "";
??? if (strOpr == "+"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) + Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "-"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) - Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "*"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) * Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "/"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) / Convert.ToDouble(strNum2));
??? }

??? Console.WriteLine("The result is " + strResult);
? }????
}

?

另外,還有一部分人認為:計算器是一個外殼和一個處理器的組合。

class Appearance{
? public int displayInput(String &strNum1,String &strOpr, String &strNum2) {
??? Console.Write("Please input the first number:");
??? strNum1 = Console.ReadLine();
???
??? Console.Write(Please input the operator:");
??? strOpr= Console.ReadLine();

?

??? Console.Write("Please input the second number:");
??? strNum2 = Console.ReadLine();

?

??? return 0;
? }

? public String displayOutput(String strResult) {
??? Console.WriteLine("The result is " + strResult);
? }
}

?

class Processor{
? public String calculate(String strNum1,String strOpr, String strNum2){
??? String strResult = "";
??? if (strOpr == "+"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) + Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "-"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) - Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "*"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) * Convert.ToDouble(strNum2));
??? }
??? else if (strOpr == "/"){
????? strResult = Convert.ToString(Convert.ToDouble(strNum1) / Convert.ToDouble(strNum2));
??? }
??? return strResult;
? }
}

為什么這么做呢?因為外殼和處理器是兩個職責,是兩件事情,而且都是很容易發生需求變動的因素,所以把它們放到一個類中,違背了單一職責原則。
??? 比如,用戶可能對計算器提出以下要求:
??? 第一,目前已經實現了“加法”、“減法”、“乘法”和“除法”,以后還可能出現“乘方”、“開方”等很多運算。
??? 第二,現在人機界面太簡單了,還可能做個Windows計算器風格的界面或者Mac計算器風格的界面。
所以,把一個類Calculator 拆分為兩個類Appearance和Processor,一個類做一件事情,這樣更容易應對需求變化。如果界面需要修改,那么就去修改Appearance類;如果處理器需要修改,那么就去修改Processor類。

?

??? 我們再舉一個郵件的例子。我們平常收到的郵件內容,看起來是一封信,實際上內部有兩部分組成:郵件頭和郵件體。電子郵件的編碼要求符合RFC822標準。
第一種設計方式是這樣:
interface IEmail {
??? public void setSender(String sender);
??? public void setReceiver(String receiver);
??? public void setContent(String content);
}

class Email implements IEmail {?
??? public void setSender(String sender) {// set sender; }?
??? public void setReceiver(String receiver) {// set receiver; }?
??? public void setContent(String content) {// set content; }
}

這個設計是有問題的,因為郵件頭和郵件體都有變化的可能性。
1、郵件頭的每一個域的編碼,可能是BASE64,也可能是QP,而且域的數量也不固定。
2、郵件體中封裝的郵件內容可能是PlainText類型,也可能是HTML類型,甚至于流媒體。
所謂第一種設計方式違背了單一職責原則,里面封裝了兩種可能引起變化的原因。
我們依照單一職責原則,對其進行改進后,變為第二種設計方式:
interface IEmail {
? public void setSender(String sender);
? public void setReceiver(String receiver);
? public void setContent(IContent content);
}

interface IContent {
? public String getAsString();
}

class Email implements IEmail {
? public void setSender(String sender) {// set sender; }?
? public void setReceiver(String receiver) {// set receiver; }?
? public void setContent(IContent content) {// set content; }
}

有的資料把單一職責解釋為:“僅有一個引起它變化的原因”。這個解釋跟“專注于做一件事”是等價的。如果一個類同時做兩件事情,那么這兩件事情都有可能引起它的變化。同樣的道理,如果僅有一個引起它變化的原因,那么這個類也就只能做一件事情。

2.2?單一職責原則的使用
???單一職責原則的尺度如何掌握?我們怎么能知道該拆分還是不應該拆分呢?原則很簡單:需求決定。如果你所需要的計算器,永遠都沒有外觀和處理器變動的可能性,那么就應該把它抽象為一個整體的計算器;如果你所需要的計算器,外殼和處理器都有可能發生變動,那么就必須把它拆離為外殼和處理器。
單一職責原則實際上是把相同的職責進行了聚合,避免把相同的職責分散到不同的類之中,這樣就可以控制變化,把變化限制在一個地方,防止因為一個地方的變動,引起更多地方的變動的“漣漪效應”,單一職責原則避免一個類承擔過多的職責。單一職責原則不是說一個類就只有一個方法,而是具有單一功能。
??? 我們在使用單一職責原則的時候,牢記以下幾點:
A、一個設計合理的類,應該僅有一個可以引起它變化的原因,即單一職責,如果有多個原因可以引起它的變化,就必須進行分離;
B、在沒有需求變化征兆的情況下,應用單一職責原則或其他原則是不明智的,因為這樣會使系統變得很復雜,系統將會變成一堆細小的顆粒組成,純屬于沒事找抽;
C、在需求能夠預計或實際發生變化時,就應該使用單一職責原則來重構代碼,有經驗的設計師、架構師對可能出現的需求變化很敏感,設計上就會具有一定的前瞻性。

?-------------------------------------------------------------

后記:最近看了一個“現場說法”的電視節目,著實有意思。說是最近有兩個偷車大盜被我警方抓獲。這倆大盜都是賊中高手,非常了得,不過,他們卻有著不同的成長路線。
????其中一個大盜,苦心鉆研開鎖技術,專門去香港學習先進技術,前后花了一百多萬,非常舍得投資,回來后屢屢得手。另一個大盜,就比較狡猾,整天到商場的停車場,跟隨著寶馬、奔馳的車主,在車主購物的時候,伺機偷去車鑰匙,然后從停車場把車開走,案發的時候案值達到了千萬。呵呵,看來干什么事,都得找到關鍵所在。


架構師之路(6)---OOD的開閉原則

2?開閉原則(Open-Closed Principle,OCP)


2.1?什么是開閉原則
??? 開閉原則是面向對象設計中“可復用設計”的基石,是面向對象設計中最重要的原則之一,其它很多的設計原則都是實現開閉原則的一種手段。


??? 1988年,Bertrand Meyer在他的著作《Object Oriented Software Construction》中提出了開閉原則,它的原文是這樣:“Software entities should be open for extension,but closed for modification”。翻譯過來就是:“軟件實體應當對擴展開放,對修改關閉”。這句話說得略微有點專業,我們把它講得更通俗一點,也就是:軟件系統中包含的各種組件,例如模塊(Modules)、類(Classes)以及功能(Functions)等等,應該在不修改現有代碼的基礎上,引入新功能。開閉原則中“開”,是指對于組件功能的擴展是開放的,是允許對其進行功能擴展的;開閉原則中“閉”,是指對于原有代碼的修改是封閉的,即不應該修改原有的代碼。


2.2?如何實現開閉原則
??? 實現開閉原則的關鍵就在于“抽象”。把系統的所有可能的行為抽象成一個抽象底層,這個抽象底層規定出所有的具體實現必須提供的方法的特征。作為系統設計的抽象層,要預見所有可能的擴展,從而使得在任何擴展情況下,系統的抽象底層不需修改;同時,由于可以從抽象底層導出一個或多個新的具體實現,可以改變系統的行為,因此系統設計對擴展是開放的。


??? 我們在軟件開發的過程中,一直都是提倡需求導向的。這就要求我們在設計的時候,要非常清楚地了解用戶需求,判斷需求中包含的可能的變化,從而明確在什么情況下使用開閉原則。


??? 關于系統可變的部分,還有一個更具體的對可變性封裝原則(Principle of Encapsulation of Variation,? EVP),它從軟件工程實現的角度對開閉原則進行了進一步的解釋。EVP要求在做系統設計的時候,對系統所有可能發生變化的部分進行評估和分類,每一個可變的因素都單獨進行封裝。


??? 我們在實際開發過程的設計開始階段,就要羅列出來系統所有可能的行為,并把這些行為加入到抽象底層,根本就是不可能的,這么去做也是不經濟的,費時費力。另外,在設計開始階段,對所有的可變因素進行預計和封裝也不太現實,也是很難做得到。所以,開閉原則描繪的愿景只是一種理想情況或是極端狀態,現實世界中是很難被完全實現的。我們只能在某些組件,在某種程度上符合開閉原則的要求。


??? 通過以上的分析,對于開閉原則,我們可以得出這樣的結論:雖然我們不可能做到百分之百的封閉,但是在系統設計的時候,我們還是要盡量做到這一點。


??? 對于軟件系統的功能擴展,我們可以通過繼承、重載或者委托等手段實現。以接口為例,它對修改就是是封閉的,而對具體的實現是開放的,我們可以根據實際的需要提供不同的實現,所以接口是符合開閉原則的。

?


2.3?開閉原則能夠帶來什么好處
??? 如果一個軟件系統符合開閉原則的,那么從軟件工程的角度來看,它至少具有這樣的好處:


??可復用性好。

??? 我們可以在軟件完成以后,仍然可以對軟件進行擴展,加入新的功能,非常靈活。因此,這個軟件系統就可以通過不斷地增加新的組件,來滿足不斷變化的需求。


??可維護性好。

??? 由于對于已有的軟件系統的組件,特別是它的抽象底層不去修改,因此,我們不用擔心軟件系統中原有組件的穩定性,這就使變化中的軟件系統有一定的穩定性和延續性。


2.4?開閉原則與其它原則的關系
??? 開閉原則具有理想主義的色彩,它是面向對象設計的終極目標。因此,針對開閉原則的實現方法,一直都有面向對象設計的大師費盡心機,研究開閉原則的實現方式。后面要提到的里氏代換原則(LSP)、依賴倒轉原則(DIP)、接口隔離原則(ISP)以及抽象類(Abstract Class)、接口(Interace)等等,都可以看作是開閉原則的實現方法。

架構師之路(7)---里氏代換原則

4?里氏代換原則(Liskov Substitution Principle, LSP)


4.1?什么是里氏代換原則

??? 里氏代換原則是由麻省理工學院(MIT)計算機科學實驗室的Liskov女士,在1987年的OOPSLA大會上發表的一篇文章《Data Abstraction and Hierarchy》里面提出來的,主要闡述了有關繼承的一些原則,也就是什么時候應該使用繼承,什么時候不應該使用繼承,以及其中的蘊涵的原理。2002年,我們前面單一職責原則中提到的軟件工程大師Robert C. Martin,出版了一本《Agile Software Development Principles Patterns and Practices》,在文中他把里氏代換原則最終簡化為一句話:“Subtypes must be substitutable for their base types”。也就是,子類必須能夠替換成它們的基類。
??? 我們把里氏代換原則解釋得更完整一些:在一個軟件系統中,子類應該可以替換任何基類能夠出現的地方,并且經過替換以后,代碼還能正常工作。

4.2?第一個例子:正方形不是長方形
??? “正方形不是長方形”是一個理解里氏代換原則的最經典的例子。在數學領域里,正方形毫無疑問是長方形,它是一個長寬相等的長方形。所以,我們開發的一個與幾何圖形相關的軟件系統中,讓正方形繼承自長方形是順利成章的事情。現在,我們截取該系統的一個代碼片段進行分析:
長方形類Rectangle:
class Rectangle {
? double length;
? double width;
? public double getLength() { return length; }
? public void setLength(double height) { this.length = length; }??
? public double getWidth() { return width; }
? public void setWidth(double width) { this.width = width; }
}
正方形類Square:
class Square extends Rectangle {
? public void setWidth(double width) {
??? super.setLength(width);
??? super.setWidth(width);??
?}
? public void setLength(double length) {
??? super.setLength(length);
??? super.setWidth(length);??
? }
}
??? 由于正方形的度和寬度必須相等,所以在方法setLength和setWidth中,對長度和寬度賦值相同。類TestRectangle是我們的軟件系統中的一個組件,它有一個resize方法要用到基類Rectangle,resize方法的功能是模擬長方形寬度逐步增長的效果:
? 測試類TestRectangle:
class TestRectangle {
? public void resize(Rectangle objRect) {
??? while(objRect.getWidth() <= objRect.getLength()? ) {
??????? objRect.setWidth(? objRect.getWidth () + 1 );
??? }
? }
}
??? 我們運行一下這段代碼就會發現,假如我們把一個普通長方形作為參數傳入resize方法,就會看到長方形寬度逐漸增長的效果,當寬度大于長度,代碼就會停止,這種行為的結果符合我們的預期;假如我們再把一個正方形作為參數傳入resize方法后,就會看到正方形的寬度和長度都在不斷增長,代碼會一直運行下去,直至系統產生溢出錯誤。所以,普通的長方形是適合這段代碼的,正方形不適合。
??? 我們得出結論:在resize方法中,Rectangle類型的參數是不能被Square類型的參數所代替,如果進行了替換就得不到預期結果。因此,Square類和Rectangle類之間的繼承關系違反了里氏代換原則,它們之間的繼承關系不成立,正方形不是長方形。

4.3?第二個例子:鴕鳥不是鳥
??? “鴕鳥非鳥”也是一個理解里氏代換原則的經典的例子。“鴕鳥非鳥”的另一個版本是“企鵝非鳥”,這兩種說法本質上沒有區別,前提條件都是這種鳥不會飛。生物學中對于鳥類的定義:“恒溫動物,卵生,全身披有羽毛,身體呈流線形,有角質的喙,眼在頭的兩側。前肢退化成翼,后肢有鱗狀外皮,有四趾”。所以,從生物學角度來看,鴕鳥肯定是一種鳥。
我們設計一個與鳥有關的系統,鴕鳥類順理成章地由鳥類派生,鳥類所有的特性和行為都被鴕鳥類繼承。大多數的鳥類在人們的印象中都是會飛的,所以,我們給鳥類設計了一個名字為fly的方法,還給出了與飛行相關的一些屬性,比如飛行速度(velocity)。
? 鳥類Bird:
class Bird {
?? double velocity;
?? public fly() { //I am flying; };

?? public setVelocity(double velocity) { this.velocity = velocity; };
?? public getVelocity() { return this.velocity; };
}
??? 鴕鳥不會飛怎么辦?我們就讓它扇扇翅膀表示一下吧,在fly方法里什么都不做。至于它的飛行速度,不會飛就只能設定為0了,于是我們就有了鴕鳥類的設計。
?? 鴕鳥類Ostrich:
class Ostrich extends Bird {
??? public fly() { //I do nothing; };
??? public setVelocity(double velocity) { this.velocity = 0; };
?? public getVelocity() { return 0; };
}
??? 好了,所有的類都設計完成,我們把類Bird提供給了其它的代碼(消費者)使用。現在,消費者使用Bird類完成這樣一個需求:計算鳥飛越黃河所需的時間。
??? 對于Bird類的消費者而言,它只看到了Bird類中有fly和getVelocity兩個方法,至于里面的實現細節,它不關心,而且也無需關心,于是給出了實現代碼:
?? 測試類TestBird:
class TestBird {
?? public calcFlyTime(Bird bird) {
?? try{
???? double riverWidth = 3000;
???? System.out.println(riverWidth / bird.getVelocity());
?? }catch(Exception err){
???? System.out.println("An error occured!");
?? }
?? };
}
??? 如果我們拿一種飛鳥來測試這段代碼,沒有問題,結果正確,符合我們的預期,系統輸出了飛鳥飛越黃河的所需要的時間;如果我們再拿鴕鳥來測試這段代碼,結果代碼發生了系統除零的異常,明顯不符合我們的預期。
??? 對于TestBird類而言,它只是Bird類的一個消費者,它在使用Bird類的時候,只需要根據Bird類提供的方法進行相應的使用,根本不會關心鴕鳥會不會飛這樣的問題,而且也無須知道。它就是要按照“所需時間 = 黃河的寬度 / 鳥的飛行速度”的規則來計算鳥飛越黃河所需要的時間。
??? 我們得出結論:在calcFlyTime方法中,Bird類型的參數是不能被Ostrich類型的參數所代替,如果進行了替換就得不到預期結果。因此,Ostrich類和Bird類之間的繼承關系違反了里氏代換原則,它們之間的繼承關系不成立,鴕鳥不是鳥。


4.4?鴕鳥到底是不是鳥?
??? “鴕鳥到底是不是鳥”,鴕鳥是鳥也不是鳥,這個結論似乎就是個悖論。產生這種混亂有兩方面的原因:
原因一:對類的繼承關系的定義沒有搞清楚。
??? 面向對象的設計關注的是對象的行為,它是使用“行為”來對對象進行分類的,只有行為一致的對象才能抽象出一個類來。我經常說類的繼承關系就是一種“Is-A”關系,實際上指的是行為上的“Is-A”關系,可以把它描述為“Act-As”。關于類的繼承的細節,我們可以單獨再講。
??? 我們再來看“正方形不是長方形”這個例子,正方形在設置長度和寬度這兩個行為上,與長方形顯然是不同的。長方形的行為:設置長方形的長度的時候,它的寬度保持不變,設置寬度的時候,長度保持不變。正方形的行為:設置正方形的長度的時候,寬度隨之改變;設置寬度的時候,長度隨之改變。所以,如果我們把這種行為加到基類長方形的時候,就導致了正方形無法繼承這種行為。我們“強行”把正方形從長方形繼承過來,就造成無法達到預期的結果。
??? “鴕鳥非鳥”基本上也是同樣的道理。我們一講到鳥,就認為它能飛,有的鳥確實能飛,但不是所有的鳥都能飛。問題就是出在這里。如果以“飛”的行為作為衡量“鳥”的標準的話,鴕鳥顯然不是鳥;如果按照生物學的劃分標準:有翅膀、有羽毛等特性作為衡量“鳥”的標準的話,鴕鳥理所當然就是鳥了。鴕鳥沒有“飛”的行為,我們強行給它加上了這個行為,所以在面對“飛越黃河”的需求時,代碼就會出現運行期故障。


原因二:設計要依賴于用戶要求和具體環境。
??? 繼承關系要求子類要具有基類全部的行為。這里的行為是指落在需求范圍內的行為。圖中鳥類具有4個對外的行為,其中2個行為分別落在A和B系統需求中:

?


系統需求和對象關系示意圖


??? A需求期望鳥類提供與飛翔有關的行為,即使鴕鳥跟普通的鳥在外觀上就是100%的相像,但在A需求范圍內,鴕鳥在飛翔這一點上跟其它普通的鳥是不一致的,它沒有這個能力,所以,鴕鳥類無法從鳥類派生,鴕鳥不是鳥。
??? B需求期望鳥類提供與羽毛有關的行為,那么鴕鳥在這一點上跟其它普通的鳥一致的。雖然它不會飛,但是這一點不在B需求范圍內,所以,它具備了鳥類全部的行為特征,鴕鳥類就能夠從鳥類派生,鴕鳥就是鳥。

??? 所有派生類的行為功能必須和使用者對其基類的期望保持一致,如果派生類達不到這一點,那么必然違反里氏替換原則。在實際的開發過程中,不正確的派生關系是非常有害的。伴隨著軟件開發規模的擴大,參與的開發人員也越來越多,每個人都在使用別人提供的組件,也會為別人提供組件。最終,所有人的開發的組件經過層層包裝和不斷組合,被集成為一個完整的系統。每個開發人員在使用別人的組件時,只需知道組件的對外裸露的接口,那就是它全部行為的集合,至于內部到底是怎么實現的,無法知道,也無須知道。所以,對于使用者而言,它只能通過接口實現自己的預期,如果組件接口提供的行為與使用者的預期不符,錯誤便產生了。里氏代換原則就是在設計時避免出現派生類與基類不一致的行為。

4.5?如何正確地運用里氏代換原則
?? 里氏代換原則目的就是要保證繼承關系的正確性。我們在實際的項目中,是不是對于每一個繼承關系都得費這么大勁去斟酌?不需要,大多數情況下按照“Is-A”去設計繼承關系是沒有問題的,只有極少的情況下,需要你仔細處理一下,這類情況對于有點開發經驗的人,一般都會覺察到,是有規律可循的。最典型的就是使用者的代碼中必須包含依據子類類型執行相應的動作的代碼:
動物類Animal:
public class Animal{
? String name;
? public Animal(String name) {
??? this.name = name;
?}
? public void printName(){
? try{
???? System.out.println("I am a " + name + "!");
??}catch(Exception err){
???? System.out.println("An error occured!");
??}
}
}
貓類Cat:
public class Cat extends Animal{
? public Cat(String name){
??? super(name);
? }
? public void Mew(){
??try{
?????? System.out.println("Mew~~~ ");
??}catch(Exception err){
?????? System.out.println("An error occured!");
??}
?}
}
狗類Dog:
public class Dog extends Animal {
? public Dog(String name) {
??? super(name);
?}
?public void Bark(){
?try{
???? System.out.println("Bark~~~ ");
?}catch(Exception err){
???? System.out.println("An error occured!");
?}
}
}
測試類:TestAnimal
public class TestAnimal {
???public void TestLSP(Animal animal){
???? if (animal instanceof Cat ){
???????? Cat cat = (Cat)animal;
???????? cat.printName();
???????? cat.Mew();
???? }
???? if (animal instanceof Dog ){

?????? Dog dog = (Dog)animal;
?????? dog.printName();
?????? dog.Bark();
???? }
?}
}
??? 象這種代碼是明顯不符合里氏代換原則的,它給使用者使用造成很大的麻煩,甚至無法使用,對于以后的維護和擴展帶來巨大的隱患。實現開閉原則的關鍵步驟是抽象化,基類與子類之間的繼承關系就是一種抽象化的體現。因此,里氏代換原則是實現抽象化的一種規范。違反里氏代換原則意味著違反了開閉原則,反之未必。里氏代換原則是使代碼符合開閉原則的一個重要保證。

??? 我們常見這樣的代碼,至少我以前的Java和php項目中就出現過。比如有一個網頁,要實現對于客戶資料的查看、增加、修改、刪除功能,一般Server端對應的處理類中都有這么一段:
if(action.Equals(“add”)){
? //do add action
}
else if(action.Equals(“view”)){
? //do view action
}
else if(action.Equals(“delete”)){
? //do delete action
}
else if(action.Equals(“modify”)){
? //do modify action
}
?? 大家都很熟悉吧,其實這是違背里氏代換原則的,結果就是可維護性和可擴展性會變差。有人說:我這么用,效果好像不錯,干嘛講究那么多呢,實現需求是第一位的。另外,這種寫法看起來很很直觀的,有利于維護。其實,每個人所處的環境不同,對具體問題的理解不同,難免局限在自己的領域內思考問題。對于這個說法,我覺得應該這么解釋:作為一個設計原則,是人們經過很多的項目實踐,最終提煉出來的指導性的內容。如果對于你的項目來講,顯著增加了工作量和復雜度,那我覺得適度的違反并不為過。做任何事情都是個度的問題,過猶不及都不好。在大中型的項目中,是一定要講究軟件工程的思想,講究規范和流程的,否則人員協作和后期維護將會是非常困難的。對于小型的項目可能相應的要簡化很多,可能取決于時間、資源、商業等各種因素,但是多從軟件工程的角度去思考問題,對于系統的健壯性、可維護性等性能指標的提高是非常有益的。像生命周期只有一個月的系統,你還去考慮一大堆原則,除非腦袋被驢踢了。
??? 實現開閉原則的關鍵步驟是抽象化,基類與子類之間的繼承關系就是一種抽象化的體現。因此,里氏代換原則是實現抽象化的一種規范。違反里氏代換原則意味著違反了開閉原則,反之未必。里氏代換原則是使代碼符合開閉原則的一個重要保證。

通過里氏代換原則給我們帶來了什么樣的啟示?
??? 類的繼承原則:如果一個繼承類的對象可能會在基類出現的地方出現運行錯誤,則該子類不應該從該基類繼承,或者說,應該重新設計它們之間的關系。
??? 動作正確性保證:符合里氏代換原則的類擴展不會給已有的系統引入新的錯誤。


架構師之路(8)---IoC框架


1?IoC理論的背景
??? 我們都知道在面向對象的應用中,軟件系統都是由N個對象組成的,它們通過彼此的合作,最終實現業務邏輯。


?
圖1:耦合在一起的對象


???如果我們打開機械式手表的后蓋,就會看到與上面類似的情形,各個齒輪分別帶動時針、分針和秒針順時針旋轉,從而在表盤上產生正確的時間。上圖畫的就是這樣的一個齒輪組,它擁有多個獨立的齒輪,這些齒輪相互嚙合在一起,協同工作,來共同完成某項任務。我們可以看到,在齒輪組中,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的運轉。


???齒輪組中各個齒輪之間的嚙合關系,與軟件系統中對象與對象之間的耦合關系,非常類似。對象之間的耦合關系是必要的,是協同工作的基礎,當然也是無法避免的,否則無法保證系統整體的正常運轉。目前,很多工業級的應用越來越龐大,對象之間的依賴關系也越來越復雜,就會出現對象之間的多重依賴性關系,因此,架構師和設計師對系統進行分析和設計將面臨很大的挑戰。對象之間耦合度過高的系統,必然會出現牽一發而動全身的情形。

?

?
圖2:對象之間復雜的依賴關系


??? 耦合關系不僅會出現在對象與對象之間,也會出現在軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何降低系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。
??? 所以有人就提出來IOC理論,用來實現對象之間的“解耦”,目前已被廣泛應用于很多項目中。


2?什么是控制反轉(IoC)
??? IoC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”,還有些書籍翻譯成為“控制反向”或者“控制倒置”,這些都大同小異,我個人覺得這個翻譯有待商榷,容易引起歧義,是不是翻譯為 “控制轉移”會更好一些。
???1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IoC這個概念。對于面向對象設計及編程的基本思想,前面我們已經講了很多了,不再贅述,讀者可以參考我前面的文章,簡單來說就是把復雜系統分解成相互合作的對象,這些對象類的內部實現是透明的,從而降低了解決問題的復雜度,而且可以靈活地被重用和擴展。IoC理論提出的觀點大體是這樣的:借助于“第三方”實現具有依賴關系的對象之間的解耦,如下圖:

?


?圖3:IoC解耦示意圖


???大家看到了吧,由于引進了中間的“第三方”,也就是IoC容器,使得A、B、C、D這4個對象沒有了耦合關系,齒輪之間的傳動全部依靠“第三方”了,所有對象的控制權全部上繳給“第三方”,這就是“控制反轉”說法的由來,意思就是各個對象的控制權都被轉移給“第三方”了。
??? 從另一個角度來看,作為“第三方”的IoC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有對象粘合在一起發揮作用,如果沒有這個“粘合劑”,對象與對象之間會彼此失去聯系,這就是所謂的Ioc容器被稱為“粘合劑”的原因。
??? 我們再把上圖中間的Ioc容器拿掉后,整個系統變為這樣的情形:


圖4:拿掉IoC容器后的系統


???拿掉IoC容器后,我們看到的就是系統開發所需要完成的全部內容,這時候,A、B、C、D這4個對象之間已經沒有了耦合關系,彼此毫不影響,所以當你在實現ClassA的時候,根本不用再考慮B、C和D了,系統對象之間的依賴已經降低到了最低程度。至于IoC容器,你可以到開源組織的網站上找一找,里面有很多比較成熟而且Free的,使用起來非常簡便。
如果真能實現控制反轉,對于系統開發而言,這將是一件多么美好的事情!

?

3?什么是依賴注入(DI)
??? 我們先看一些生活中的例子,幫助你理解依賴注入(DI):

3.1?主機和內置硬盤
??? 我們平時所用的電腦,它的硬盤安裝在主機里面,從電腦的外部,我們是看不見硬盤的。所以,我們通常認為,電腦的所有部件是融為一體的。


圖5:主機和內置硬盤


???對于一體機而言,一旦出現了問題,我們可能無法準確地判斷到底是什么零部件出現了問題,有可能是CPU壞了,也有可能是主板燒了,還有可能是內存松動了。還有的時候,比如,電腦硬盤出現了問題,可能導致整臺電腦都無法使用。從這個例子,我們可以看到部件之間“緊密耦合”的產生的問題:無法準確的定位和診斷故障所在。這種情形,在軟件工程的理論中,稱之為可理解性和可測試性差。


??? 如果你想修理電腦的硬盤,那么在修理過程中就必須小心翼翼,不要把其它的部件再搞壞了,比如不慎把內存給碰松動了,硬盤固然是修好了,但整臺電腦仍然無法使用。這種情形,在軟件工程的理論中,稱之為可修改性差。
可理解性、可測試性、可修改性組成了系統的可維護性,一體機的可維護性就表現得比較差。

?

3.2?主機和USB設備
??? 大家對USB接口和設備應該都很熟悉。自從有了USB接口,給我們使用電腦帶來了很大的方便,現在有很多的外部設備都支持USB接口。


圖6:主機和USB設備

?

??? 從軟件工程角度,我們分析一下USB帶來的好處:
1、USB設備作為主機的外部設備,在插入主機之前,與主機沒有任何的關系,兩者都可獨立進行測試,無論兩者中的任何一方出現什么的問題,都不會影響另一方的運行,所以可維護性比較好。
2、同一個USB設備可以插接到不同的支持USB的任何主機,也就是USB設備可以被重復利用,所以可復用性比較好。
3、支持熱插拔,只要是支持USB接口的設備都可以接入,所以可擴展性比較好,非常靈活。


3.3?依賴注入
???2004年,MartinFowler從另一個角度來思考這個問題,提出了“哪些方面的控制被反轉了?”這樣一個問題,并給出了答案:“依賴對象的獲得被反轉”。于是,他給“控制反轉”取了一個他認為更合適的名字叫做“依賴注入(DependencyInjection)”。他的這個答案,實際上點明了實現IoC理論的解決方法。所謂依賴注入,就是由IoC容器在運行期間,動態地將某種依賴關系注入到對象之中。


??? 依賴注入(DI)和控制反轉(IoC)是從不同的角度的描述的同一件事情,都是指通過引入第三方,即IoC容器,實現軟件系統中對象之間的解耦。


??? 控制反轉能夠帶給系統開發的好處,與USB機制帶來的好處基本類似,而且依賴注入的實現跟USB機制也完全一樣。USB機制是現實中依賴注入的很好的案例。我們用一個實際的例子,分析一下USB機制:


任務:主機通過USB接口讀取一個文件。
思路:首先,必須制定一個USB接口標準,主機對USB設備的訪問嚴格按照USB接口標準,USB設備提供的功能也必須符合USB接口標準。
??? 當主機需要獲取一個文件的時候,它直接去讀取USB接口,根本不會關心USB接口上連接的是什么設備。
如果我給主機連接上一個U盤,那么主機就從U盤上讀取文件;如果我給主機連接上一個外接硬盤,那么主機就從外接硬盤上讀取文件。選取何種外部設備的權力由我說了算,也就是控制權歸我。
至此,依賴注入的思路已經非常清楚:當主機需要讀取文件的時候,我就把它所要依賴的外部設備,挑出來一個,幫他掛接上。這個過程就是一個被依賴的對象在系統運行時被注入另外一個對象內部的過程。在這個過程中,我就起到了IoC容器的作用。


??? 我們再把依賴注入應用到軟件工程中:
??? ClassA依賴于Class B,當Class A需要用到Class B的時候,IoC容器就會立刻創建一個Class B送給ClassA使用。IoC容器就是一個類制造工廠,你需要什么,直接來拿,直接用就可以了,而不需要去關心你所用的東西是如何制成的,也不用關心最后是怎么被銷毀的,這一切全由IoC容器包辦。

?

? 4?實現IoC容器

?----------------------------------------------------

后記:之所以突然跳躍到39,是因為有的同學基礎比較好,已經沒有必要閱讀有關面向對象、設計模式以及軟件工程的基本理論,那么可以從這里開始閱讀。基礎需要繼續補全的同學,可以從4繼續看,我會定期在兩個方向進行更新。框架理論,是架構師知識體系中非常重要的部分,我會逐步結合實例,把常見的一些框架方面的知識與大家共享。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/453573.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453573.shtml
英文地址,請注明出處:http://en.pswp.cn/news/453573.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

r語言做斷軸_R語言用nls做非線性回歸以及函數模型的參數估計

非線性回歸是在對變量的非線性關系有一定認識前提下&#xff0c;對非線性函數的參數進行最優化的過程&#xff0c;最優化后的參數會使得模型的RSS(殘差平方和)達到最小。在R語言中最為常用的非線性回歸建模函數是nls&#xff0c;下面以car包中的USPop數據集為例來講解其用法。數…

day8-異常處理與網絡編程

第1章 異常處理 1.1 什么是異常? 1.1.1 描述 #1 什么是異常&#xff1f; # 異常是錯誤發生的信號&#xff0c;一旦程序出錯&#xff0c;就會產生一個異常&#xff0c;應用程序未處理該異常&#xff0c; # 異常便會拋出&#xff0c;程序隨之終止 異常就是程序運行時發生錯誤的信…

常用數據結構的一部分類

VECTORvector是可以實現自動增長的對象數組。java.util.vector提供了向量類&#xff08;vector&#xff09;來實現向量數組的功能。在C和C中可以使用指針來實現動態數組&#xff0c;java通過提供大量的類庫來彌補這個功能。向量類的對象 可以向其中隨意插入不同類的對象&#x…

進程(并發,并行) join start 進程池 (同步異步)

一、背景知識 顧名思義&#xff0c;進程即正在執行的一個過程。進程是對正在運行程序的一個抽象。進程的概念起源于操作系統&#xff0c;是操作系統最核心的概念&#xff0c;也是操作系統提供的最古老也是最重要的抽象概念之一。操作系統的其他所有內容都是圍繞進程的概念展開的…

面對職業誘惑,我們如何作出理性的選擇?

版權聲明&#xff1a;原創作品&#xff0c;允許轉載&#xff0c;轉載時請務必以超鏈接形式標明文章原始出版、作者信息和本聲明。否則將追究法律責任。本文地址&#xff1a;http://blog.csdn.net/jobchanceleo/archive/2007/07/08/1682484.aspx 分享一個發生在我們身邊的案例&a…

xamarin怎么調用java的_XamarinSQLite教程在Xamarin.Android項目中使用數據庫

XamarinSQLite教程在Xamarin.Android項目中使用數據庫在Xamarin.Android項目中使用預設數據庫的具體操作步驟如下&#xff1a;(1)創建一個Xamarin.Android項目&#xff0c;如AndroidSQLiteDemo。(2)在AndroidSQLiteDemo項目的Resources文件夾下創建一個Raw文件夾。(3)將上一節中…

Selector的一些state使用

(一)Selector的基本狀態android:state_selected 控件選中狀態&#xff0c;可以為true或falseandroid:state_focused 控件獲得焦點狀態&#xff0c;可以為true或falseandroid:state_pressed 控件點擊狀態&#xff0c;可以為true或falseandroid:state_enabled 控件使能狀態&#…

服務框架及服務治理組件——業界調研

聲明&#xff1a;主要內容來自公司內部 對業界的調研,不一定恰當、準確、實時。 表格文字較多&#xff0c;APP閱讀體驗較差 團隊服務相關組件\方案通信框架監控負載均衡\路由是否開源騰訊完全自研&#xff1b;BG內部自治&#xff0c;每個BG有自己相應的解決方案&#xff0c;單獨…

在操作系統重啟后恢復應用程序的工作狀態

Windows 10 創意者更新之后&#xff0c;默認開啟了重啟后恢復應用程序狀態的功能。這是自 Vista 以來就提供的功能——Restart Manager。 應用程序實現這一功能只需要調用 RegisterApplicationRestart 即可。傳入兩個參數&#xff1a; 重啟后使用的命令行參數&#xff08;例如當…

裁員感悟

好員工&#xff0c;別以為裁員與你無關(上) 版權聲明&#xff1a;原創作品&#xff0c;允許轉載&#xff0c;轉載時請務必以超鏈接形式標明文章原始出版、作者信息和本聲明。否則將追究法律責任。本文地址&#xff1a;http://blog.csdn.net/jobchanceleo/archive/2007/05/26/…

php傳中文給Java_完美解決PHP中文亂碼(轉) - - JavaEye技術網站

PHP中文亂碼一般是字符集問題&#xff0c;編碼主要有下面幾個問題。一&#xff0e;首先是PHP網頁的編碼1.文件本身的編碼與網頁的編碼應匹配a.如果欲使用gb2312編碼&#xff0c;那么php要輸出頭&#xff1a;header(“Content-Type: text/html; charsetgb2312")&#xff0c…

CharSequence類

CharSequence是char類型的一個可讀序列&#xff0c;它本身是一個接口&#xff0c;CharBuffer、String、StringBuffer、StringBuilder這個四個類實現了這個接口。此接口對于不同種類的char序列提供統一的只讀訪問以下是這個函數的API 它只定義了四個方法 /*** This interface re…

程序員考核的五大死因

程序員考核的五大死因&#xff08;上&#xff09; 程序員作為企業開發力量的最核心資產&#xff0c;無疑得到公司從上至下的一致關注。開發是個智力密集型產業&#xff0c;程序開發的特點是&#xff0c;付出相同時間的情況下&#xff0c;兩個開發者之間的產能會相差十幾甚至幾…

java編寫螺旋矩陣講解_Java如何實現螺旋矩陣 Java實現螺旋矩陣代碼實例

本篇文章小編給大家分享一下Java實現螺旋矩陣代碼實例&#xff0c;小編覺得挺不錯的&#xff0c;現在分享給大家供大家參考&#xff0c;有需要的小伙伴們可以來看看。給定一個包含 m x n 個元素的矩陣(m 行, n 列)&#xff0c;請按照順時針螺旋順序&#xff0c;返回矩陣中的所有…

Vue Axios的配置 (高仿餓了么)

export default {name: "app",components: {"v-header": header},data() {return {seller: {}};},created() {let _this this; // 讓this始終代表最初this指向的對象this.axios.get(../data.json).then(function(res) {_this.seller res.data.sellercons…

PagerAdapter學習

前言: ViewGroup可以實現很多功能&#xff0c;如簡單的頁面導航和頁面滑動等等。谷歌公司為我們提供ViewGroup的API。谷歌公司推薦我們把ViewGroup和Fragment一起使,如果一起使用的話&#xff0c;應該使用FragmentPagerAdapter和FragmentStatePagerAdapter來進行適配處理&#…

arXiv網站

arXiv 原先是由物理學家保羅金斯巴格在1991年建立的網站&#xff0c; 我們會將預稿上傳到arvix作為預收錄&#xff0c;因此這就是個可以證明論文原創性&#xff08;上傳時間戳&#xff09;的文檔收錄網站。轉載于:https://www.cnblogs.com/AntonioSu/p/8387324.html

加薪——愛你在心口難開

加薪——愛你在心口難開(1) &#xff08;原文刊登于《程序員》雜志07年第4期&#xff09; 剛過了春節的4月份&#xff0c;空氣中到處透著躁動的味道&#xff0c;“求職”、“招聘”不斷刺激著程序員們的耳鼓&#xff0c;其實大多數跳槽者如果能靜下心來審視自己一下&#xf…

java線程interrupt用法_Java線程中interrupt那點事 | 學步園

1.先看一下例子程序&#xff1a;import java.io.IOException;import java.net.ServerSocket;import javax.rmi.CORBA.Tie;/**author: ZhengHaibo*web: http://blog.csdn.net/nuptboyzhb*mail: zhb931706659126.com*2014-3-16 Nanjing,njupt,China*/public class TestThread {/*…

Kotlin Native新增Objective-C互操作能力以及對WebAssembly的支持

根據JetBrains技術主管Nikolay Igotti的介紹&#xff0c;Kotlin/Native 0.4已經可用于為iOS和macOS開發原生應用。此外該版本還為WebAssembly平臺提供了實驗性支持。 \\Kotlin/Native對iOS/macOS開發的支持&#xff0c;關鍵在于實現了與Objective-C的互操作性。JetBrains目前已…