到底哪裡抽象了?


在Java當中,你還可以定義抽象類別(Abstract Class)。表面上看來,定義類別時僅宣告方法名稱而不實作當中的邏輯,因為該方法定義不完整,這樣的方法稱之為抽象方法(Abstract Method),如果一個類別中包括了抽象方法,因為該類別定義不完整,所以該類別稱之為抽象類別。在 繼承了什麼? 中看過一個基本的例子。

然而事實上,不管類別是否標示為abstract,定義類別,本身就是在進行抽象化,到底什麼稱之為抽象?「象」這個字眼最直接的描述,就是代表著「事物的輪廓」,你看到一隻麻雀、一隻鴿子、一隻雞,你會知道它們都是鳥,因為從它們形態的輪廓上,你可以歸納(抽)出一些共同的表象(象)。有些共同表象看得到具體的行為,但有些不是。

所以如何使用抽象類別,其中一個方式就是在如何為你所定義的類別歸納出一些共同的表象,並發掘出其中無法看出具體行為的部份。單純地以一隻麻雀、一隻鴿子、一隻雞,假設你所看到的共同表象是一對翅膀、一對腳、會振翅,因而抽取出來定義為鳥類所應共同具有的特性:
public abstract class Bird {
    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();
}


如果是個文字模式下的猜數字遊戲,可以將顯示訊息、取得使用者輸入等以文字模式下的具體作法實現出來。例如:
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();
    }
}

你可以這麼使用:
GuessGame game = new ConsoleGame();
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();
    }
}

public interface Document {
    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 文件關閉.... */ }
               };
    }
}

抽象所代表的是抽取出「事物的輪廓」,你如何決定輪廓就決定了你如何定義抽象,或者更具體而言,如何使用抽象類別。