與計算機學院研究生探討 Java 時,他們拋出的問題幾乎全圍繞著「介面」:介面有什麼用?為什麼要用介面?何時該使用介面?幸好他們沒問如何連接 SQL Server 或開發 J2EE 應用,這類問題殺傷力太大,避之則吉。記得當年本科畢業設計有 J2ME 題目,選這題的同學到 5 月底還在苦研 java.util.* 套件,令人唏噓。
許多人認為介面的存在是為了彌補 Java 缺乏 C++ 風格多重繼承的缺憾。但這種理解其實相當牽強——介面與繼承是完全不同的兩個概念,介面既無能力也無義務取代多重繼承。介面的核心價值用一句話概括就是:標識類的類別。將不同類型的類歸屬到不同介面,能實現更有效的管理。物件導向的精髓在於對物件的抽象,而介面正是這一精髓的最佳體現。我們之所以只在具備抽象能力的語言(C++、Java、C# 等)中討論設計模式,正是因為設計模式研究的核心就是如何合理地進行抽象(牛仔名言:「抽象就是抽去像的部分」,看似調侃,實乃至理)。
設計模式中最基礎的是工廠模式。在一個簡單的應用中,我需要讓程式能在多個資料庫間移植,單是兼容不同 DBMS 的 SQL 就讓人頭大。先簡化問題:只考慮如何連接不同資料庫。
假設有 Mysql.java、SQLServer.java、Oracle.java、DB2.java 等類,分別連接不同資料庫,統一返回 Connection 物件並提供 close() 方法。用戶會用什麼資料庫我不知道,但希望極少修改代碼就能滿足需求。此時可抽象出介面:
package org.bromon.test;
public interface DB {
java.sql.Connection openDB(String url, String user, String password);
void close();
}
介面僅定義兩個方法,無實際代碼,具體實現由實作類完成,例如 Mysql.java:
package org.bromon.test;
import java.sql.*;
public class Mysql implements DB {
private String url = "jdbc:mysql:localhost:3306/test";
private String user = "root";
private String password = "";
private Connection conn;
public Connection openDB(String url, String user, String password) {
// 連接資料庫代碼
}
public void close() {
// 關閉資料庫
}
}
類似地還有 Oracle.java 等。介面 DB 給這些類歸了類,應用程式中這樣定義物件:
org.bromon.test.DB myDB;
使用 myDB 操作資料庫,無需關心具體用的是哪個類,這就是所謂的「開閉原則」。但介面無法實例化,myDB = new DB() 是錯誤的,只能 myDB = new Mysql() 或 myDB = new Oracle()。麻煩來了:仍需指定具體實例化哪個類,用了介面似乎跟沒用一樣。因此需要一個工廠:
package org.bromon.test;
public class DBFactory {
public static DB getConn() {
return new Mysql();
}
}
實例化代碼變為:myDB = DBFactory.getConn()。這就是 23 種模式中最基礎的普通工廠。工廠類負責具體實例化,其他邏輯針對 DB 介面操作,即「面向介面編程」。責任推給工廠類,若繼續定義工廠介面往上拋,就演變成抽象工廠。
整個過程中介面不負責任何具體操作,其他程式連接資料庫只需構造一個 DB 物件,不管工廠類如何變化。這就是介面的意義——抽象。
繼承概念很好理解。為什麼要繼承?因為想重用代碼?這絕對不是理由。繼承的意義同樣在於抽象,而非代碼重用。若物件 A 有 run() 方法,物件 B 也想要,有人就 Class B extends A。這是不經大腦的做法。若在 B 中實例化一個 A,調用 A 的 run(),不也能達到同樣目的?
Class B {
A a = new A();
a.run();
}
這就是利用類的聚合重用代碼,是委派模式的雛形,也是 GoF 一貫倡導的做法。
那麼繼承的意義何在?歷史原因:最早的 OO 語言只有繼承,沒有介面,只能用繼承實現抽象。請注意:繼承的本意在於抽象,而非代碼重用(雖然繼承也有這個作用)。這是許多 Java 爛書最嚴重的錯誤之一,其陰影至今未消,壞書害人啊,尤其是入門類,流毒太大。何時該用繼承?只在抽象類中使用,其他情況盡量不用。抽象類也不能實例化,僅提供模版,這說明了一切。
軟體開發的萬惡之源:一是重複代碼而非重用代碼,二是濫用繼承,尤以 C++ 程式設計師為甚。Java 取締多重繼承,目的正是制止濫用繼承,實為明智之舉,可惜很多人不理解。Java 能更好地體現設計,這是我入迷的原因之一。