1.抽象的進步
所有編程語言的最終目的都是提供一種“抽象”方法。
匯編語言是對基礎機器的少量抽象。后來的許多“命令式”語言(如FORTRAN,BASIC 和C)是對匯編語言的一種抽象。
與匯編語言相比,這些語言已有了長足的進步,但它們的抽象原理依然要求我們著重考慮計算機的結構,而非考慮問題本身的結構。在機器模型(位于“方案空間”)與實際解決的問題模型(位于“問題空間”)之間,程序員必須建立起一種聯(lián)系。這個過程要求人們付出較大的精力,而且由于它脫離了編程語言本身的范圍,造成程序代碼很難編寫,而且要花較大的代價進行維護。
為機器建模的另一個方法是為要解決的問題制作模型。對一些早期語言來說,如LISP 和APL,它們的做法是“從不同的角度觀察世界”——“所有問題都歸納為列表”或“所有問題都歸納為算法”。PROLOG 則將所有問題都歸納為決策鏈。對于這些語言,我們認為它們一部分是面向基于“強制”的編程,另一部分則是專為處理圖形符號設計的。每種方法都有自己特殊的用途,適合解決某一類的問題。
面向對象的程序設計在此基礎上則跨出了一大步,程序員可利用一些工具表達問題空間內的元素。由于這種表達非常普遍,所以不必受限于特定類型的問題。我們將問題空間中的元素以及它們在方案空間的表示物稱作“對象”(Object)。當然,還有一些在問題空間沒有對應體的其他對象。通過添加新的對象類型,程序可進行靈活的調整,以便與特定的問題配合。所以在閱讀方案的描述代碼時,會讀到對問題進行表達的話語。與我們以前見過的相比,這無疑是一種更加靈活、更加強大的語言抽象方法??傊?,OOP 允許我們根據問題來描述問題,而不是根據方案。然而,仍有一個聯(lián)系途徑回到計算機。每個對象都類似一臺小計算機;它們有自己的狀態(tài),而且可要求它們進行特定的操作。與現(xiàn)實世界的“對象”或者“物體”相比,編程“對象”與它們也存在共通的地方:它們都有自己的特征和行為。
2.對象的接口
當我們進行面向對象的程序設計時,面臨的最大一項挑戰(zhàn)性就是:如何在“問題空間”(問題實際存在的地方)的元素與“方案空間”(對實際問題進行建模的地方,如計算機)的元素之間建立理想的“一對一”對應或映射關系。
向對象發(fā)出的請求是通過它的“接口”(Interface)定義的,對象的“類型”或“類”則規(guī)定了它的接口形式?!邦愋汀迸c“接口”的等價或對應關系是面向對象程序設計的基礎。
3.實現(xiàn)方案的隱藏
對類創(chuàng)建者來說,他們的目標則是從頭構建一個類,只向客戶程序員開放有必要開放的東西(接口),其他所有細節(jié)都隱藏起來。為什么要這樣做?隱藏之后,客戶程序員就不能接觸和改變那些細節(jié),所以原創(chuàng)者不用擔心自己的作品會受到非法修改,可確保它們不會對其他人造成影響。
“接口”(Interface)規(guī)定了可對一個特定的對象發(fā)出哪些請求。然而,必須在某個地方存在著一些代碼,以便滿足這些請求。這些代碼與那些隱藏起來的數(shù)據便叫作“隱藏的實現(xiàn)”。一種類型含有與每種可能的請求關聯(lián)起來的
函數(shù)。一旦向對象發(fā)出一個特定的請求,就會調用那個函數(shù)。我們通常將這個過程總結為向對象“發(fā)送一條消息”(提出一個請求)。對象的職責就是決定如何對這條消息作出反應(執(zhí)行相應的代碼)。
有兩方面的原因促使我們控制對成員的訪問。第一個原因是防止程序員接觸他們不該接觸的東西——通常是內部數(shù)據類型的設計思想。進行訪問控制的第二個原因是允許庫設計人員修改內部結構,不用擔心它會對客戶程序員造成什么影響。
相關知識點:public private protected friendly
4.方案的重復使用
新建類的時候,首先應考慮“組織”對象;這樣做顯得更加簡單和靈活。利用對象的組織,我們的設計可保持清爽。
繼承。
5. 繼承:重新使用接口
衍生類具有與基礎類相同的類型!為真正理解面向對象程序設計的含義,首先必須認識到這種類型的等價關系。
有兩種做法可將新得的衍生類與原來的基礎類區(qū)分開。第一種做法十分簡單:為衍生類添加新函數(shù)(功能)
為區(qū)分我們的新類,第二個辦法是改變基礎類一個現(xiàn)有函數(shù)的行為。我們將其稱作“改善”那個函數(shù)。
6. 多態(tài)對象的互換使用
將這種把衍生類型當作它的基本類型處理的過程叫作“Upcasting”(向上轉型)。
將一條消息發(fā)給對象時,如果并不知道對方的具體類型是什么,但采取的行動同樣是正確的,這種情況就叫作“多態(tài)性”(Polymorphism)。
設計程序時,我們經常都希望基礎類只為自己的衍生類提供一個接口。也就是說,我們不想其他任何人實際創(chuàng)建基礎類的一個對象,只向上轉型成它,以便使用它們的接口。為達到這個目的,需要把那個類變成“抽象”的——使用abstract 關鍵字。
亦可用abstract 關鍵字描述一個尚未實現(xiàn)的方法——作為一個“根”使用。
7.對象的創(chuàng)建和存在時間
從技術角度說,OOP(面向對象程序設計)只是涉及抽象的數(shù)據類型、繼承以及多態(tài)性,但另一些問題也可能顯得非常重要。
最重要的問題之一是對象的創(chuàng)建及銷毀方式。
另外一個問題,亦即對象的“存在時間”或者“生存時間”(Lifetime)。
7.1 集合與繼承器
針對一個特定問題的解決,如果事先不知道需要多少個對象,或者它們的持續(xù)時間有多長,那么也不知道如何保存那些對象。
在需要的時候,集合會自動擴充自己,以便適應我們在其中置入的任何東西。所以我們事先不必知道要在一個集合里容下多少東西。只需創(chuàng)建一個集合,以后的工作讓它自己負責好了。
由于抽象是通過繼承器進行的,所以能在兩者方便地切換,對代碼的影響則顯得微不足道。
7.2 單根結構
所有類最終是否都應從單獨一個基礎類繼承。
終級基礎類的名字很簡單,就是一個“Object”。
單根結構中的所有對象(比如所有Java 對象)都可以保證擁有一些特定的功能。在自己的系統(tǒng)中,我們知道對每個對象都能進行一些基本操作。一個單根結構,加上所有對象都在內存堆中創(chuàng)建,可以極大簡化參數(shù)的傳遞(這在C++里是一個復雜的概念)。
利用單根結構,我們可以更方便地實現(xiàn)一個垃圾收集器。與此有關的必要支持可安裝于基礎類中,而垃圾收集器可將適當?shù)南l(fā)給系統(tǒng)內的任何對象。如果沒有這種單根結構,而且系統(tǒng)通過一個句柄來操縱對象,那么實現(xiàn)垃圾收集器的途徑會有很大的不同,而且會面臨許多障礙。
由于運行期的類型信息肯定存在于所有對象中,所以永遠不會遇到判斷不出一個對象的類型的情況。這對系統(tǒng)級的操作來說顯得特別重要,比如違例控制;而且也能在程序設計時獲得更大的靈活性。
7.3 集合庫與方便使用集合
單根結構意味著、所有東西歸根結底都是一個Object!所以容納了Object 的一個集合實際可以容納任何東西。這使我們對它的重復使用變得非常簡便。
但由于集合只能容納Object,所以在我們向集合里添加對象句柄時,它會上溯造型成Object,這樣便丟失了它的身份或者標識信息。再次使用它的時候,會得到一個Object 句柄,而非指向我們早先置入的那個類型的句柄。所以怎樣才能歸還它的本來面貌,調用早先置入集合的那個對象的有用接口呢?
使用“向下轉型”(Downcasting)。假如向下轉型成錯誤的東西,會得到我們稱為“異?!保‥xception)的一種運行期錯誤。但在從一個集合提取對象句柄時,必須用某種方式準確地記住它們是什么,以保證向下轉型的正確進行。
能不能創(chuàng)建一個“智能”集合,令其知道自己容納的類型呢?
可以采用“參數(shù)化類型”,它們是編譯器能自動定制的類,可與特定的類型配合。在C++中,用于實現(xiàn)參數(shù)化類型的關鍵字是template(模板)。Java 目前尚未提供參數(shù)化類型,因為由于使用的是單根結構,所以使用它顯得有些笨拙。
涉及到泛型編程。
7.4 清除時的困境:由誰負責清除?
垃圾收集器“知道”一個對象在什么時候不再使用,然后會自動釋放那個對象占據的內存空間。采用這種方式,另外加上所有對象都從單個根類Object 繼承的事實,而且由于我們只能在內存堆中以一種方式創(chuàng)建對象,所以Java 的編程要比C++的編程簡單得多。我們只需要作出少量的抉擇,即可克服原先存在的大量障礙。
8.異??刂疲航鉀Q錯誤
由于采用的是獨立的執(zhí)行路徑,所以不會干擾我們的常規(guī)執(zhí)行代碼。這樣便使代碼的編寫變得更加簡單,因為不必經常性強制檢查代碼。除此以外,拋出的一個異常不同于從函數(shù)返回的錯誤值,也不同于由函數(shù)設置的一個標志。那些錯誤值或標志的作用是指示一個錯誤狀態(tài),是可以忽略的。但異常不能被忽略,所以肯定能在某個地方得到處置。最后,利用異常能夠可靠地從一個糟糕的環(huán)境中恢復。此時一般不需要退出,我們可以采取某些處理,恢復程序的正常執(zhí)行。顯然,這樣編制出來的程序顯得更加可靠。
在Java 中,異??刂颇K是從一開始就封裝好的,所以必須使用它!
9.多線程
Java 的多線程機制已內建到語言中,這使一個可能較復雜的問題變得簡單起來。對多線程處理的支持是在對象這一級支持的,所以一個執(zhí)行線程可表達為一個對象。Java 也提供了有限的資源鎖定方案。
10.持久化
可以將信息寫入一個文件或者數(shù)據庫,從而達到相同的效果。
11.J a v a 和因特網
11.1 C/S架構
這里要注意的一個主要問題是單個服務器需要同時向多個客戶提供服務。在這一機制中,通常少不了一套數(shù)據庫管理系統(tǒng),使設計人員能將數(shù)據布局封裝到表格中,以獲得最優(yōu)的使用。
除此以外,系統(tǒng)經常允許客戶將新信息插入一個服務器。這意味著必須確保客戶的新數(shù)據不會與其他客戶的新數(shù)據沖突,或者說需要保證那些數(shù)據在加入數(shù)據庫的時候不會丟失(用數(shù)據庫的術語來說,這叫作“事務處理”)。
客戶軟件發(fā)生了改變之后,它們必須在客戶機器上構建、調試以及安裝。所有這些會使問題變得比我們一般想象的復雜得多。
另外,對多種類型的計算機和操作系統(tǒng)的支持也是一個大問題。
最后,性能的問題顯得尤為重要:可能會有數(shù)百個客戶同時向服務器發(fā)出請求。所以任何微小的延誤都是不能忽視的。為盡可能緩解潛伏的問題,程序員需要謹慎地分散任務的處理負擔。一般可以考慮讓客戶機負擔部分處理任務,但有時亦可分派給服務器所在地的其他機器,那些機器亦叫作“中間件”(中間件也用于改進對系統(tǒng)的維護)。
Web 實際就是一套規(guī)模巨大的客戶機/服務器系統(tǒng)。
11.2 客戶端編程
Web 最初采用的“服務器-瀏覽器”方案可提供交互式內容,但這種交互能力完全由服務器提供,為服務器和因特網帶來了不小的負擔。服務器一般為客戶瀏覽器產生靜態(tài)網頁,由后者簡單地解釋并顯示出來?;綡TML 語言提供了簡單的數(shù)據收集機制:文字輸入框、復選框、單選鈕、列表以及下拉列表等,另外還有一個按鈕,只能由程序規(guī)定重新設置表單中的數(shù)據,以便回傳給服務器。用戶提交的信息通過所有Web 服務器均能支持的“通用網關接口”(CGI)回傳到服務器。包含在提交數(shù)據中的文字指示CGI 該如何操作。
今天的許多Web 站點都嚴格地建立在CGI 的基礎上,事實上幾乎所有事情都可用CGI 做到。唯一的問題就是響應時間。CGI 程序的響應取決于需要傳送多少數(shù)據,以及服務器和因特網兩方面的負擔有多重(而且CGI程序的啟動比較慢)。這種方法不僅速度非常慢,也顯得非常繁瑣。
解決的辦法就是客戶端的程序設計。運行Web 瀏覽器的大多數(shù)機器都擁有足夠強的能力,可進行其他大量工作。與此同時,原始的靜態(tài)HTML 方法仍然可以采用,它會一直等到服務器送回下一個頁??蛻舳司幊桃馕吨鳺eb 瀏覽器可獲得更充分的利用,并可有效改善Web 服務器的交互(互動)能力。
11.2.1 插件
利用插件,程序員可以方便地為瀏覽器添加新功能,用戶只需下載一些代碼,把它們“插入”瀏覽器的適當位置即可。
11.2.2 腳本語言
通過這種腳本語言,可將用于自己客戶端程序的源碼直接插入HTML頁,而對那種語言進行解釋的插件會在HTML 頁顯示的時候自動激活。
腳本語言真正面向的是特定類型問題的解決,其中主要涉及如何創(chuàng)建更豐富、更具有互動能力的圖形用戶界面(GUI)。然而,腳本語言也許能解決客戶端編程中80%的問題。你碰到的問題可能完全就在那80%里面。而且由于腳本編制語言的宗旨是盡可能地簡化與快速,所以在考慮其他更復雜的方案之前(如Java 及ActiveX),首先應想一下腳本語言是否可行。
目前討論得最多的腳本編制語言包括JavaScript(它與Java 沒有任何關系;之所以叫那個名字,完全是一種市場策略)、VBScript(同Visual Basic 很相似)以及Tcl/Tk(來源于流行的跨平臺GUI 構造語言)。
11.2.3 Java
Java 通過(Applet)巧妙地解決了客戶端編程的問題。
11.2.4 ActiveX
在某種程度上,Java 的一個有力競爭對手應該是微軟的ActiveX,盡管它采用的是完全不同的一套實現(xiàn)機制。ActiveX 最早是一種純Windows 的方案。經過一家獨立的專業(yè)協(xié)會的努力,ActiveX 現(xiàn)在已具備了跨平臺使用的能力。實際上,ActiveX 的意思是“假如你的程序同它的工作環(huán)境正常連接,它就能進入Web 頁,并在支持ActiveX 的瀏覽器中運行。
11.2.5 安全
目前解決的辦法是“數(shù)字簽名”,代碼會得到權威機構的驗證,顯示出它的作者是誰。但我對它能消除惡意因素持懷疑態(tài)度,因為假如一個程序便含有Bug,那么同樣會造成問題。
Java 通過“沙箱”來防止這些問題的發(fā)生。
11.2.6 因特網和內聯(lián)網
Web 是解決客戶機/服務器問題的一種常用方案,所以最好能用相同的技術解決此類問題的一些“子集”,特別是公司內部的傳統(tǒng)客戶機/服務器問題。
11.3 服務器端編程
如果向服務器發(fā)出一個請求,會發(fā)生什么事情?大多數(shù)時候的請求都是很簡單的一個“把這個文件發(fā)給我”。瀏覽器隨后會按適當?shù)男问浇忉屵@個文件:作為HTML 頁、一幅圖、一個Java 程序片、一個腳本程序等等。向服務器發(fā)出的較復雜的請求通常涉及到對一個數(shù)據庫進行操作(事務處理)。其中最常見的就是發(fā)出一個數(shù)據庫檢索命令,得到結果后,服務器會把它格式化成HTML頁,并作為結果傳回來。
另外,有時需要在數(shù)據庫中注冊自己的名字(比如加入一個組時),或者向服務器發(fā)出一份訂單,這就涉及到對那個數(shù)據庫的修改。這類服務器請求必須通過服務器端的一些代碼進行,我們稱其為“服務器端的編程”。在傳統(tǒng)意義上,服務器端編程是用Perl 和CGI 腳本進行的,但更復雜的系統(tǒng)已經出現(xiàn)。其中包括基于Java 的Web 服務器,它允許我們用Java 進行所有服務器端編程,寫出的程序就叫作“小服務程序”(Servlet)。
11.4 一個獨立的領域:應用程序
12.分析和設計
12.1 不要迷失
時刻提醒自己注意以下幾個問題:
(1) 對象是什么?(怎樣將自己的項目分割成一系列單獨的組件?)
(2) 它們的接口是什么?(需要將什么消息發(fā)給每一個對象?)
12.2 階段0 :擬出一個計劃
決定在后面的過程中采取哪些步驟。
在整個過程中設置幾個標志,或者“路標”,將更有益于你集中注意力。這恐怕比單純地為了“完成工作”而工作好得多。至少,在達到了一個又一個的目標,經過了一個接一個的路標以后,可對自己的進度有清晰的把握,干勁也會相應地提高,不會產生“路遙漫漫無期”的感覺。
12.3 階段1 :要制作什么?
在上一代程序設計中(即“過程化或程序化設計”),這個階段稱為“建立需求分析和系統(tǒng)規(guī)格”。
需求分析的意思是“建立一系列規(guī)則,根據它判斷任務什么時候完成,以及客戶怎樣才能滿意”。系統(tǒng)規(guī)格則表示“這里是一些具體的說明,讓你知道程序需要做什么(而不是怎樣做)才能滿足要求”。需求分析實際就是你和客戶之間的一份合約(即使客戶就在本公司內部工作,或者是其他對象及系統(tǒng))。系統(tǒng)規(guī)格是對所面臨問題的最高級別的一種揭示,我們依據它判斷任務是否完成,以及需要花多長的時間。由于這些都需要取得參與者的一致同意,所以我建議盡可能地簡化它們——最好采用列表和基本圖表的形式——以節(jié)省時間。可能還會面臨另一些限制,需要把它們擴充成為更大的文檔。
我們特別要注意將重點放在這一階段的核心問題上,不要糾纏于細枝末節(jié)。
應盡可能總結出自己系統(tǒng)的一套完整的“使用條件”或者“應用場合”。一旦完成這個工作,就相當于摸清了想讓系統(tǒng)完成的核心任務。
在這一階段,最好用幾個簡單的段落對自己的系統(tǒng)作出描述,然后圍繞它們再進行擴充,添加一些“名詞”和“動詞”?!懊~”自然成為對象,而“動詞”自然成為要整合到對象接口中的“方法”
12.4 階段2 :如何構建?
在這一階段,必須拿出一套設計方案,并解釋其中包含的各類對象在外觀上是什么樣子,以及相互間是如何溝通的。此時可考慮采用一種特殊的圖表工具:“統(tǒng)一建模語言”(UML)。
作出了對對象以及它們的接口的說明后,就完成了第2 階段的工作。當然,這些工作可能并不完全。
12.5 階段3 :開始創(chuàng)建
由于手頭上有一個計劃——無論它有多么簡要,而且在正式編碼前掌握了正確的設計結構,所以會發(fā)現(xiàn)接下去的工作比一開始就埋頭寫程序要簡單得多。而這正是我們想達到的目的。讓代碼做到我們想做的事情,這是所有程序項目最終的目標。
全面的思考、周密的準備、良好的構造不僅使程序更易構建與調試,也使其更易理解和維護,而那正是一套軟件贏利的必要條件。
12.6 階段4 :校訂
事實上,整個開發(fā)周期還沒有結束,現(xiàn)在進入的是傳統(tǒng)意義上稱為“維護”的一個階段。
什么時候才叫“達到理想的狀態(tài)”呢?這并不僅僅意味著程序必須按要求的那樣工作,并能適應各種指定的“使用條件”,它也意味著代碼的內部結構應當盡善盡美。至少,我們應能感覺出整個結構都能良好地協(xié)調運作。沒有笨拙的語法,沒有臃腫的對象,也沒有一些華而不實的東西。除此以外,必須保證程序結構有很強的生命力。由于多方面的原因,以后對程序的改動是必不可少。但必須確定改動能夠方便和清楚地進行。這里沒有花巧可言。不僅需要理解自己構建的是什么,也要理解程序如何不斷地進化。
12.7 計劃的回報
不管制訂出的計劃有多么小,但與完全沒有計劃相比,一些形式的計劃會極大改善你的項目。