2010年11月30日 星期二

當兵前驗出僵直性脊椎炎

Q:役男在當兵前驗出僵直性脊椎炎應該如何處理?
A:請申請兵役複檢。

在7月底繳了畢業證書影本到鄉公所,希望公所兵役課人員幫我排9月的梯次,豈料適逢碩博士畢業潮,9月、10月的梯次大部分名額都讓年次比較前面的畢業生佔走了,不斷詢問之下終於在10月初確定入伍時間為11月3日。然而,正當我問著「在不強迫健康女人懷孕生子的年代,為什麼一個半殘男人竟有服兵役的義務?」這個問題時,中國醫藥大學附設醫院中醫傷科的劉醫師就像下凡來傳福音的天使一樣,為我帶來了一絲曙光-「你可能有僵直性脊椎炎」祂如是說。是的,不是我刻意逃兵,而是六年前就開始時好時壞的腳痛最近又惡化,連走路都一拐一拐,根本不可能跑步,去當兵難道是貪圖一群男人的服侍?我可沒有這種性向,不過已經收到兵單了。在劉醫師的建議下,我到署立彰化醫院掛了免疫風濕科,免疫風濕科高醫師看了我之前在復健科檢查時拍的腰部X光後就說「你這個有問題耶」,當下就確定是僵直性脊椎炎,還開了診斷書。由於沒聽說過家人有僵直性脊椎炎的病史,高醫師建議我抽血檢查看是不是有這樣的抗原,所以那天抽完血才回家。(相當感謝這兩位細心的醫師,在此之前我看過10個以上的醫師,包括骨科、復健科、神經內科,其中也不乏有名的台大與台北馬偕,都沒診斷出來,甚至一副質疑我想逃兵的樣子。)

目前僵直性脊椎炎的免役標準為
1. 血清檢察HLA-B27陽性,骨盆X光檢查有單側二級以上薦腸關節炎
2. 血清檢察HLA-B27陰性,骨盆X光檢查有單側第三級以上或雙側第二級以上之薦腸關節炎

2010年10月12日 星期二

Overloading VS. Overriding

剛學物件導向程式設計的人常常把Overriding與Overloading兩個詞搞混,不僅是因為這兩個單字看起來很像,連使用方式都很類似。於是愛用國貨的中文書市場為了造福國內廣大英文辨識力不良族群,推出了以下絕妙的通俗翻譯:
  • Overloading=多載。
  • Overriding=覆載。
這翻譯絕妙的地方在於繼承了英文單字看起來很類似的特性,從字面來看也很難明白其中意涵,於是還是讓人看得霧煞煞,令人不禁想豎起大拇指大讚「這就是物件導向啊!」
就在這個時候,國內的大學生發現這可能是教授為了能當更多人而玩的文字遊戲,又或者是當初的翻譯者害怕太多人學會之後飯碗不保,刻意翻得很奇怪,於是提出更明確的翻譯方式以自救:
  • Overloading = 給予太多工作 (load多到太over)。
  • Overriding =忤逆家長 (也就是ride在家長頭上太over的意思)。

2010年6月2日 星期三

最難得的是有水準的觀眾

如果有人問我哪一場音樂表演是令我印象最深刻的,我的回答大概是那場辦在台中洲際棒球場的張藝謀執導的《杜蘭朵公主》,因為那天的觀眾實在不是烏合之眾足以形容的。以觀眾數量看來票賣得不錯,但似乎就是很少人注意到節目開始前就不斷宣導的「表演期間請勿拍照」,也似乎很少人真正想去欣賞這場歌劇,全場一團團閃光燈的閃爍以及一陣陣觀眾的討論聲,引人注目的程度甚至快壓過台上的表演與樂聲。不關手機的人很多,表演中手機拿起來直接放聲高談自己多麼無法接受歌劇的也是大有人在。我正前方與右方的觀眾忙著拍照,我正後方的觀眾則在聊著與演出不太相關的事情,在這種三面受敵的情況下,要人如何能去享受一齣歌劇?在演出結束,演員出來謝幕之前,一群人開始離場,彷彿從一開始就期待這一刻的到來,留著為優秀的導演、演唱家及指揮鼓掌喝采的,大約只有三分之二。相信我,那場演出的水準絕對沒有差到這種程度,甚至男高音、指揮家以及兒童合唱團的水準都是相當罕見的,沒有理由令觀眾反感到這種程度。後來我在離開時聽見幾個觀眾說:「要欣賞歌劇這種東西,真的是要有興趣。」原來他們並不是因為對歌劇有興趣才來欣賞這場演出。我相信這麼多觀眾對這場演出不滿意,主要是因為演出與他們的期望不一致。也許他們真正想要看的是音樂劇,而在他們人生中還沒有人告訴他們音樂劇與歌劇是不一樣的東西。另外一個原因也許是這場演出的贊助商太多,這些贊助商都拿了很多貴賓券,而最後拿到這些貴賓券的人究竟對節目感不感興趣就看運氣。當我在上層的位置看到貴賓券的區域呈現出類似銀河的景象,我想他們大多數應該都只是來湊熱鬧的。於是演出的水準與觀眾的能耐呈現強烈的對比,對表演藝術而言,沒什麼能比這更糟的了。

