文章來源:電腦愛好者 作者:Gary Chan
構思這篇咖啡館的時候時值奧運聖火熊熊燃燒,看到中國奧運代表團努力爲國爭光,不由不決定把這次的主題獻給我們的奧運健兒!
面向對象編程
如果你是Java咖啡館的常客,那麼在不知不覺中你早已接觸並運用過Java的面向對象知識。在這回的咖啡館中,讓我們詳細剖析一個面向對象編程的實例,把知識鞏固下來。
奧運是國際性的運動盛會,中國運動員自然要用英文形式的名字才便於同國際接軌。這回要編寫的程序便是用來解析英文名字的工具。通常,Gary Chan這樣的英文名字形式表示名在前、姓在後。而Yao, Ming這樣的形式則表示姓在前名在後。大家千萬不要以爲Gary在這裏平白無故用Yao, Ming打廣告,Yao初中時候可是跟Gary一個班的,他被語文老師仰着頭臭罵痛哭後,經常是Gary安慰他,並經常一同騎車回家。看着今日的Yao已經是世界級的運動員爲國爭光了,Gary更需要加倍努力了……
OK,言歸正傳,我們的程序將自動判斷名字形式,並且分解出姓和名。還是老規矩,請用Eclipse生成一個名爲Chap 07 NameParser的項目,並且加入一個新的名爲NameParser類,在Package屬性填寫com.cfan.garychan.nameparser。如果你忘記了package的知識,請參考《Java咖啡館(6)—編寫猜數字遊戲 》中關於包概念的描述。
回顧一下,類是定義了從類生成的實例(instance)中的數據和方法的關係的模板。有人喜歡把類比作圖章,圖章敲出來的圖案便是對象,的確很形象。
Java中用class關鍵字來定義類,不過我們用Eclipse來定義更加方便。仍然用Eclipse新建一個叫做Namer的類,記得不要在public static void main(String[] args)前面打勾,確定後Eclipse便生成一個新的Java源文件Namer.java,裏面的代碼如下:
public class Namer {
}
這個類非常簡單,可惜不能做任何事情。
1.封裝
面向對象程序設計中,一個非常重要的技術便是封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。這樣做的好處在於可以使類內部的具體實現透明化,只要其他代碼不依賴類內部的私房數據,你便可以安心修改這些代碼。此外,這樣做也是出於安全方面的考慮,如果代表網上支付卡密碼的變量隨便就可以被訪問到,這樣的系統誰還敢用呢?
封裝主要依靠對類、數據和方法的訪問控制,從語法上講就是加上private、protected、public等關鍵詞,如果沒有關鍵詞修飾則默認爲package。它們控制權限如下表所示:
Specifier 類 子類 包 世界
private X
protected X X* X
public X X X X
package X X
注意上面的X*,父類的protected部分,只有在與父類在同一個包內的子類才能夠訪問,否則也是不可訪問的。
讓我們結合實例理解一下。稍微把Namer類改一下:
public class Namer {
protected String surname; // 姓
protected String firstname; // 名
public String getFirstname() {
return firstname;
}
public String getSurname() {
return surname;
}
}
這個類有兩個String類型的成員變量,surname和firstname,分別用來儲存姓和名。這兩個成員變量前都有protected修飾詞,按照表格,這兩個變量僅能夠被類本身、子類以及包中其他類操作,而包外的類則無權訪問。不過,爲了跟包外的代碼進行溝通,Namer類提供了getFirstname和getSurname這兩個public的方法。從而,對包外的類而言,姓名數據是隻讀的。
2.繼承
對象是用類來定義的。通過類,你能夠充分了解對象的全貌。比如,一說起自行車,你就會聯想到自行車是有兩個輪子、車把以及腳踏板。
更進一步,面嚮對象語言的另一個特點便是允許從一個已有的類定義新的類。比如,山地車、公路賽車和兩人三輪車都是自行車。在面嚮對象語言中,你可以從一個已經有的自行車類定義山地車類、公路賽車類等等。山地車類、公路賽車類都稱爲自行車類的子類,自行車類是它們的父類,而這種定義關係,便是繼承關係。
子類繼承了父類的屬性。比如,山地車、公路賽車都是有兩個輪子一個車座。子類也可繼承了父類的方法,比如山地車、公路賽車、兩人三輪車都可以前進、剎車、轉彎等。
當然,子類並不限於繼承,還可以發揚光大。比如兩人三輪車便顛覆了自行車只有兩個輪子、一個座墊的屬性,使得自己更加休閒瀟灑。
讓我們看看如何運用繼承來處理名在姓之前的模式。這種模式中,由於姓和名是用空格分割的,所以程序如下:
class FirstFirst extends Namer {
public FirstFirst(String s) {
int i = s.lastIndexOf(" "); // 搜索空格
if (i > 0) {
firstname = s.substring(0, i).trim();
surname = s.substring(i + 1).trim();
}
}
}
FirstFirst類通過extends關鍵詞表示對Namer類進行繼承,只有一個類方法,名字恰好是FirstFirst。這並不是一個巧合。
所有的Java類都擁有若干特殊方法用來初始化對象,它們稱爲構造函數,特徵就是與類同名,可以帶有或者沒有參數。這種同名函數不同參數的現象,在面向對象中稱作重載(Overload)。拿以前使用new操作符生成隨機數的代碼來說:
Random random = new Random();
new操作符實例化一個Random對象後,緊接着就調用了Random類的構造函數進行初始化,只不過這個構造函數沒有參數。沒有參數的構造函數,稱爲默認構造函數。默認的構造函數是每個類都擁有的,即使沒有聲明在代碼中,Java編譯器在編譯時也會自動加入。
回過頭來看FirstFirst類。FirstFirst類繼承自Namer類,從而也擁有自己的firstname和surname屬性。在FirstFirst類的構造函數中,通過解析參數s,通過搜索空格的方法來解析出空格前面的名和空格後面的姓,從而執行
FirstFirst parser = new FirstFirst("Gary Chan");
之後,我的姓和名已經解析出來並且分別保存在firstname和surname變量中了。同時,FirstFirst類繼承了Namer的方法,從而便可以通過如下語句來返回姓——Gary了:
String mySername = parser.getSurname();
注意,我們並沒有在FirstFirst類中定義getSurname()方法,這是從父類繼承來的,這就是代碼重用的概念,避免了無謂的重複勞動。
有了上面的基礎,再來編寫名在姓之後的模式:
class FirstLast extends Namer {
public FirstLast(String s) {
int i = s.indexOf(","); // 搜索逗號
if (i > 0) {
surname = s.substring(0, i).trim();
firstname = s.substring(i + 1).trim();
}
}
}
由此可見,Namer類的兩個子類擁有它全部的屬性和方法,並且在其之上更加入瞭解析姓名的能力,而代碼卻增加不多。代碼重用,這是面向對象的主要魅力之一!
3.多態
至此,我們已經分別爲兩種名字解析方法編寫了兩個類,即FirstLast類和FirstFirst類。爲了更好地使用這兩個類,讓我們玩一些小技巧。
首先,對於姓名解析器的使用者,具體是使用Namer類還是FirstLast類還是FirstFirst類,他是不關心的。這些東西最好都是自動化的,他只要能得到姓和名即可。
其次,如果你是屬於膽大心細遇事不慌的(阿慶嫂類型)IT青年的話,一定會發現Namer.java中只有Namer類是public的,FirstFirst類和FirstLast類之前沒有修飾——它們是默認的package的,也就是說,在com.cfan.garychan.nameparser包之外,都是無法被訪問到的。
如果僅僅能夠Namer類來解析姓名那該多好啊!
實際上,運用多態的概念,這些問題將迎刃而解。
面向對象一共有三個特性:封裝、繼承、多態。所謂封裝,就是通過定義類並且給類的屬性和方法加上訪問控制來抽象事物的本質特性。所謂繼承,就是代碼重用。而多態,從另外一個角度分割了接口和實現,即把“什麼”和“如何”兩個概念分離開來。舉個例子,公路賽車是自行車,繼承了自行車的剎車方法。假設你和朋友騎着捷安特的公路賽車出遊,當你的朋友正好側着臉看風景時,前面突然竄出來一隻貓,你一定大聲驚呼:趕快剎車!仔細體會這句話,你的意識中只是知道自行車可以剎車,所以讓朋友按下車閘讓自行車剎車,而絕對不是認爲—捷安特牌子的公路賽車趕快剎車!從而,思考的是抽象的
自行車的剎車,而最終動作卻是捷安特牌子的公路賽車剎車,通過類指代實例,這就是多態的概念。
回過頭看我們的程序,public的Namer類正好是FirstFirst類和FirstLast類的共同父類,應用多態的概念實在是太合適不過了。新建一個名爲NameFactory的類,並且把這個類也放在com.cfan.garychan.nameparser包中,代碼如下:
public class NameFactory {
public static Namer getNamer(String entry) {
if (entry.indexOf(",") > 0)
return new FirstLast(entry); //return one class
else if (entry.indexOf(" ") > 0)
return new FirstFirst(entry); //or the other
else
return null;
}
}
NameFactory類只有一個靜態方法getNamer,注意返回的是一個Namer類。下面根據entry參數是否包含“,”符號來確定實際生成的是FirstLast類還是FirstFirst類,最終將其返回。你看,說是返回Namer類,實際返回的是FirstLast類或者FirstFirst類,這就是多態的典型應用。需要注意的是,並非毫不相關的類都能夠當作多態使用,必須是有繼承關係,而且有方向性。結合生活經驗,多態的概念並不難理解。
最後讓我們看看如何使用這個姓名解析器。新建Chap07NameParser類,Package是com.cfan.garychan,代碼如下:
package com.cfan.garychan;
import com.cfan.garychan.nameparser.NameFactory;
import com.cfan.garychan.nameparser.Namer;
/**
* 用解析器解析姚明的英文名字。
*/
public class Chap07NameParser {
public static void main(String[] args) {
Namer namer = NameFactory.getNamer("Yao, Ming");
if (null == namer) {
System.out.println("姓名不合法");
else {
System.out.println("姓:" + namer.getSurname());
System.out.println("名:" + namer.getFirstname());
}
}
}
你看,我們通過NameFactory返回一個Namer對象,這個對象能夠解析姚明的英文名字,你不必關心這個Namer對象究竟是FirstFirst類還是FirstLast類,方便極了。
面向對象的未來
面向對象技術是軟件技術自然演變的結果,在許多領域有着強大的生命力與美好的前景。借用Maurice Wilkes在他的圖靈獎領獎儀式上的話,“面向對象技術是70年代以來最激動人心的革新之一”。然而,面向對象並非包治百病的靈丹妙藥,其發展還遠未成熟,還有許多問題值得我們付出真正的熱情!