從技術角度說
OOP(面向對象程序設計)只是涉及抽象的數據類型
繼承以及多形性
但另一些問題也可能顯得非常重要
本節將就這些問題進行探討
最重要的問題之一是對象的創建及破壞方式
對象需要的數據位於哪兒
如何控制對象的
存在時間
呢?針對這個問題
解決的方案是各異其趣的
C++認為程序的執行效率是最重要的一個問題
所以它允許程序員作出選擇
為獲得最快的運行速度
存儲以及存在時間可在編寫程序時決定
只需將對象放置在堆棧(有時也叫作自動或定域變量)或者靜態存儲區域即可
這樣便為存儲空間的分配和釋放提供了一個優先級
某些情況下
這種優先級的控制是非常有價值的
然而
我們同時也犧牲了靈活性
因為在編寫程序時
必須知道對象的准確的數量
存在時間
以及類型
如果要解決的是一個較常規的問題
如計算機輔助設計
倉儲管理或者空中交通控制
這一方法就顯得太局限了
第二個方法是在一個內存池中動態創建對象
該內存池亦叫
堆
或者
內存堆
若采用這種方式
除非進入運行期
否則根本不知道到底需要多少個對象
也不知道它們的存在時間有多長
以及准確的類型是什麼
這些參數都在程序正式運行時才決定的
若需一個新對象
只需在需要它的時候在內存堆裡簡單地創建它即可
由於存儲空間的管理是運行期間動態進行的
所以在內存堆裡分配存儲空間的時間比在堆棧裡創建的時間長得多(在堆棧裡創建存儲空間一般只需要一個簡單的指令
將堆棧指針向下或向下移動即可)
由於動態創建方法使對象本來就傾向於復雜
所以查找存儲空間以及釋放它所需的額外開銷不會為對象的創建造成明顯的影響
除此以外
更大的靈活性對於常規編程問題的解決是至關重要的
C++允許我們決定是在寫程序時創建對象
還是在運行期間創建
這種控制方法更加靈活
大家或許認為既然它如此靈活
那麼無論如何都應在內存堆裡創建對象
而不是在堆棧中創建
但還要考慮另外一個問題
亦即對象的
存在時間
或者
生存時間
(Lifetime)
若在堆棧或者靜態存儲空間裡創建一個對象
編譯器會判斷對象的持續時間有多長
到時會自動
破壞
或者
清除
它
程序員可用兩種方法來破壞一個對象
用程序化的方式決定何時破壞對象
或者利用由運行環境提供的一種
垃圾收集器
特性
自動尋找那些不再使用的對象
並將其清除
當然
垃圾收集器顯得方便得多
但要求所有應用程序都必須容忍垃圾收集器的存在
並能默許隨垃圾收集帶來的額外開銷
但這並不符合C++語言的設計宗旨
所以未能包括到C++裡
但Java確實提供了一個垃圾收集器(Smalltalk也有這樣的設計
盡管Delphi默認為沒有垃圾收集器
但可選擇安裝
而C++亦可使用一些由其他公司開發的垃圾收集產品)
本節剩下的部分將討論操縱對象時要考慮的另一些因素
集合與繼承器 針對一個特定問題的解決
如果事先不知道需要多少個對象
或者它們的持續時間有多長
那麼也不知道如何保存那些對象
既然如此
怎樣才能知道那些對象要求多少空間呢?事先上根本無法提前知道
除非進入運行期
在面向對象的設計中
大多數問題的解決辦法似乎都有些輕率——只是簡單地創建另一種類型的對象
用於解決特定問題的新型對象容納了指向其他對象的句柄
當然
也可以用數組來做同樣的事情
那是大多數語言都具有的一種功能
但不能只看到這一點
這種新對象通常叫作
集合
(亦叫作一個
容器
但AWT在不同的場合應用了這個術語
所以本書將一直沿用
集合
的稱呼
在需要的時候
集合會自動擴充自己
以便適應我們在其中置入的任何東西
所以我們事先不必知道要在一個集合裡容下多少東西
只需創建一個集合
以後的工作讓它自己負責好了
幸運的是
設計優良的OOP語言都配套提供了一系列集合
在C++中
它們是以
標准模板庫
(STL)的形式提供的
Object Pascal用自己的
可視組件庫
(VCL)提供集合
Smalltalk提供了一套非常完整的集合
而Java也用自己的標准庫提供了集合
在某些庫中
一個常規集合便可滿足人們的大多數要求
而在另一些庫中(特別是C++的庫)
則面向不同的需求提供了不同類型的集合
例如
可以用一個矢量統一對所有元素的訪問方式
一個鏈接列表則用於保證所有元素的插入統一
所以我們能根據自己的需要選擇適當的類型
其中包括集
隊列
散列表
樹
堆棧等等
所有集合都提供了相應的讀寫功能
將某樣東西置入集合時
采用的方式是十分明顯的
有一個叫作
推
(Push)
添加
(Add)或其他類似名字的函數用於做這件事情
但將數據從集合中取出的時候
方式卻並不總是那麼明顯
如果是一個數組形式的實體
比如一個矢量(Vector)
那麼也許能用索引運算符或函數
但在許多情況下
這樣做往往會無功而返
此外
單選定函數的功能是非常有限的
如果想對集合中的一系列元素進行操縱或比較
而不是僅僅面向一個
這時又該怎麼辦呢?
辦法就是使用一個
繼續器
(Iterator)
它屬於一種對象
負責選擇集合內的元素
並把它們提供給繼承器的用戶
作為一個類
它也提供了一級抽象
利用這一級抽象
可將集合細節與用於訪問那個集合的代碼隔離開
通過繼承器的作用
集合被抽象成一個簡單的序列
繼承器允許我們遍歷那個序列
同時毋需關心基礎結構是什麼——換言之
不管它是一個矢量
一個鏈接列表
一個堆棧
還是其他什麼東西
這樣一來
我們就可以靈活地改變基礎數據
不會對程序裡的代碼造成干擾
Java最開始(在
和
版中)提供的是一個標准繼承器
名為Enumeration(枚舉)
為它的所有集合類提供服務
Java
新增一個更復雜的集合庫
其中包含了一個名為Iterator的繼承器
可以做比老式的Enumeration更多的事情
從設計角度出發
我們需要的是一個全功能的序列
通過對它的操縱
應該能解決自己的問題
如果一種類型的序列即可滿足我們的所有要求
那麼完全沒有必要再換用不同的類型
有兩方面的原因促使我們需要對集合作出選擇
首先
集合提供了不同的接口類型以及外部行為
堆棧的接口與行為與隊列的不同
而隊列的接口與行為又與一個集(Set)或列表的不同
利用這個特征
我們解決問題時便有更大的靈活性
其次
不同的集合在進行特定操作時往往有不同的效率
最好的例子便是矢量(Vector)和列表(List)的區別
它們都屬於簡單的序列
擁有完全一致的接口和外部行為
但在執行一些特定的任務時
需要的開銷卻是完全不同的
對矢量內的元素進行的隨機訪問(存取)是一種常時操作
無論我們選擇的選擇是什麼
需要的時間量都是相同的
但在一個鏈接列表中
若想到處移動
並隨機挑選一個元素
就需付出
慘重
的代價
而且假設某個元素位於列表較遠的地方
找到它所需的時間也會長許多
但在另一方面
如果想在序列中部插入一個元素
用列表就比用矢量劃算得多
這些以及其他操作都有不同的執行效率
具體取決於序列的基礎結構是什麼
在設計階段
我們可以先從一個列表開始
最後調整性能的時候
再根據情況把它換成矢量
由於抽象是通過繼承器進行的
所以能在兩者方便地切換
對代碼的影響則顯得微不足道
最後
記住集合只是一個用來放置對象的儲藏所
如果那個儲藏所能滿足我們的所有需要
就完全沒必要關心它具體是如何實現的(這是大多數類型對象的一個基本概念)
如果在一個編程環境中工作
它由於其他因素(比如在Windows下運行
或者由垃圾收集器帶來了開銷)產生了內在的開銷
那麼矢量和鏈接列表之間在系統開銷上的差異就或許不是一個大問題
我們可能只需要一種類型的序列
甚至可以想象有一個
完美
的集合抽象
它能根據自己的使用方式自動改變基層的實現方式
單根結構 在面向對象的程序設計中
由於C++的引入而顯得尤為突出的一個問題是
所有類最終是否都應從單獨一個基礎類繼承
在Java中(與其他幾乎所有OOP語言一樣)
對這個問題的答案都是肯定的
而且這個終級基礎類的名字很簡單
就是一個
Object
這種
單根結構
具有許多方面的優點
單根結構中的所有對象都有一個通用接口
所以它們最終都屬於相同的類型
另一種方案(就象C++那樣)是我們不能保證所有東西都屬於相同的基本類型
從向後兼容的角度看
這一方案可與C模型更好地配合
而且可以認為它的限制更少一些
但假期我們想進行純粹的面向對象編程
那麼必須構建自己的結構
以期獲得與內建到其他OOP語言裡的同樣的便利
需添加我們要用到的各種新類庫
還要使用另一些不兼容的接口
理所當然地
這也需要付出額外的精力使新接口與自己的設計方案配合(可能還需要多重繼承)
為得到C++額外的
靈活性
付出這樣的代價值得嗎?當然
如果真的需要——如果早已是C專家
如果對C有難捨的情結——那麼就真的很值得
但假如你是一名新手
首次接觸這類設計
象Java那樣的替換方案也許會更省事一些
單根結構中的所有對象(比如所有Java對象)都可以保證擁有一些特定的功能
在自己的系統中
我們知道對每個對象都能進行一些基本操作
一個單根結構
加上所有對象都在內存堆中創建
可以極大簡化參數的傳遞(這在C++裡是一個復雜的概念)
利用單根結構
我們可以更方便地實現一個垃圾收集器
與此有關的必要支持可安裝於基礎類中
而垃圾收集器可將適當的消息發給系統內的任何對象
如果沒有這種單根結構
而且系統通過一個句柄來操縱對象
那麼實現垃圾收集器的途徑會有很大的不同
而且會面臨許多障礙
由於運行期的類型信息肯定存在於所有對象中
所以永遠不會遇到判斷不出一個對象的類型的情況
這對系統級的操作來說顯得特別重要
比如違例控制
而且也能在程序設計時獲得更大的靈活性
但大家也可能產生疑問
既然你把好處說得這麼天花亂墜
為什麼C++沒有采用單根結構呢?事實上
這是早
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25640.html