2010年3月5日 星期五

創作與惡搞的差別

對我而言,惡搞只是一種創作方式,因此對於弄清楚創作與惡搞的差別,一時間我還真不知該說什麼才好,於是乎我求助了維基百科。它給了我以下解釋:
  • 惡搞文化,指的是對嚴肅主題加以解構,從而建構出喜劇或諷刺效果的胡鬧娛樂文化。常見形式是將一些既成話題,節目等改編後再次發布,屬於二次創作的其中一手法。惡搞在當代流行文化中很常見。
這串字裡最吸引我的兩個字是「解構」,看到這兩個字,我才終於了解為什麼我搞不清楚差別在哪裡。

2010年1月30日 星期六

用Java盡大學生的義務

最近總是出現一堆新聞在報導時下大學生的醜態,舉凡遲到、翹課、上課吃雞腿、考試作弊等等。每次看到這類新聞,都不禁為這些大學同學感到惋惜。但是,請不要誤會,我寫這篇文章的目的絕不是要炫耀自己是個努力用功人見人愛的乖學生,而是這些醜態我在大學前就全都幹過了。我惋惜的不是他們和我一樣糟,而是可憐他們運氣差,剛好符合媒體的胃口而被爆料。
身為一個大學生,我大約知道公認的義務,這些義務不外乎:
  1. 記得上課
  2. 但是不要遲到
  3. 過程中保持清醒
  4. 再餓也不能吃雞腿
  5. 沒雞腿吃也要認真聽課
  6. 快被二一,仍保持考試不作弊
  7. 就算作業完全不會寫,也不能抄襲,而且要準時交
大學生簡直是聖人了!

對我這個糟糕大學生而言,只有兩件事情是必須做的:
  1. 註冊
  2. 選課
這麼說或許漠視了全天下負責繳學費的父母與全天下努力教書的老師們的期望。確實,每個父母都希望孩子成龍成鳳;每個教授都希望學生認真聽課;每個公民都希望學生(尤其是那些念到台大醫科的)不要浪費社會資源。然而,我認為大學不僅是一個鑽研學術的機構,更是個學習如何抉擇的小社會:除了註冊與選課外,其他義務應是學生了解做的理由後才選擇去做的,否則,我們的教育還是在製造同樣思維的人,與我們一直要擺脫的填鴨式教育並無什麼不同。

看到這裡,一定有人開始懷疑本文與主題的關係。但是,別著急,按照慣例,接下來的文章一定會努力拗回正題的!
我真的要說的是,每次我在執行「選課」這項大學生的義務時,總是會遇到相當多競爭者要搶同一門課,而且好死不死每次運氣都很差,抽籤的結果總是沒選到,只好在加退選階段等選上的人退選。然而加退選階段搶課的人還是很多,每次為了選到一門課,都得坐在電腦前不斷按加選按鈕,浪費了相當多時間。為了剷除那些妨礙我盡大學生義務的絆腳石,上次選課時,我終於下定決心寫出一個滑鼠連點程式(然而,當程式一完成,開始讀秒準備發射時,我才發現想選的課已經選上了..囧rz)。

本文要教大家做的就是如何以Java實作滑鼠連點程式。程式碼十分短,大約只要15行。
在此先介紹Java AWT裡的一個稱為Robot的類別,大家可別看到名字就以為是給搜尋引擎用來做網路蜘蛛(Web Spider)的,事實上我有個不懂裝懂的同學就這麼想;也不要認為它是用來做機器人行動規畫的工具,它和真實的機器人一點關係也沒有。它其實是個用來模擬圖形介面使用者動作的類別。換句話說,它可以模擬使用者滑鼠移動、按下鍵盤或滑鼠按鍵,並且可以擷取目前螢幕的畫面。

