通常,繼承最終會以創建一系列類收場,所有類都建立在統一的接口基礎上。我們用一幅顛倒的樹形圖來闡明這一點(注釋⑤):
⑤:這兒采用了“統一記號法”,本書將主要采用這種方法。
對這樣的一系列類,我們要進行的一項重要處理就是將衍生類的對象當作基礎類的一個對象對待。這一點是非常重要的,因為它意味著我們只需編寫單一的代碼,令其忽略類型的特定細節,只與基礎類打交道。這樣一來,那些代碼就可與類型信息分開。所以更易編寫,也更易理解。此外,若通過繼承增添了一種新類型,如“三角形”,那麼我們為“幾何形狀”新類型編寫的代碼會象在舊類型裡一樣良好地工作。所以說程序具備了“擴展能力”,具有“擴展性”。
以上面的例子為基礎,假設我們用Java寫了這樣一個函數:
void doStuff(Shape s) { s.erase(); // ... s.draw(); }
這個函數可與任何“幾何形狀”(Shape)通信,所以完全獨立於它要描繪(draw)和刪除(erase)的任何特定類型的對象。如果我們在其他一些程序裡使用doStuff()函數:
Circle c = new Circle(); Triangle t = new Triangle(); Line l = new Line(); doStuff(c); doStuff(t); doStuff(l);
那麼對doStuff()的調用會自動良好地工作,無論對象的具體類型是什麼。
這實際是一個非常有用的編程技巧。請考慮下面這行代碼:
doStuff(c);
此時,一個Circle(圓)句柄傳遞給一個本來期待Shape(形狀)句柄的函數。由於圓是一種幾何形狀,所以doStuff()能正確地進行處理。也就是說,凡是doStuff()能發給一個Shape的消息,Circle也能接收。所以這樣做是安全的,不會造成錯誤。
我們將這種把衍生類型當作它的基本類型處理的過程叫作“Upcasting”(上溯造型)。其中,“cast”(造型)是指根據一個現成的模型創建;而“Up”(向上)表明繼承的方向是從“上面”來的——即基礎類位於頂部,而衍生類在下方展開。所以,根據基礎類進行造型就是一個從上面繼承的過程,即“Upcasting”。
在面向對象的程序裡,通常都要用到上溯造型技術。這是避免去調查准確類型的一個好辦法。請看看doStuff()裡的代碼:
s.erase();
// ...
s.draw();
注意它並未這樣表達:“如果你是一個Circle,就這樣做;如果你是一個Square,就那樣做;等等”。若那樣編寫代碼,就需檢查一個Shape所有可能的類型,如圓、矩形等等。這顯然是非常麻煩的,而且每次添加了一種新的Shape類型後,都要相應地進行修改。在這兒,我們只需說:“你是一種幾何形狀,我知道你能將自己刪掉,即erase();請自己采取那個行動,並自己去控制所有的細節吧。”