知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價值,我們在追求其視覺表現(xiàn)的同時,更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!
《Thinking In Java》讀書筆記004_對象入門
發(fā)表時間:2020-10-18
發(fā)布人:葵宇科技
瀏覽次數(shù):53
對象入門
1.1 抽象的進(jìn)步
所有編程語言的最終目的都是提供一種“抽象”方法。一種較有爭議的說法是:解決問題的復(fù)雜程度直接取決于抽象的種類及質(zhì)量。這兒的“種類”是指準(zhǔn)備對什么進(jìn)行“抽象”?匯編語言是對基礎(chǔ)機(jī)器的少量抽象。后來的許多“命令式”語言(如FORTRAN,BASIC和C)是對匯編語言的一種抽象。與匯編語言相比,這些語言已有了長足的進(jìn)步,但它們的抽象原理依然要求我們著重考慮計算機(jī)的結(jié)構(gòu),而非考慮問題本身的結(jié)構(gòu)。在機(jī)器模型(位于“方案空間”)與實際解決的問題模型(位于“問題空間”)之間,程序員必須建立起一種聯(lián)系。這個過程要求人們付出較大的精力,而且由于它脫離了編程語言本身的范圍,造成程序代碼很難編寫,而且要花較大的代價進(jìn)行維護(hù)。由此造成的副作用便是一門完善的“編程方法”學(xué)科。
Alan Kay總結(jié)了Smalltalk的五大基本特征。這是第一種成功的面向?qū)ο蟪绦蛟O(shè)計語言,也是Java的基礎(chǔ)語言。通過這些特征,我們可理解“純粹”的面向?qū)ο蟪绦蛟O(shè)計方法是什么樣的:
(1) 所有東西都是對象??蓪ο笙胂蟪梢环N新型變量;它保存著數(shù)據(jù),但可要求它對自身進(jìn)行操作。理論上講,可從要解決的問題身上提出所有概念性的組件,然后在程序中將其表達(dá)為一個對象。
(2) 程序是一大堆對象的組合;通過消息傳遞,各對象知道自己該做些什么。為了向?qū)ο蟀l(fā)出請求,需向那個對象“發(fā)送一條消息”。更具體地講,可將消息想象為一個調(diào)用請求,它調(diào)用的是從屬于目標(biāo)對象的一個子例程或函數(shù)。
(3) 每個對象都有自己的存儲空間,可容納其他對象?;蛘哒f,通過封裝現(xiàn)有對象,可制作出新型對象。所以,盡管對象的概念非常簡單,但在程序中卻可達(dá)到任意高的復(fù)雜程度。
(4) 每個對象都有一種類型。根據(jù)語法,每個對象都是某個“類”的一個“實例”。其中,“類”(Class)是“類型”(Type)的同義詞。一個類最重要的特征就是“能將什么消息發(fā)給它?”。
(5) 同一類所有對象都能接收相同的消息。這實際是別有含義的一種說法,大家不久便能理解。由于類型為“圓”(Circle)的一個對象也屬于類型為“形狀”(Shape)的一個對象,所以一個圓完全能接收形狀消息。這意味著可讓程序代碼統(tǒng)一指揮“形狀”,令其自動控制所有符合“形狀”描述的對象,其中自然包括“圓”。這一特性稱為對象的“可替換性”,是OOP最重要的概念之一。
1.2 對象的接口
1.3 實現(xiàn)方案的隱藏
為方便后面的討論,讓我們先對這一領(lǐng)域的從業(yè)人員作一下分類。從根本上說,大致有兩方面的人員涉足面向?qū)ο蟮木幊?#xff1a;“類創(chuàng)建者”(創(chuàng)建新數(shù)據(jù)類型的人)以及“客戶程序員”(在自己的應(yīng)用程序中采用現(xiàn)成數(shù)據(jù)類型的人;注釋④)。對客戶程序員來講,最主要的目標(biāo)就是收集一個充斥著各種類的編程“工具箱”,以便快速開發(fā)符合自己要求的應(yīng)用。而對類創(chuàng)建者來說,他們的目標(biāo)則是從頭構(gòu)建一個類,只向客戶程序員開放有必要開放的東西(接口),其他所有細(xì)節(jié)都隱藏起來。為什么要這樣做?隱藏之后,客戶程序員就不能接觸和改變那些細(xì)節(jié),所以原創(chuàng)者不用擔(dān)心自己的作品會受到非法修改,可確保它們不會對其他人造成影響。
“接口”(Interface)規(guī)定了可對一個特定的對象發(fā)出哪些請求。然而,必須在某個地方存在著一些代碼,以便滿足這些請求。這些代碼與那些隱藏起來的數(shù)據(jù)便叫作“隱藏的實現(xiàn)”。站在程式化程序編寫(Procedural Programming)的角度,整個問題并不顯得復(fù)雜。一種類型含有與每種可能的請求關(guān)聯(lián)起來的函數(shù)。一旦向?qū)ο蟀l(fā)出一個特定的請求,就會調(diào)用那個函數(shù)。我們通常將這個過程總結(jié)為向?qū)ο蟆?strong>發(fā)送一條消息”(提出一個請求)。對象的職責(zé)就是決定如何對這條消息作出反應(yīng)(執(zhí)行相應(yīng)的代碼)。
有兩方面的原因促使我們控制對成員的訪問。第一個原因是防止程序員接觸他們不該接觸的東西——通常是內(nèi)部數(shù)據(jù)類型的設(shè)計思想。若只是為了解決特定的問題,用戶只需操作接口即可,毋需明白這些信息。我們向用戶提供的實際是一種服務(wù),因為他們很容易就可看出哪些對自己非常重要,以及哪些可忽略不計。
進(jìn)行訪問控制的第二個原因是允許庫設(shè)計人員修改內(nèi)部結(jié)構(gòu),不用擔(dān)心它會對客戶程序員造成什么影響。例如,我們最開始可能設(shè)計了一個形式簡單的類,以便簡化開發(fā)。以后又決定進(jìn)行改寫,使其更快地運(yùn)行。若接口與實現(xiàn)方法早已隔離開,并分別受到保護(hù),就可放心做到這一點,只要求用戶重新鏈接一下即可。
Java采用三個顯式(明確)關(guān)鍵字以及一個隱式(暗示)關(guān)鍵字來設(shè)置類邊界:public,private,protected以及暗示性的friendly。若未明確指定其他關(guān)鍵字,則默認(rèn)為后者。這些關(guān)鍵字的使用和含義都是相當(dāng)直觀的,它們決定了誰能使用后續(xù)的定義內(nèi)容?!皃ublic”(公共)意味著后續(xù)的定義任何人均可使用。而在另一方面,“private”(私有)意味著除您自己、類型的創(chuàng)建者以及那個類型的內(nèi)部函數(shù)成員,其他任何人都不能訪問后續(xù)的定義信息。private在您與客戶程序員之間豎起了一堵墻。若有人試圖訪問私有成員,就會得到一個編譯期錯誤?!癴riendly”(友好的)涉及“包裝”或“封裝”(Package)的概念——即Java用來構(gòu)建庫的方法。若某樣?xùn)|西是“友好的”,意味著它只能在這個包裝的范圍內(nèi)使用(所以這一訪問級別有時也叫作“包裝訪問”)?!皃rotected”(受保護(hù)的)與“private”相似,只是一個繼承的類可訪問受保護(hù)的成員,但不能訪問私有成員。繼承的問題不久就要談到。
1.4 方案的重復(fù)使用
為重復(fù)使用一個類,最簡單的辦法是僅直接使用那個類的對象。但同時也能將那個類的一個對象置入一個新類。我們把這叫作“創(chuàng)建一個成員對象”。新類可由任意數(shù)量和類型的其他對象構(gòu)成。無論如何,只要新類達(dá)到了設(shè)計要求即可。這個概念叫作“組織”——在現(xiàn)有類的基礎(chǔ)上組織一個新類。有時,我們也將組織稱作“包含”關(guān)系,比如“一輛車包含了一個變速箱”。
對象的組織具有極大的靈活性。新類的“成員對象”通常設(shè)為“私有”(Private),使用這個類的客戶程序員不能訪問它們。這樣一來,我們可在不干擾客戶代碼的前提下,從容地修改那些成員。也可以在“運(yùn)行期”更改成員,這進(jìn)一步增大了靈活性。后面要講到的“繼承”并不具備這種靈活性,因為編譯器必須對通過繼承創(chuàng)建的類加以限制。
由于繼承的重要性,所以在面向?qū)ο蟮某绦蛟O(shè)計中,它經(jīng)常被重點強(qiáng)調(diào)。作為新加入這一領(lǐng)域的程序員,或許早已先入為主地認(rèn)為“繼承應(yīng)當(dāng)隨處可見”。沿這種思路產(chǎn)生的設(shè)計將是非常笨拙的,會大大增加程序的復(fù)雜程度。相反,新建類的時候,首先應(yīng)考慮“組織”對象;這樣做顯得更加簡單和靈活。利用對象的組織,我們的設(shè)計可保持清爽。一旦需要用到繼承,就會明顯意識到這一點。
1.5 繼承:重新使用接口
1.6 多形對象的互換使用
通常,繼承最終會以創(chuàng)建一系列類收場,所有類都建立在統(tǒng)一的接口基礎(chǔ)上。
通常,繼承最終會以創(chuàng)建一系列類收場,所有類都建立在統(tǒng)一的接口基礎(chǔ)上。我們用一幅顛倒的樹形圖來闡明這一點(注釋⑤):
⑤:這兒采用了“統(tǒng)一記號法”,本書將主要采用這種方法。
對這樣的一系列類,我們要進(jìn)行的一項重要處理就是將衍生類的對象當(dāng)作基礎(chǔ)類的一個對象對待。這一點是非常重要的,因為它意味著我們只需編寫單一的代碼,令其忽略類型的特定細(xì)節(jié),只與基礎(chǔ)類打交道。這樣一來,那些代碼就可與類型信息分開。所以更易編寫,也更易理解。此外,若通過繼承增添了一種新類型,如“三角形”,那么我們?yōu)椤皫缀涡螤睢毙骂愋途帉懙拇a會象在舊類型里一樣良好地工作。所以說程序具備了“擴(kuò)展能力”,具有“擴(kuò)展性”。
以上面的例子為基礎(chǔ),假設(shè)我們用Java寫了這樣一個函數(shù):
void doStuff(Shape s) {
s.erase();
// …
s.draw();
}
這個函數(shù)可與任何“幾何形狀”(Shape)通信,所以完全獨立于它要描繪(draw)和刪除(erase)的任何特定類型的對象。如果我們在其他一些程序里使用doStuff()函數(shù):
Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff?;
doStuff(t);
doStuff(l);
那么對doStuff()的調(diào)用會自動良好地工作,無論對象的具體類型是什么。
這實際是一個非常有用的編程技巧。請考慮下面這行代碼:
doStuff?;
此時,一個Circle(圓)句柄傳遞給一個本來期待Shape(形狀)句柄的函數(shù)。由于圓是一種幾何形狀,所以doStuff()能正確地進(jìn)行處理。也就是說,凡是doStuff()能發(fā)給一個Shape的消息,Circle也能接收。所以這樣做是安全的,不會造成錯誤。
我們將這種把衍生類型當(dāng)作它的基本類型處理的過程叫作“Upcasting”(上溯造型)。其中,“cast”(造型)是指根據(jù)一個現(xiàn)成的模型創(chuàng)建;而“Up”(向上)表明繼承的方向是從“上面”來的——即基礎(chǔ)類位于頂部,而衍生類在下方展開。所以,根據(jù)基礎(chǔ)類進(jìn)行造型就是一個從上面繼承的過程,即“Upcasting”。
在面向?qū)ο蟮某绦蚶?#xff0c;通常都要用到上溯造型技術(shù)。這是避免去調(diào)查準(zhǔn)確類型的一個好辦法。請看看doStuff()里的代碼:
s.erase();
// …
s.draw();
注意它并未這樣表達(dá):“如果你是一個Circle,就這樣做;如果你是一個Square,就那樣做;等等”。若那樣編寫代碼,就需檢查一個Shape所有可能的類型,如圓、矩形等等。這顯然是非常麻煩的,而且每次添加了一種新的Shape類型后,都要相應(yīng)地進(jìn)行修改。在這兒,我們只需說:“你是一種幾何形狀,我知道你能將自己刪掉,即erase();請自己采取那個行動,并自己去控制所有的細(xì)節(jié)吧。
1.6.1 動態(tài)綁定
在doStuff()的代碼里,最讓人吃驚的是盡管我們沒作出任何特殊指示,采取的操作也是完全正確和恰當(dāng)?shù)摹N覀冎?#xff0c;為Circle調(diào)用draw()時執(zhí)行的代碼與為一個Square或Line調(diào)用draw()時執(zhí)行的代碼是不同的。但在將draw()消息發(fā)給一個匿名Shape時,根據(jù)Shape句柄當(dāng)時連接的實際類型,會相應(yīng)地采取正確的操作。這當(dāng)然令人驚訝,因為當(dāng)Java編譯器為doStuff()編譯代碼時,它并不知道自己要操作的準(zhǔn)確類型是什么。盡管我們確實可以保證最終會為Shape調(diào)用erase(),為Shape調(diào)用draw(),但并不能保證為特定的Circle,Square或者Line調(diào)用什么。然而最后采取的操作同樣是正確的,這是怎么做到的呢?
將一條消息發(fā)給對象時,如果并不知道對方的具體類型是什么,但采取的行動同樣是正確的,這種情況就叫作“多形性”(Polymorphism)。對面向?qū)ο蟮某绦蛟O(shè)計語言來說,它們用以實現(xiàn)多形性的方法叫作“動態(tài)綁定”。編譯器和運(yùn)行期系統(tǒng)會負(fù)責(zé)對所有細(xì)節(jié)的控制;我們只需知道會發(fā)生什么事情,而且更重要的是,如何利用它幫助自己設(shè)計程序。
有些語言要求我們用一個特殊的關(guān)鍵字來允許動態(tài)綁定。在C++中,這個關(guān)鍵字是virtual。在Java中,我們則完全不必記住添加一個關(guān)鍵字,因為函數(shù)的動態(tài)綁定是自動進(jìn)行的。所以在將一條消息發(fā)給對象時,我們完全可以肯定對象會采取正確的行動,即使其中涉及上溯造型之類的處理。
1.6.2 抽象的基礎(chǔ)類和接口
設(shè)計程序時,我們經(jīng)常都希望基礎(chǔ)類只為自己的衍生類提供一個接口。也就是說,我們不想其他任何人實際創(chuàng)建基礎(chǔ)類的一個對象,只對上溯造型成它,以便使用它們的接口。為達(dá)到這個目的,需要把那個類變成“抽象”的——使用abstract關(guān)鍵字。若有人試圖創(chuàng)建抽象類的一個對象,編譯器就會阻止他們。這種工具可有效強(qiáng)制實行一種特殊的設(shè)計。
亦可用abstract關(guān)鍵字描述一個尚未實現(xiàn)的方法——作為一個“根”使用,指出:“這是適用于從這個類繼承的所有類型的一個接口函數(shù),但目前尚沒有對它進(jìn)行任何形式的實現(xiàn)。”抽象方法也許只能在一個抽象類里創(chuàng)建。繼承了一個類后,那個方法就必須實現(xiàn),否則繼承的類也會變成“抽象”類。通過創(chuàng)建一個抽象方法,我們可以將一個方法置入接口中,不必再為那個方法提供可能毫無意義的主體代碼。
interface(接口)關(guān)鍵字將抽象類的概念更延伸了一步,它完全禁止了所有的函數(shù)定義。“接口”是一種相當(dāng)有效和常用的工具。另外如果自己愿意,亦可將多個接口都合并到一起(不能從多個普通class或abstract class中繼承)。
發(fā)現(xiàn)《Thinking In Java》第四版中文版的翻譯有很多錯誤或者說一些專業(yè)名詞翻譯很別扭,加上原作者有點嘮叨,看著頭大,所以換成了原作者的《On Java 8》進(jìn)行學(xué)習(xí)。
相關(guān)案例查看更多
相關(guān)閱讀
- 云南網(wǎng)站制作
- 網(wǎng)站建設(shè)
- 網(wǎng)站建設(shè)開發(fā)
- 云南小程序代建
- 昆明網(wǎng)站設(shè)計
- 云南網(wǎng)站建設(shè)特性
- 小程序模板開發(fā)公司
- 云南軟件公司
- 云南網(wǎng)站建設(shè)服務(wù)公司
- 網(wǎng)站建設(shè)招商
- 百度小程序
- asp網(wǎng)站
- 大理網(wǎng)站建設(shè)公司
- 汽車報廢管理
- 網(wǎng)絡(luò)公司
- 汽車報廢管理系統(tǒng)
- 云南小程序開發(fā)費(fèi)用
- 云南網(wǎng)站建設(shè)專業(yè)品牌
- 搜索引擎優(yōu)化
- 網(wǎng)站建設(shè)電話
- 云南網(wǎng)站建設(shè)優(yōu)化
- 云南etc小程序
- 微信小程序開發(fā)入門課程
- 小程序被攻擊
- 網(wǎng)站建設(shè)公司地址
- 保山小程序開發(fā)
- 網(wǎng)站維護(hù)
- 退款
- 小程序開發(fā)費(fèi)用
- 商標(biāo)