Robot類別提供以下幾個方法來模擬滑鼠動作:
  • public void mouseMove(int x, int y); // x,y為螢幕像素位置

  • public void mousePress(int buttons);
    /* buttons可以是以下的值
    * java.awt.event.InputEvent.BUTTON1_MASK 代表左鍵
    * java.awt.event.InputEvent.BUTTON2_MASK 代表中鍵
    * java.awt.event.InputEvent.BUTTON3_MASK 代表右鍵*/

  • public void mouseRelease(int buttons); //同上

  • public void mouseWheel(int wheelAmt);
    /* wheelAmt為滑鼠滾輪的轉動量,
    * 正值為向前轉,負值為向後轉。*/

注意按下(press)滑鼠按鍵後,必須放開(release)才是一次點擊(click)。若忘了把按鍵放開,按鍵會一直被按著。

連點程式全部的程式碼只有這樣:
import java.awt.*;
import java.awt.event.*;
class RobotTest{
public static void main(String[] arg)throws AWTException,
InterruptedException{
Robot robot = new Robot();
while(true){
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
Thread.sleep(3000); // 讓執行緒睡3秒
}
}
}

注意在每次點擊必須讓執行緒暫停一段時間,否則滑鼠會不停點擊,到時連這個連點程式都很難關掉。

這裡只介紹到Robot類別的一種應用,事實上他能夠實現相當多功能,諸如螢幕放大鏡、螢幕畫面擷取器之類的小程式。
有興趣深入的人可以參考Java API的介紹:Robot

2010年1月24日 星期日

正名運動:Java與JavaScript不是直系血親

有相當多人認為Java與JavaScript存在著某種關係,更有人認為JavaScript其實是Java的變形體,然而若對JavaScript的歷史稍有研究,你可以發現JavaScript與Java可以說是一點關係也沒有,全都是名字與刻意模仿的命名誤導了大家。
JavaScript的原名為LiveScript,由Brendan Eich所設計,1996年誕生於網景(Netscape)公司。當時的網景公司是瀏覽器的霸主,為了推行這種新穎的動態網頁技術而和昇陽(Sun Mirco Systems)公司達成協議,將LiveScript更名為JavaScript(因為昇陽的Java Applet在當時曾風光一時,藉由Java的名號推廣新技術比較容易。),網景公司則以幫助昇陽研發Navigator瀏覽器上的虛擬機器(VM)做為交換條件,也因此JavaScript的商標權目前是屬於昇陽。換句話說,JavaScript最多只是Java的乾兒子,何況在程式語言的系譜上,Java與JavaScript的關係也不在同一條血脈上。Java在特性上承繼了C++與SmallTalk,有著封裝、繼承與多型三種傳統物件導向程式語言的主要特徵,反觀JavaScript,嚴格來說沒有封裝與繼承,更不用說多型。JavaScript雖然有成員變數與成員函式的觀念,但與其說那是封裝,不如說是沒有存取權限控制的關聯式陣列(associative array),例如在Java中宣告一個類別:

class JavaClass{
private String name;
int id;
}

String型態的name變數只能在其宣告的類別內存取,整數型態的id變數則可以在所屬package內存取,然而在JavaScript,當你這樣宣告:

function JavaScriptClass(){
this.name = "Default";
this.id = 0;
}

※JavaScript的實體建構函式(建構子)與一般函式是沒有分別的,因此當下文提到建構子,即是指程式中專門用來建構實體(new)的函式。

name和id的存取是不用考慮權限的,甚至可以用以下兩種方式來存取實體內的成員變數:

  1. var instance = new JavaScriptClass();
    instance["name"]; // = "Default"
    instance["id"]; // = 0

  2. var instance = new JavaScriptClass();
    instance.name; // = "Default"
    instance.id; // = 0

