說明
從生活面的觀點來觀察時,當發現到兩個以上的類別有其相似之處(但又不盡相同),我們可以把相似之處抽象(abstract)放在更高層次的一般性類別。例如,觀察「貓」與「狗」兩個類別,是否有可能抽象化成為一般性的類別? 兩者的品種完全不同,但其實也存在著某種程度的相似性,事實上,若我們要開發一個 “寵物店管理系統”,那麼,其實很自然,就可以將此兩個類別抽象成為「寵物」這個一般化的類別。相對來說,只要是符合「寵物」一般化類別共同特性的其它類別,包括可愛、能取悅、陪伴主人等特徵與行為,就可以成為「寵物」的特殊化類別。一般化—特殊化關係的 UML 表示法如下圖。
圖1、範例 一般化—特殊化的 UML 表示法
概念上,所有貓、狗的個體(instances)從定義來看也都是「寵物」的個體,那麼就可以將「貓」、「狗」等視為是「寵物」的子型態(sub-type)。由此可以看出,「貓」是一種特別的「寵物」。其中一個重要的觀念在於,所有與「寵物」有關係的特徵(features),包括關連、屬性(attributes)與操作(operations),對「貓」、「狗」、「烏龜」來說也都是成立的。
從軟體的觀點來看,對一般化關係的具體實作就是 “繼承(inheritance)” : 「貓」是「寵物」的子類別(sub-class),在主流的 OOP 語言,包括 Java 與 .NET 等,子類別會繼承超類別(super-class)的所有特性,而且還可以覆寫(override)任何超類別的方法(method)。
一般程式設計人員最容易誤解物件導向的繼承觀念就在於,以為繼承是被用來 “可重用(re-use)”的:可重用既有的程式碼。其實繼承的最重要原則是在於 “可被替代性(substitutability)”,而這正是物件導向另一個非常重要的思維– “多型(polymorphism)”:讓外界(Client)能以「一視同仁」的角度來看待多個特殊化類別所抽象出的一般化類別!
關於 "多型" ,留待在在進階主題內再行詳述。請記得,多型同時也是解決軟體複雜度的一個重要原則。
範例—車子的一般化—特殊化類別
UML 表示法
圖2、範例—範例—車子的一般化—特殊化 UML 表示
卡車、坦克車、悍馬車,都是屬於 “車子” 的特殊化類別。特殊化類別均具有 “車子” 的共同特性,但也可以 “修正” 與 “擴展” 一般化類別的行為。以上圖為例,卡車可以 “超載(override)” “車子” 的 “get車況” 這個操作;同時又 “擴展(extend)” 了原來 “車子” 所沒有的行為: “裝載物資”。 事實上,當從一般化類別的角度來看時,設計人員是因為需要 “修正(也就是超載)” 與 “擴展” 原來類別的行為時,才有必要設計 “特殊化類別”。
再來從 “汽車工廠” 的角度來觀察時,它提供了 “列出所有車況” 這個操作給外界來使用(有可能是部隊指揮官想知道部隊內所有各類型車子的車況),請注意,”汽車工廠” 是將所有車子的類型,包括卡車、坦克車、悍馬車…等,均當作 “一般性車子” 來看待,因為,這些特殊軍用車輛,仍是具備了 “車子” 的共同特性,所以並不需要讓外界個別來宣告與存取這些特殊化類別,只要透過 “車子” 的 “get車況” 操作,然後才在動態的期間,來呼叫這些實體(instance)物件的 “get車況” 操作。如此封裝了這些特殊化類別的變動設計(你不知道哪時候又會增加了 “奘甲飛彈車” 這種新的特殊化類別設計),卻又能動態存取這些特殊化類別的操作,這就是 “多型(polymorphism)” 的效果!
※延伸參考:
「類別之間的關係(Relationship) — 整體-局部(Whole-parts) (2)」
「類別之間的關係(Relationship) — 結合(Association)關係 (1)」
Java 程式範例碼
#001 public class 車子 { #002 public String get車況(){ #003 return "我是一般化車子..."; #004 } #005 } #001 public class 卡車 extends 車子 { #002 public String get車況(){ #003 return "我是卡車,可以裝載很多物資..."; #004 } #005 public String 裝載物資(String 物資){ #006 return ""; #007 } #008 } #001 public class 坦克車 extends 車子 { #002 public String get車況(){ #003 return "我是坦克車,可以發射大砲..."; #004 } #005 #006 public void 發射大砲(){ #007 } #008 } #001 public class 悍馬車 extends 車子 { #002 public String get車況(){ #003 return "我是悍馬車,機動靈活也可以發射火箭炮..."; #004 } #005 public void 發射火箭炮(){ #006 } #007 } #001 import java.util.*; #002 public class 汽車工廠 { #003 private ArrayList list = new ArrayList(); #004 #005 public 汽車工廠(){ #006 list.add(製造(0)); #007 list.add(製造(1)); #008 list.add(製造(2)); #009 list.add(製造(3)); #010 } #011 #012 public String 列出所有車子(){ #013 String 車子資訊=""; #014 Iterator e = list.iterator(); #015 while (e.hasNext()) #016 車子資訊 = 車子資訊 + ((車子)e.next()).get車況() + "\n"; #017 return 車子資訊; #018 } #019 #020 private 車子 製造(int 型別){ #021 switch(型別){ #022 case 0: return new 車子(); #023 case 1: return new 卡車(); #024 case 2: return new 悍馬車(); #025 case 3: return new 坦克車(); #026 default: return new 車子(); #027 } #028 } #029 }
這裡設計一個測試用的 Java stand-alone 的程式: TestCar,以站在 “汽車工廠r” 的角度,來看看如何取得工廠內所有車子的車況:
#001 public class TestCar { #002 public static void main(String[] args) { #003 汽車工廠 factory = new 汽車工廠(); #004 #005 System.out.println(factory.列出所有車子()); #006 } #007 }
執行結果如下:
我是一般化車子... 我是卡車,可以裝載很多物資... 我是悍馬車,機動靈活也可以發射火箭炮... 我是坦克車,可以發射大砲...