Scala 並不僅僅只給 JVM 引入了函數概念,它還為我們提供了一種對於面向對象語言設計的現代視角。在這一期的 面向 Java 開發人員的 Scala 指南 中,Ted Neward 介紹了 Scala 如何利用特征(trait)使對象更加簡單、更易於構建。您將了解到,特征與 Java™ 接口和 C++ 多重繼承提供的傳統極性既有相似之處,也有不同之處。
著名科學家、研究學者艾薩克.牛頓爵士有這樣一句名言:“如果說我看得比別人遠一些,那是因為我站在巨人的肩膀上”。作為一名熱心的歷史和政治學家,我想對這位偉人的名言略加修改:“如果說我看得比別人遠一些,那是因為我站在歷史的肩膀上”。而這句話又體現出另一位歷史學家 George Santayana 的名言:“忘記歷史必將重蹈覆轍”。換句話說,如果我們不能回顧歷史,從過去的錯誤(包括我們自己過去的經驗)中吸取教訓,就沒有機會做出改進。
您可能會疑惑,這樣的哲學與 Scala 有什麼關系?繼承就是我們要討論的內容之一。考慮這樣一個事實,Java 語言的創建已經是近 20 年前的事情,當時是 “面向對象” 的全盛時期。它設計用於模仿當時的主流語言 C++,嘗試將使用這種語言的開發人員吸引到 Java 平台上來。毫無疑問,在當時看來,這樣的決策是明智而且必要的,但回顧一下,就會發現其中有些地方並不像創建者設想的那樣有益。
例如,在二十年前,對於 Java 語言的創建者來說,反映 C++ 風格的私有繼承和多重繼承是必要的。自那之後,許多 Java 開發人開始為這些決策而後悔。在這一期的 Scala 指南中,我回顧了 Java 語言中多重繼承和私有繼承的歷史。隨後,您將看到 Scala 是怎樣改寫了歷史,為所有人帶來更大收益。
C++ 和 Java 語言中的繼承
歷史是人們願意記錄下來的事實。
—拿破侖.波拿巴
從事 C++ 工作的人們能夠回憶起,私有繼承是從基類中獲取行為的一種方法,不必顯式地接受 IS-A 關系。將基類標記為 “私有” 允許派生類從該基類繼承而來,而無需實際成為 一個基類。但對自身的私有繼承是未得到廣泛應用的特性之一。繼承一個基類而無法將它向下或向上轉換到基類的理念是不明智的。
另一方面,多重繼承往往被視為面向對象編程的必備要素。在建模交通工具的層次結構時, SeaPlane 無疑需要繼承 Boat(使用其 startEngine() 和 sail() 方法)以及 Plane(使用其 startEngine() 和 fly() 方法)。SeaPlane 既是 Boat,也是 Plane,難道不是嗎?
無論如何,這是在 C++ 鼎盛時期的想法。在快速轉向 Java 語言時,我們認為多重繼承與私有繼承一樣存在缺陷。所有 Java 開發人員都會告訴您,SeaPlane 應該繼承 Floatable 和 Flyable 接口(或許還包括 EnginePowered 接口或基類)。繼承接口意味著能夠實現該類需要的所有方法,而不會遇到 虛擬多重繼承 的難題(遇到這種難題時,要弄清楚在調用 SeaPlane 的 startEngine() 方法時應調用哪個基類的 startEngine())。
遺憾的是,徹底放棄私有繼承和多重繼承會使我們在代碼重用方面付出昂貴的代價。Java 開發人員可能會因從虛擬多重繼承中解放出來而高興,但代價是程序員往往要完成辛苦而易於出錯的工作。
回顧可重用行為
事情大致可以分為可能永遠不會發生的和不重要的。
—William Ralph Inge
JavaBeans 規范是 Java 平台的基礎,它帶來了眾多 Java 生態系統作為依據的 POJO。我們都明白一點,Java 代碼中的屬性由 get()/set() 對管理,如清單 1 所示:
清單 1. Person POJO
//This is Java
public class Person
{
private String lastName;
private String firstName;
private int age;
public Person(String fn, String ln, int a)
{
lastName = ln; firstName = fn; age = a;
}
public String getFirstName() { return firstName; }
public void setFirstName(String v) { firstName = v; }
public String getLastName() { return lastName; }
public void setLastName(String v) { lastName = v; }
public int getAge() { return age; }
public void setAge(int v) { age = v; }
}
這些代碼看起來非常簡單,編寫起來也不難。但如果您希望提供通知支持 — 使第三方能夠使用 POJO 注冊並在變更屬性時接收回調,事情會怎樣?根據 JavaBeans 規范,必須實現 PropertyChangeListener 接口以及它的一個方法 propertyChange()。如果您希望允許任何 POJO 的 PropertyChangeListener 都能夠對屬性更改 “投票”,那麼 POJO 就需要實現 VetoableChangeListener 接口,該接口的實現又依賴於 vetoableChange() 方法的實現。
至少,事情應該是這樣運作的。
實際上,希望成為屬性變更通知接收者的用戶必須實現 PropertyChangeListener 接口,發送者(本例中的 Person 類)必須提供接收該接口實例的公共方法和監聽器需要監聽的屬性名稱。最終得到更加復雜的 Person,如清單 2 所示:
清單 2. Person POJO,第 2 種形式
//This is Java
public class Person
{
// rest as before, except that inside each setter we have to do something
// like:
// public setFoo(T newValue)
// {
// T oldValue = foo;
// foo = newValue;
// pcs.firePropertyChange("foo", oldValue, newValue);
// }
public void addPropertyChangeListener(PropertyChangeListener pcl)
{
// keep a reference to pcl
}
public void removePropertyChangeListener(PropertyChangeListener pcl)
{
// find the reference to pcl and remove it
}
}
保持引用屬性變更監聽器意味著 Person POJO 必須保留某種類型的集合類(例如 ArrayList)來包含所有引用。然後必須實例化、插入並移除 POJO — 由於這些操作不是原子操作,因此還必須包含恰當的同步保護。
最後,如果某個屬性發生變化,屬性監聽器列表必須得到通知,通常通過遍歷 PropertyChangeListener 的集合並對各元素調用 propertyChange() 來實現。此過程包括傳入新的 PropertyChangeEvent 描述屬性、原有值和新值,這是 PropertyChangeEvent 類和 JavaBeans 規范的要求。
在我們編寫的 POJO 中,只有少數支持監聽器通知,這並不意外。在這裡要完成大量工作,必須手動地重復處理所創建的每一個 JavaBean/POJO。
除了工作還是工作 — 變通方法在哪裡?
有趣的是,C++ 對於私有繼承的支持在 Java 語言中得到了延續,今天,我們用它來解決 JavaBeans 規范的難題。一個基類為 POJO 提供了基本 add() 和 remove() 方法、集合類以及 “firePropertyChanged()” 方法,用於通知監聽器屬性變更。
我們仍然可以通過 Java 類完成,但由於 Java 缺乏私有繼承,Person 類必須繼承 Bean 基類,從而可向上轉換 到 Bean。這妨礙了 Person 繼承其他類。多重繼承可能使我們不必處理後續的問題,但它也重新將我們引向了虛擬繼承,而這是絕對要避免的。
針對這個問題的 Java 語言解決方案是運用眾所周知的支持 類,在本例中是 PropertyChangeSupport:實例化 POJO 中的一個類,為 POJO 本身使用必要的公共方法,各公共方法都調用 Support 類來完成艱難的工作。更新後的 Person POJO 可以使用 PropertyChangeSupport,如下所示:
清單 3. Person POJO,第 3 種形式
//This is Java
import java.beans.*;
public class Person
{
private String lastName;
private String firstName;
private int age;
private PropertyChangeSupport propChgSupport =
new PropertyChangeSupport(this);
public Person(String fn, String ln, int a)
{
lastName = ln; firstName = fn; age = a;
}
public String getFirstName() { return firstName; }
public void setFirstName(String newValue)
{
String old = firstName;
firstName = newValue;
propChgSupport.firePropertyChange("firstName", old, newValue);
}
public String getLastName() { return lastName; }
public void setLastName(String newValue)
{
String old = lastName;
lastName = newValue;
propChgSupport.firePropertyChange("lastName", old, newValue);
}
public int getAge() { return age; }
public void setAge(int newValue)
{
int old = age;
age = newValue;
propChgSupport.firePropertyChange("age", old, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener pcl)
{
propChgSupport.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl)
{
propChgSupport.removePropertyChangeListener(pcl);
}
}
不知道您有何感想,但這段代碼的復雜得讓我想去重拾匯編語言。最糟糕的是,您要對所編寫的每一個 POJO 重復這樣的代碼序列。清單 3 中的半數工作都是在 POJO 本身中完成的,因此無法被重用 — 除非是通過傳統的 “復制粘貼” 編程方法。
現在,讓我們來看看 Scala 提供什麼樣內容來實現更好的變通方法。
Scala 中的特征和行為重用
所有人都有義務考慮自己的性格特征。必須合理控制這些特征,而不去質疑他人的性格特征是否更適合自己。
—西塞羅
Scala 使您能夠定義處於接口和類之間的新型結構,稱為特征(trait)。特征很奇特,因為一個類可以按照需要整合許多特征,這與接口相似,但它們還可包含行為,這又與類相似。同樣,與類和接口類似,特征可以引入新方法。但與類和接口不同之處在於,在特征作為類的一部分整合之前,不會檢查行為的定義。或者換句話說,您可以定義出這樣的方法,在整合到使用特征的類定義之前,不會檢查其正確性。
特征聽起來十分復雜,但一個實例就可以非常輕松地理解它們。首先,下面是在 Scala 中重定義的 Person POJO:
清單 4. Scala 的 Person POJO
//This is Scala
class Person(var firstName:String, var lastName:String, var age:Int)
{
}
您還可以確認 Scala POJO 具備基於 Java POJO 的環境中需要的 get()/set() 方法,只需在類參數 firstName、lastName 和 age 上使用 scala.reflect.BeanProperty 注釋即可。現在,為簡單起見,我們暫時不考慮這些方法。
如果 Person 類需要能夠接收 PropertyChangeListener,可以使用如清單 5 所示的方式來完成此任務:
清單 5. Scala 的 Person POJO 與監聽器
//This is Scala
object PCL
extends java.beans.PropertyChangeListener
{
override def propertyChange(pce:java.beans.PropertyChangeEvent):Unit =
{
System.out.println("Bean changed its " + pce.getPropertyName() +
" from " + pce.getOldValue() +
" to " + pce.getNewValue())
}
}
object App
{
def main(args:Array[String]):Unit =
{
val p = new Person("Jennifer", "Aloi", 28)
p.addPropertyChangeListener(PCL)
p.setFirstName("Jenni")
p.setAge(29)
System.out.println(p)
}
}
注意,如何使用清單 5 中的 object 實現將靜態方法注冊為監聽器 — 而在 Java 代碼中,除非顯式創建並實例化 Singleton 類,否則永遠無法實現。這進一步證明了一個理論:Scala 從 Java 開發的歷史 痛苦 中吸取了教訓。
Person 的下一步是提供 addPropertyChangeListener() 方法,並在屬性更改時對各監聽器觸發 propertyChange() 方法調用。在 Scala 中,以可重用的方式完成此任務與定義和使用特征一樣簡單,如清單 6 所示。我將此特征稱為 BoundPropertyBean,因為在 JavaBeans 規范中,“已通知” 的屬性稱為綁定屬性。
清單 6. 神聖的行為重用!
//This is Scala
trait BoundPropertyBean
{
import java.beans._
val pcs = new PropertyChangeSupport(this)
def addPropertyChangeListener(pcl : PropertyChangeListener) =
pcs.addPropertyChangeListener(pcl)
def removePropertyChangeListener(pcl : PropertyChangeListener) =
pcs.removePropertyChangeListener(pcl)
def firePropertyChange(name : String, oldVal : _, newVal : _) : Unit =
pcs.firePropertyChange(new PropertyChangeEvent(this, name, oldVal, newVal))
}
同樣,我依然要使用 java.beans 包的 PropertyChangeSupport 類,不僅因為它提供了約 60% 的實現細節,還因為我所具備的行為與直接使用它的 JavaBean/POJO 相同。對 “Support” 類的其他任何增強都將傳播到我的特征。不同之處在於 Person POJO 不需要再直接使用 PropertyChangeSupport,如清單 7 所示:
清單 7. Scala 的 Person POJO,第 2 種形式
//This is Scala
class Person(var firstName:String, var lastName:String, var age:Int)
extends Object
with BoundPropertyBean
{
override def toString = "[Person: firstName=" + firstName +
" lastName=" + lastName + " age=" + age + "]"
}
在編譯後,簡單查看 Person 定義即可發現它有公共方法 addPropertyChangeListener()、removePropertyChangeListener() 和 firePropertyChange(),就像 Java 版本的 Person 一樣。實際上,Scala 的 Person 版本僅通過一行附加的代碼即獲得了這些新方法:類聲明中的 with 子句將 Person 類標記為繼承 BoundPropertyBean 特征。
遺憾的是,我還沒有完全實現;Person 類現在支持接收、移除和通知監聽器,但 Scala 為 firstName 成員生成的默認方法並沒有利用它們。同樣遺憾的是,這樣編寫的 Scala 沒有很好的注釋以自動地 生成利用 PropertyChangeSupport 實例的 get/set 方法,因此我必須自行編寫,如清單 8 所示:
清單 8. Scala 的 Person POJO,第 3 種形式
//This is Scala
class Person(var firstName:String, var lastName:String, var age:Int)
extends Object
with BoundPropertyBean
{
def setFirstName(newvalue:String) =
{
val oldvalue = firstName
firstName = newvalue
firePropertyChange("firstName", oldvalue, newvalue)
}
def setLastName(newvalue:String) =
{
val oldvalue = lastName
lastName = newvalue
firePropertyChange("lastName", oldvalue, newvalue)
}
def setAge(newvalue:Int) =
{
val oldvalue = age
age = newvalue
firePropertyChange("age", oldvalue, newvalue)
}
override def toString = "[Person: firstName=" + firstName +
" lastName=" + lastName + " age=" + age + "]"
}
應該具備的出色特征
特征不是一種函數編程 概念,而是十多年來反思對象編程的結果。實際上,您很有可能正在簡單的 Scala 程序中使用以下特征,只是沒有意識到而已:
清單 9. 再見,糟糕的 main()!
//This is Scala
object App extends Application
{
val p = new Person("Jennifer", "Aloi", 29)
p.addPropertyChangeListener(PCL)
p.setFirstName("Jenni")
p.setAge(30)
System.out.println(p)
}
Application 特征定義了一直都是手動定義的 main() 的方法。實際上,它包含一個有用的小工具:計時器,如果系統屬性 scala.time 傳遞給了 Application 實現代碼,它將為應用程序的執行計時,如清單 10 所示:
清單 10. 時間就是一切
$ scala -Dscala.time App
Bean changed its firstName from Jennifer to Jenni
Bean changed its age from 29 to 30
[Person: firstName=Jenni lastName=Aloi age=30]
[total 15ms]
JVM 中的特征
任何足夠高級的技術都近乎魔術。
— Arthur C Clarke
在這個時候,有必要提出這樣一個問題,這種看似魔術的接口與方法結構(即 特征)是如何映射到 JVM 的。在清單 11 中,我們的好朋友 javap 展示了魔術背後發生了什麼:
清單 11. Person 內幕
$ javap -classpath C:\Prg\scala-2.7.0-final\lib\scala-library.jar;classes Person Compiled from "Person.scala" public class Person extends java.lang.Object implements BoundPropertyBean,scala. ScalaObject{ public Person(java.lang.String, java.lang.String, int); public java.lang.String toString(); public void setAge(int); public void setLastName(java.lang.String); public void setFirstName(java.lang.String); public void age_$eq(int); public int age(); public void lastName_$eq(java.lang.String); public java.lang.String lastName(); public void firstName_$eq(java.lang.String); public java.lang.String firstName(); public int $tag(); public void firePropertyChange(java.lang.String, java.lang.Object, java.lang .Object); public void removePropertyChangeListener(java.beans.PropertyChangeListener); public void addPropertyChangeListener(java.beans.PropertyChangeListener); public final void pcs_$eq(java.beans.PropertyChangeSupport); public final java.beans.PropertyChangeSupport pcs(); }
請注意 Person 的類聲明。該 POJO 實現了一個名為 BoundPropertyBean 的接口,這就是特征作為接口映射到 JVM 本身的方法。但特征方法的實現又是什麼樣的呢?請記住,編譯器可以容納所有技巧,只要最終結果符合 Scala 語言的語義含義即可。在這種情況下,它會將特征中定義的方法實現和字段聲明納入實現特征的類 Person 中。使用 -private 運行 javap 會使這更加顯著 — 如果 javap 輸出的最後兩行體現的還不夠明顯(引用特征中定義的 pcs 值):
清單 12. Person 內幕,第 2 種形式
$ javap -private -classpath C:\Prg\scala-2.7.0-final\lib\scala-library.jar;classes Person
Compiled from "Person.scala"
public class Person extends java.lang.Object implements BoundPropertyBean,scala.
ScalaObject{
private final java.beans.PropertyChangeSupport pcs;
private int age;
private java.lang.String lastName;
private java.lang.String firstName;
public Person(java.lang.String, java.lang.String, int);
public java.lang.String toString();
public void setAge(int);
public void setLastName(java.lang.String);
public void setFirstName(java.lang.String);
public void age_$eq(int);
public int age();
public void lastName_$eq(java.lang.String);
public java.lang.String lastName();
public void firstName_$eq(java.lang.String);
public java.lang.String firstName();
public int $tag();
public void firePropertyChange(java.lang.String, java.lang.Object, java.lang.Object);
public void removePropertyChangeListener(java.beans.PropertyChangeListener);
public void addPropertyChangeListener(java.beans.PropertyChangeListener);
public final void pcs_$eq(java.beans.PropertyChangeSupport);
public final java.beans.PropertyChangeSupport pcs();
}
實際上,這個解釋也回答了為何可以推遲特征方法的執行,直至用該檢查的時候。因為在類實現特征的方法之前,它實際上並不是任何類的一 “部分”,因此編譯器可將方法的某些邏輯方面留到以後再處理。這非常有用,因為它允許特征在不了解實現特征的實際基類將是什麼的情況下調用 super()。
關於特征的備注
在 BoundPropertyBean 中,我在 PropertyChangeSupport 實例的構建中使用了特征功能。其構造方法需要屬性得到通知的 bean,在早先定義的特征中,我傳入了 “this”。由於在 Person 上實現之前並不會真正定義特征,“this” 將引用 Person 實例,而不是 BoundPropertyBean 特征本身。特征的這個具體方面 — 定義的推遲解析 — 非常微妙,但對於此類的 “遲綁定” 來說可能非常強大。
對於 Application 特征的情況,有兩部分很有魔力;Application 特征的 main() 方法為 Java 應用程序提供普適入口點,還會檢查 -Dscala.time 系統屬性,查看是否應該跟蹤執行時間。但由於 Application 是一個特征,方法實際上會在子類上出現(App)。要執行此方法,必須創建 App 單體,也就是說構造 App 的一個實例,“處理” 類的主體,這將有效地執行應用程序。只有在這種處理完成之後,特征的 main() 才會被調用並顯示執行所耗費的時間。
雖然有些落後,但它仍然有效,盡管應用程序無權訪問任何傳入 main() 的命令行參數。它還表明特征的行為如何 “下放到” 實現類。
特征和集合
不是解決方法的一部分,就注定被淘汰。
— Henry J Tillman
在將具體行為與抽象聲明相結合以便為實現者提供便捷時,特征非常強大。例如,考慮經典的 Java 集合接口/類 List 和 ArrayList。List 接口保證此集合的內容能夠按照插入時的次序被遍歷,用更正規的術語來說,“位置語義得到了保證”。
ArrayList 是 List 的具體類型,在分配好的數組中存儲內容,而 LinkedList 使用的是鏈表實現。ArrayList 更適合列表內容的隨機訪問,而 LinkedList 更適合在除了列表末尾以外的位置進行插入和刪除操作。無論如何,這兩種類之間存在大量相同的行為,它們繼承了公共基類 AbstractList。
如果 Java 編程支持特征,它們應已成為出色的結構,能夠解決 “可重用行為,而無需訴諸於繼承公共基類” 之類的問題。特征可以作為 C++ “私有繼承” 機制,避免出現新 List 子類型是否應直接實現 List(還有可能忘記實現 RandomAccess 接口)或者擴展基類 AbstractList 的迷惑。這有時在 C++ 中稱為 “混合”,與 Ruby 的混合(或後文中探討的 Scala 混合)有所不同。
在 Scala 文檔集中,經典的示例就是 Ordered 特征,它定義了名字很有趣的方法,以提供比較(以及排序)功能,如清單 13 所示:
清單 13. 順序、順序
//This is Scala
trait Ordered[A] {
def compare(that: A): Int
def < (that: A): Boolean = (this compare that) < 0
def > (that: A): Boolean = (this compare that) > 0
def <= (that: A): Boolean = (this compare that) <= 0
def >= (that: A): Boolean = (this compare that) >= 0
def compareTo(that: A): Int = compare(that)
}
在這裡,Ordered 特征(具有參數化類型,采用 Java 5 泛型方式)定義了一個抽象方法 compare,它應獲得一個 A 作為參數,並需要在 “小於” 的情況下返回小於 1 的值,在 “大於” 的情況下返回大於 1 的值,在相等的情況下返回 0。然後它繼續使用 compare() 方法和更加熟悉的 compareTo() 方法(java.util.Comparable 接口也使用該方法)定義關系運算符(< 和 > 等)。
Scala 和 Java 兼容性
一張圖片勝過千言萬語。一個界面勝過上千圖片。
—Ben Shneiderman
實際上,偽實現繼承並不是 Scala 內特征的最常見應用或最強大用法,與此不同,特征在 Scala 內作為 Java 接口的基本替代項。希望使用 Scala 的 Java 程序員也應熟悉特征,將其作為使用 Scala 的一種機制。
我在本系列的文章中一直強調,編譯後的 Scala 代碼並非總是能夠保證 Java 語言的特色。例如,回憶一下,Scala 的 “名字很有趣的方法”(例如 “+” 或 “\”),這些方法往往會使用 Java 語言語法中不直接可用的字符編碼(“$” 就是一個需要考慮的嚴重問題)。出於這方面的原因,創建 “Java 可調用” 的接口往往要求深入研究 Scala 代碼。
這個特殊示例有些憋足,Scala 主義者 通常並不需要特征提供的間接層(假設我並未使用 “名字很有趣的方法”),但概念在這裡十分重要。在清單 14 中,我希望獲得一個傳統的 Java 風格工廠,生成 Student 實例,就像您經常在各種 Java 對象模型中可以看到的那樣。最初,我需要一個兼容 Java 的接口,接合到 Student:
清單 14. 我,學生
//This is Scala
trait Student
{
def getFirstName : String;
def getLastName : String;
def setFirstName(fn : String) : Unit;
def setLastName(fn : String) : Unit;
def teach(subject : String)
}
在編譯時,它會轉換成 POJI:Plain Old Java Interface,查看 javap 會看到這樣的內容:
清單 15. 這是一個 POJI!
$ javap Student
Compiled from "Student.scala"
public interface Student extends scala.ScalaObject{
public abstract void setLastName(java.lang.String);
public abstract void setFirstName(java.lang.String);
public abstract java.lang.String getLastName();
public abstract java.lang.String getFirstName();
public abstract void teach(java.lang.String);
}
接下來,我需要一個類成為工廠本身。通常,在 Java 代碼中,這應該是類上的一個靜態方法(名稱類似於 “StudentFactory”),但回憶一下,Scala 並沒有此類的實例方法。我認為這就是我在這裡希望得到的結論,因此,我創建了一個 StudentFactory 對象,將我的 Factory 方法放在那裡:
清單 16. 我構造 Students
//This is Java
object StudentFactory
{
class StudentImpl(var first:String, var last:String, var subject:String)
extends Student
{
def getFirstName : String = first
def setFirstName(fn: String) : Unit = first = fn
def getLastName : String = last
def setLastName(ln: String) : Unit = last = ln
def teach(subject : String) =
System.out.println("I know " + subject)
}
def getStudent(firstName: String, lastName: String) : Student =
{
new StudentImpl(firstName, lastName, "Scala")
}
}
嵌套類 StudentImpl 是 Student 特征的實現,因而提供了必需的 get()/set() 方法對。切記,盡管特征可以具有行為,但它根據 JVM 作為接口建模這一事實意味著嘗試實例化特征將產生錯誤 —— 表明 Student 是抽象的。
當然,這個簡單示例的目的在於編寫出一個 Java 應用程序,使之可以利用這些由 Scala 創建的新對象:
清單 17. 學生 Neo
//This is Java
public class App
{
public static void main(String[] args)
{
Student s = StudentFactory.getStudent("Neo", "Anderson");
s.teach("Kung fu");
}
}
運行此代碼,您將看到:“I know Kung fu”。(我知道,我們經過了漫長的設置過程,只是得到了一部廉價電影的推介)。
結束語
人們不喜歡思考。思考總是要得出結論。而結論並非總是令人愉快。
— Helen Keller
特征提供了在 Scala 中分類和定義的強大機制,目的在於定義一種接口,供客戶端使用,按照 傳統 Java 接口的形式定義;同時提供一種機制,根據特征內定義的其他行為來繼承行為。或許我們需要的是一種全新的繼承術語,用於 描述特征和實現類之間的關系。
除了本文所述內容之外,還有很多種方法可以使用特征,但本系列文章的部分目的就在於提供關於這種語言的足夠信息,鼓勵您在家中進一步開展實驗;下載 Scala 實現,親自試用,查看 Scala 可插入當前 Java 系統的什麼位置。此外,如果您發現 Scala 非常有用,如果您對本文有任何意見建議,或者(歎息)您發現代碼、行文中存在 bug,請 給我留言,讓我知道您的意見。
這一期的內容到此為止,實用主義愛好者請關注本系列的下一期文章。