第一章 面向?qū)ο蟮母拍詈喗?/p>
軟件行業(yè)中,技術(shù)變遷非常快,而概念則是逐步演進。
概念是相對穩(wěn)定,但也在變化。同時,也經(jīng)常被重新反思,引發(fā)新的討論。
從 90 年代中后期至今,行業(yè)技術(shù)的發(fā)展都是在使用這些概念。
本書主要目標是,讓你學會思考如何將面向?qū)ο蟾拍顟糜诿嫦驅(qū)ο蟮南到y(tǒng)設計中。
1.1 基本概念
歷史上定義面向?qū)ο蟮恼Z言擁有以下特點:封裝(encapsulation)、繼承(inheritance)和多態(tài)(polymorphism)。
作者認為面向?qū)ο蟮母拍钊缦拢悍庋b(encapsulation)、繼承(inheritance)、多態(tài)(polymorphism)和組合。
1.2 對象及遺留系統(tǒng)
面向?qū)ο笈c結(jié)構(gòu)化編程絕不是互斥的。它們是互補的,因為對象可以與結(jié)構(gòu)化代碼很好地繼承。
1.3 過程式編程與面向?qū)ο缶幊?/p>
究竟什么是對象?
這既是一個復雜的問題,也是一個簡單的問題。它復雜是因為學習任何一種軟件開發(fā)方法論都非易事。它簡單是因為人們已經(jīng)在按對象的方式進行思考。
例如,當你看到一個人,你會把他看做一個對象。一個對象由兩部分組成:屬性及行為。
一個人具有屬性,比如眼睛顏色、年齡、身高等。一個人也有行為,比如行走、講話、呼吸等。
對象的基本定義是一個包含了數(shù)據(jù)和行為的實體。
在面向?qū)ο蟮脑O計中,屬性及行為包含在單個對象中,而在過程式或結(jié)構(gòu)式設計中,屬性和行為通常是分開的。
面向?qū)ο蟮臄?shù)據(jù)庫與關(guān)系型數(shù)據(jù)庫
結(jié)構(gòu)化編程中數(shù)據(jù)往往與程序分離,而且數(shù)據(jù)是全局的,所以在你的代碼作用域之外依然可以很容易修改數(shù)據(jù)。
對象不會完全替代其他的軟件開發(fā)范式,它是一種進化。
在面向?qū)ο蟮男g(shù)語中,數(shù)據(jù)表現(xiàn)為屬性,行為表現(xiàn)為方法。限制訪問具體屬性和(或)方法的行為叫做數(shù)據(jù)隱藏。
將屬性及方法合并到同一個實體中,叫做封裝(encapsulation)。
1.4 由面向過程開發(fā)過渡到面向?qū)ο箝_發(fā)
過程式編程通常會將系統(tǒng)的數(shù)據(jù)與對數(shù)據(jù)的操作分離開來。例如,通過網(wǎng)絡發(fā)送信息,只發(fā)送相關(guān)數(shù)據(jù)/數(shù)據(jù)包,而期望網(wǎng)絡管道另一端的程序知道如何處理該數(shù)據(jù)。換句話說,客戶端和服務器端要對數(shù)據(jù)傳輸建立起一種握手約定。
面向?qū)ο缶幊痰淖畲髢?yōu)勢是數(shù)據(jù)和對數(shù)據(jù)的操作(代碼)都被封裝在一個對象中。例如,當通過網(wǎng)絡傳輸對象時,整個對象(包括里面的數(shù)據(jù)和行為)都會一起被傳輸。
大多數(shù)情況下,行為本身不會被發(fā)送,因為兩端都有行為代碼的副本。
UML 類圖
可視化建模工具提供了一種方式來使用統(tǒng)一建模語言(unified modeling language, UML)創(chuàng)建和操作類圖。
類是創(chuàng)建對象的模板
1.6 究竟什么是類
類是對象的藍圖
以關(guān)系型數(shù)據(jù)庫為例,在數(shù)據(jù)表中,表自身的定義(即字段、描述和數(shù)據(jù)類型)是類(即元數(shù)據(jù)),而對象則是表中的行(即數(shù)據(jù))。
類定義了使用該類創(chuàng)建的所有對象具有的屬性和行為。類是一塊代碼。可以單獨分發(fā)使用類實例化的對象,也可以將類作為程序庫的一部分進行分發(fā)。因為對象從類中穿件,所以類必須定義對象的基礎(chǔ)材料(即屬性、行為和消息)。
1.8 封裝和數(shù)據(jù)隱藏
除了如何使用該對象,其他細節(jié)都應當對其他對象隱藏起來。
封裝是基于對象既包含屬性也包含行為。數(shù)據(jù)隱藏是封裝的主要部分。
接口定義了對象之間通信的基本手段。每個類設計接口規(guī)格來保證對象能被正確實例化和操作。必須向?qū)ο筇峁┑慕涌诎l(fā)送消息來使用對象暴露的任何行為。接口需要完整描述類與類之間的交互。
訪問修飾符指定為 public 的方法屬于接口。
類有接口,方法也有接口。類的接口是公共方法。你使用方法的簽名來調(diào)用這些公共方法。方法的簽名主要由方法名和它的參數(shù)列表組成。
Public class IntSquare {
// 私有屬性
private int squareValue;
// 公共接口
public int getSquare (int value) {
SquareValue = caculateSquare(value);
return SquareValue;
}
// 私有實現(xiàn)
private int calculateSquare (int value) {
return value * value;
}
}
1.9 繼承
繼承是實現(xiàn)代碼重用的主要手段。
繼承允許一個類繼承另一個類的屬性和方法。
我們可以通過抽象公共屬性和行為來創(chuàng)建新類。
面向?qū)ο蟪绦蛟O計中的一個主要設計問題就是識別多個類的共性。
超類和子類
超類,也成為父類(也叫基類),包含了繼承自它的所有類的公共屬性和行為。
子類,也稱為孩子類(也叫衍生類),是超類的擴展。
繼承的力量在于它的抽象和組織技術(shù)。
is-a 關(guān)系
在 Shape(形狀)例子中,Circle(圓形)、Square(矩形)和 Star(星形)都直接繼承自 Shape。這種關(guān)系通常被稱為 is-a 關(guān)系。
多態(tài)
當你告訴某人畫一個形狀時,你被問到的第一個問題是“什么形狀?”。沒人能繪制一個形狀,因為形狀是一個抽象概念(事實上 Shape 代碼中的 Draw() 方法并不包含實現(xiàn))。你必須制定一個具體的形狀。比如你需要為 Circle 提供具體的實現(xiàn)。雖然 Shape 有一個 Draw 方法,但 Circle 繼承了該方法并提供了對該方法的實現(xiàn)。
重載(overriding)的基本釋義是子類覆蓋父類中的一個實現(xiàn)。
每個類(子類)能夠?qū)Γǜ割惖模┩粋€方法返回不同的響應來繪制自己。這就是多態(tài)的意義。
如果子類繼承了父類的一個抽象方法,它必須提供該類方法的具體實現(xiàn),否則它自身也必須是個抽象類。
Circle circle = new Circle(5);
Rectangle rectangle = new Rectangle(4, 5);
stack.push(circle);
Stack.push(rectangle);
while (!stack.empty()) {
Shape shape = (Shape) stack.pop();
System.out.println("Area = " + shape.getArea());
}
1.11 組合(has-a)
對象包含其他對象非常自然。
使用其他對象來構(gòu)建或結(jié)合成新的對象,這種方式就是組合。
和繼承一樣,組合也是一種構(gòu)建對象的機制。
只有兩種方式來使用其他類構(gòu)建新類,這兩種方式就是繼承和組合。
繼承允許類繼承另一個類。我們可以把屬性和行為抽象到通用類中。例如,狗和貓都是哺乳公務,因為狗是(is-a)哺乳公務,貓也是(is-a)哺乳動物。而使用組合,可以把類嵌入其他類中來構(gòu)造新類。
單獨建模,如單獨構(gòu)建引擎,把它應用到各種車上,更別提還有其他優(yōu)勢。但我們不能說引擎是(is-a)一輛車。最好用 has-a 術(shù)語來描述組合關(guān)系。車有(has-a)引擎。
電視有(has-a)開關(guān)和顯示屏。
計算機有(has-a)顯卡,有(has-a)鍵盤,有(has-a)光驅(qū)。
電視顯而易見不是一個開關(guān),所以兩者沒有繼承關(guān)系。
繼承關(guān)系是 is-a 關(guān)系。
組合關(guān)系可以稱為 has-a 關(guān)系。
總結(jié):
封裝。把數(shù)據(jù)和行為封裝到單個對象中是面向?qū)ο箝_發(fā)中的重中之重。單個對象既包含自身的數(shù)據(jù),也包含自身的行為,并且可以向其他對象隱藏自身的某些東西。
繼承。類可以繼承自另一個類,并且可以使用父類中定義的屬性和方法。
多態(tài)。多態(tài)意味著相似的對象對相同的消息有著不同的響應。例如,你可能擁有一個有很多形狀的系統(tǒng)。然而圓、正方形和星形的繪制方式不同。使用多態(tài)你可以給這些形狀發(fā)送相同的消息(例如 Draw 方法),每個形狀可以響應自身的繪制。
組合。組合意味著可以使用其他對象來構(gòu)建新對象。