說明

從生活面的觀點來觀察時,當發現到兩個以上的類別有其相似之處(但又不盡相同),我們可以把相似之處抽象(abstract)放在更高層次的一般性類別。例如,觀察「貓」與「狗」兩個類別,是否有可能抽象化成為一般性的類別? 兩者的品種完全不同,但其實也存在著某種程度的相似性,事實上,若我們要開發一個 “寵物店管理系統”,那麼,其實很自然,就可以將此兩個類別抽象成為「寵物」這個一般化的類別。相對來說,只要是符合「寵物」一般化類別共同特性的其它類別,包括可愛、能取悅、陪伴主人等特徵與行為,就可以成為「寵物」的特殊化類別。一般化—特殊化關係的 UML 表示法如下圖。

 

範例、一般化—特殊化的 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 表示法

 

範例—範例—車子的一般化—特殊化 UML 表示
圖2、範例—範例—車子的一般化—特殊化 UML 表示

卡車、坦克車、悍馬車,都是屬於 “車子” 的特殊化類別。特殊化類別均具有 “車子” 的共同特性,但也可以 “修正” 與 “擴展” 一般化類別的行為。以上圖為例,卡車可以 “超載(override)” “車子” 的 “get車況” 這個操作;同時又 “擴展(extend)” 了原來 “車子” 所沒有的行為: “裝載物資”。 事實上,當從一般化類別的角度來看時,設計人員是因為需要 “修正(也就是超載)” 與 “擴展” 原來類別的行為時,才有必要設計 “特殊化類別”。

再來從 “汽車工廠” 的角度來觀察時,它提供了 “列出所有車況” 這個操作給外界來使用(有可能是部隊指揮官想知道部隊內所有各類型車子的車況),請注意,”汽車工廠” 是將所有車子的類型,包括卡車、坦克車、悍馬車…等,均當作 “一般性車子” 來看待,因為,這些特殊軍用車輛,仍是具備了 “車子” 的共同特性,所以並不需要讓外界個別來宣告與存取這些特殊化類別,只要透過 “車子” 的 “get車況” 操作,然後才在動態的期間,來呼叫這些實體(instance)物件的 “get車況” 操作。如此封裝了這些特殊化類別的變動設計(你不知道哪時候又會增加了 “奘甲飛彈車” 這種新的特殊化類別設計),卻又能動態存取這些特殊化類別的操作,這就是 “多型(polymorphism)” 的效果!

※延伸參考:

類別之間的關係(Relationship) — 整體-局部(Whole-parts) (2)

類別之間的關係(Relationship) — 結合(Association)關係 (1)

淺論「類別(Class)是什麼?」

淺論「什麼是物件(Object)?」

 

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 }

執行結果如下:

我是一般化車子...
我是卡車,可以裝載很多物資...
我是悍馬車,機動靈活也可以發射火箭炮...
我是坦克車,可以發射大砲...