換句話說,instance變數可被看成含有name與id成員變數的物件,也可以視為是一個以字串為鍵(key)的關聯式陣列,建構子的宣告只是幫助我們將陣列內的鍵與值先做初始化。
在JavaScript中,要製造出多個有相同成員變數與成員函式的物件,宣告一個function來作為實體的建構子會比較方便,但如果實體只會被使用一次,也可以用下列的方式來宣告:

  1. var instance = new Object();
    instance.name = "JavaScript";
    instance.id = 1;
  2. // 或者是 var instance = new Array();
    var instance = new Object();
    instance["name"] = "JavaScript";

  3. instance = {
    name : "JavaScript",
    id : 3
    }

習慣寫Java的人可能會覺得第一種寫法很弔詭,但在JavaScript裡確實是行得通的:即使Object的實體裡不存在name與id變數,只要有指派,變數就自動被配置了。也因此第二種寫法看起來較直覺,只要把實體全部看成關聯式陣列(或 Hash Table)就行了。第三種寫法是以JSON(JavaScript Object Notation)的方式寫成,但如果你用過Ruby的關聯式陣列,就會覺得這種寫法用來宣告關聯式陣列是再正常不過了。
到這裡,很明顯可以看出Java與JavaScript的不同之處,然而他們最大的不同是在函式的特性:JavaScript的函式本質上仍屬於關聯式陣列的值,而非任何實體的成員函式。例如,當你在一個建構函式內宣告一個成員函式:

function JavaScriptClass(){
this.name = "Hello";
this.displayName = function(){
alert(this.name);
}
}

var obj = new JavaScriptClass();
obj.displayName();

雖然以上的程式會確實會彈出一個警告視窗顯示"Hello",但如果你的程式是像這樣寫,也會有同樣的結果:
function func(){
alert(this.name);
}

function JavaScriptClass(){
this.name = "Hello";
}

var obj = new JavaScriptClass();
obj.displayName = func;
obj.displayName();

※注意obj.displayName = func的func後面沒有大括號,懂C的人可以把它視為函式指標。

知道原因嗎?obj實體的displayName函式並不是成員函式,因此displayName函式中的this並不是函式所屬的物件,而是函式的「呼叫者」。換句話說,obj.displayName()呼叫時,displayName函式是到呼叫者(obj)去尋找name變數。下列寫法應會比較容易理解:
function func(){
alert(this.name);
}

function JavaScriptClass(){
this.name = "Hello";
}

var obj = new JavaScriptClass();
func.call(obj);

在JavaScript中,當一個函式被呼叫,實際上是呼叫函式物件中的call函式。call函式的第一個參數為該函式物件的呼叫者,也就是說函式內的this關鍵字會關聯到call函式的第一個參數,因此當func函式執行this.name變數時,實際上是到obj實體尋找name變數。了解其中的機制後,就可以用JSON當作參數傳入函式而得到同樣的效果:
function func(){
alert(this.name);
}

func.call({name:"Hello"});

JavaScript就是一個原理如此簡單的語言!

翻開族譜,JavaScript真正的祖宗其實是Self與Scheme,才會有像prototype與第一級函式的特性。那麼,誰才是Java真正的兒子?除了源自於Java的Groovy外,最像Java的語言應是那位認錯祖宗的C#吧!

2010年1月23日 星期六

淺談設計模式之觀察者模式

小明是個勤奮的台科大學生,平時相當努力在做一些與課業毫不相干的事情,例如在計算機網路期末考的前一晚還在研究Java的反射機制。如此的勤奮,以至於他在學期結束時發現有半數以上的老師不打算讓他過關。就這樣,小明被二一了。被二一的小明在學期結束後恐懼於父母的鞭打,隱藏自己提早畢業的事實,藉故留在台北,打算就這樣瞞天過海不理人間世事,用心鑽研Java。不料,紙終究包不住火,某日小明在宿舍享受著Java優雅的語法時,一陣急促的敲門聲打斷了他的思緒。小明不耐煩地開了門,還沒來得及看清楚來訪者的臉之前就先被一記大腳踹到牆邊,抬頭一看才驚見自己青筋暴現面目猙獰的老爸。這時,小明才終於意會到,老爸是自己在學資料的觀察者之一,同樣會被告知自己被二一的消息...
儘管可能有人好奇小明的下場,這畢竟不是這篇文章要講述的重點,這篇文章的重點是「觀察者模式」的實作,也就是說明在物件導向程式設計中,如何實作物件與物件的註冊與通知機制,而上述的人倫悲劇即是我們要實作的情境。小明的下場如何,請看倌自行想像。

