在Java當中,你還可以定義抽象類別(Abstract Class)。表面上看來,定義類別時僅宣告方法名稱而不實作當中的邏輯,因為該方法定義不完整,這樣的方法稱之為抽象方法(Abstract Method),如果一個類別中包括了抽象方法,因為該類別定義不完整,所以該類別稱之為抽象類別。在 繼承了什麼? 中看過一個基本的例子。
然而事實上,不管類別是否標示為abstract,定義類別,本身就是在進行抽象化,到底什麼稱之為抽象?「象」這個字眼最直接的描述,就是代表著「事物的輪廓」,你看到一隻麻雀、一隻鴿子、一隻雞,你會知道它們都是鳥,因為從它們形態的輪廓上,你可以歸納(抽)出一些共同的表象(象)。有些共同表象看得到具體的行為,但有些不是。
所以如何使用抽象類別,其中一個方式就是在如何為你所定義的類別歸納出一些共同的表象,並發掘出其中無法看出具體行為的部份。單純地以一隻麻雀、一隻鴿子、一隻雞,假設你所看到的共同表象是一對翅膀、一對腳、會振翅,因而抽取出來定義為鳥類所應共同具有的特性:
public abstract class Bird {
private int leg;
private int wing;
public abstract void flutter();
}
private int leg;
private int wing;
public abstract void flutter();
}
這是抽象類別的基本使用方式之一。
「象」這個字眼也可以進一步描述為「流程的輸廓」,不僅僅是「表象」,也就是這個事物在進行某個行為時,整個流程的大致順序。舉個例子來說,猜數字遊戲的流程大致就是:
顯示訊息(歡迎)
隨機產生數字
遊戲迴圈
顯示訊息(提示使用者輸入)
取得使用者輸入
比較是否猜中
顯示訊息(輸入正確與否)
隨機產生數字
遊戲迴圈
顯示訊息(提示使用者輸入)
取得使用者輸入
比較是否猜中
顯示訊息(輸入正確與否)
在描述流程輸廓時,並沒有提及如何顯示訊息、沒有提及如何取得使用者輸入等具體的作法,只是歸納出一些共同的流程步驟。
public abstract class GuessGame {
protected String welcome;
protected String prompt;
protected String correct;
protected String bigger;
protected String smaller;
public void go() {
message(welcome);
int number = (int) (Math.random() * 10);
int guess = 0;
do {
message(prompt);
guess = guess();
if(guess > number) {
message(bigger);
}
else if(guess < number) {
message(smaller);
}
} while(guess != number);
message(correct);
}
protected abstract void message(String message);
protected abstract int guess();
}
protected String welcome;
protected String prompt;
protected String correct;
protected String bigger;
protected String smaller;
public void go() {
message(welcome);
int number = (int) (Math.random() * 10);
int guess = 0;
do {
message(prompt);
guess = guess();
if(guess > number) {
message(bigger);
}
else if(guess < number) {
message(smaller);
}
} while(guess != number);
message(correct);
}
protected abstract void message(String message);
protected abstract int guess();
}
如果是個文字模式下的猜數字遊戲,可以將顯示訊息、取得使用者輸入等以文字模式下的具體作法實現出來。例如:
import java.util.Scanner;
public class ConsoleGame extends GuessGame {
private Scanner scanner;
public ConsoleGame() {
welcome = "歡迎";
prompt = "輸入";
correct = "猜中了";
bigger = "你猜的比較大";
smaller = "你猜的比較小";
scanner = new Scanner(System.in);
}
protected void message(String msg) {
System.out.println(msg);
}
protected int guess() {
return scanner.nextInt();
}
}
public class ConsoleGame extends GuessGame {
private Scanner scanner;
public ConsoleGame() {
welcome = "歡迎";
prompt = "輸入";
correct = "猜中了";
bigger = "你猜的比較大";
smaller = "你猜的比較小";
scanner = new Scanner(System.in);
}
protected void message(String msg) {
System.out.println(msg);
}
protected int guess() {
return scanner.nextInt();
}
}
你可以這麼使用:
GuessGame game = new ConsoleGame();
game.go();
game.go();
抽象還可以再更進一步,不僅是流程的抽象,還可以是所使用物件的抽象。舉個例子來說,你正在設計一個文字編輯器的框架,實際上文字編輯器會開啟的格式並不知道,但基本文件處理的幾個抽象流程打算事先定義下來:
public abstract class Editor {
private Document document;
public abstract Document createDocument();
public void newDoc() {
document = createDocument();
document.show();
}
public void saveDoc() {
if(document != null)
document.save();
}
public void closeDoc() {
if(document != null)
document.close();
}
}
private Document document;
public abstract Document createDocument();
public void newDoc() {
document = createDocument();
document.show();
}
public void saveDoc() {
if(document != null)
document.save();
}
public void closeDoc() {
if(document != null)
document.close();
}
}
public interface Document {
void show();
void save();
void close();
}
void show();
void save();
void close();
}
這樣的作法,是將產生實際Document的責任推遲至子類別來實現,你僅抽象地定義父類別的流程與Document的行為(後者利用到介面來定義就是了)。也許你會設計一個XML編輯器,在實現createDocument()方具體指定所產生的Document物件:
public class XMLEditor extends Editor {
public Document createDocument() {
return new Document() {
public void show() { /* XML 文件顯示.... */ }
public void save() { /* XML 文件儲存.... */ }
public void close() { /* XML 文件關閉.... */ }
};
}
}
public Document createDocument() {
return new Document() {
public void show() { /* XML 文件顯示.... */ }
public void save() { /* XML 文件儲存.... */ }
public void close() { /* XML 文件關閉.... */ }
};
}
}
抽象所代表的是抽取出「事物的輪廓」,你如何決定輪廓就決定了你如何定義抽象,或者更具體而言,如何使用抽象類別。