觀察者模式包含兩種角色:「觀察者(Observer)」、「被觀察者(Observable)」。觀察者與被觀察者都能夠有多個:一個「被觀察者」能夠被許多「觀察者」觀察,一個「觀察者」也能一次觀察多個「被觀察者」。「觀察者」必須向「被觀察者」註冊,「被觀察者」在狀態改變或某些條件滿足時則告知「觀察者」,如下圖所示。


觀察者必須提供一個方法(update)讓被觀察者在狀態更新時呼叫,被觀察者也必須提供觀察者註冊(addObserver)及移除(removeObserver)的方法,如下介面所定義:
interface Observer{
/** 通知此觀察者*/
public void update(String message);
}

interface Observable{
/** 增加觀察者 */
public void addObserver(Observer observer);
/** 移除觀察者*/
public void removeObserver(Observer observer);
}
Observable介面的addObserver方法雖暗示了實作類別必須使用陣列或串列來儲存多個觀察者實體,但這並非強制性的,一個被觀察者也能只有一個觀察者,取決於整體的設計。Observer介面中的update方法所傳遞的參數也取決於使用的場合。若要設計出較一般化的Observer模式的實作,可以參考Java API中的Observer介面和Observable類別。

在實作上述情境前,
我們先整理出幾個步驟:首先,我們將「小明」與「小明的爸爸」註冊為「小明在學資料」的觀察者。其次,設定「小明在學資料」裡的分數。然後,學期結束時,「小明在學資料」會結算被當科目,確認小明有沒有被二一。最後,學期結束時,「小明的在學資料」會自動通知「小明」與「小明的爸爸」,小明是否有被二一。
以下是主程式:
class Main{
public static void main(String[] arg){
//小明的在學資料
StudentData studentData = new StudentData("小明");

People ming = new People("小明本人");
People father = new People("小明的爸爸");
//學生資料加入觀察者
studentData.addObserver(ming);
studentData.addObserver(father);

//設定成績
studentData.setScore("微積分", 59);
studentData.setScore("物理", 60);
studentData.setScore("計算機網路", 54);

// 學期結束,結算被當科目
studentData.validateCredits();
}
}
接下來是表示人(小明本人、小明的爸爸)的類別,實作觀察者介面:
class People implements Observer{
private String name;

public People(String name){
this.name = name;
}

public void update(String message){
System.out.println(
name + " 收到["+ message +"]的訊息。");

}
}
最後是表示「學生在學資料」的類別,實作被觀察者介面:
/** 在學學生資料*/
class StudentData implements Observable{
private String name;
/** 使用LinkedList來儲存多個觀察者。 */
private List< Observer> observers =
new LinkedList<Observer >();
private HashMap< String,Integer> scores =
new HashMap<String, Integer>();

public StudentData(String name){
this.name = name;
}

public String getName(){
return name;
}
/** 設定成績*/
public void setScore(String subject, int score){
scores.put(subject, score);
}

/** 結算學分*/
public void validateCredits(){
int totalSubject = scores.size(); //全部科目數
int failSubject = 0; //被當的科目數

/** 尋訪整個HashMap,取出所有的值 */
for(int score: scores.values()){
//不到60分,累加被當的科目數
if( score < 60)
failSubject++;
}
//若被當的科目數大於總科目數的一半
if( (totalSubject / 2) < failSubject )
notifyObservers("下學期不用繳註冊費。");
else
notifyObservers("下學期記得繳註冊費。");

}

/** 通知所有觀察者*/
public void notifyObservers(String msg){
/** 尋訪整個List,取出所有觀察者 */
for(Observer observer : observers)
observer.update(msg);
}
/** 新增觀察者*/
public void addObserver(Observer observer){
observers.add(observer);
}
/** 移除觀察者*/
public void removeObserver(Observer observer){
observers.remove(observer);
}
}
執行結果如下:
小明本人 收到[下學期不用繳註冊費。]的訊息。
小明的爸爸 收到[下學期不用繳註冊費。]的訊息。

Observer模式的原理其實很簡單:只要在被觀察者的狀態被改變(ex.set方法被呼叫)或條件滿足時,經由Observer介面所定義的方法,將狀態改變的消息傳遞給已註冊的觀察者們。然而,Observer模式有相當多應用,例如著名的MVC架構模式即是根基於此模式。關於Observer模式的各種應用,將在之後的文章提到,請耐心等待。