2020年2月17日 星期一

美國醫療保險的神奇玩法

美國醫療保險很機車,通常要先付到超過自付額 (deductible) 的金額,保險才會開始給付,每次看病還得負擔一定的比例 (通常是 10%),直到自付的錢達到年度自付額上限 (Out of Pocket Maximum) ,保險才會全額給付。有一些保險方案是不管自付額 ,每次看醫生都交固定金額的共同承擔額 (co-pay),但通常保費會高出不少,而且一些情況還是需要自付 10%(如手術),所以方案選擇就見仁見智。剛加入現在的公司時,因為覺得會常常跑醫院,一開始是選擇交共同承擔額的方案,之後算了一下開銷,發現如果真的常跑醫院,換成有自付額的方案反而更划算,因為該方案有包含 High Spending Account (HSA) 帳戶,公司每年發第一份薪水時會放錢進去,可以用在大部分醫療用途的開銷;如果無論如何都會花到年度上限 ,有補貼的情況下顯然更划算。

自從我去年把醫療保險換成用 HSA ,就發現一個很有趣的現象。

因為我僵直性脊椎炎需要打生物製劑,每個月都得訂藥寄到家裡。生物製劑的原價非常貴,即使有保險公司殺價,費用還是大約 $5000 鎂,要一直付到超過自付額,保險才會開始給付。然而並不是每個人都可以一次拿出 $5000 多鎂,這個藥實際上也沒有那麼貴,只是藥商知道保險公司會殺價,就先把價格提高,導致很多特殊藥物都是天價。廠商也怕患者因為價格過高,不願意買自己的藥,這樣自己一毛都賺不到,於是想出了一個貼心的辦法:幫患者負擔達到自付額之前的藥費。一旦達到自付額,之後的藥費大部分就會由保險買單,藥商還會再補助一部分,需要付的錢非常少。以我的例子來說,之後每次領藥,廠商都會跟保險收大約 $5000 鎂,而這一類藥物的共同承擔額是 $30,廠商會再補助 $25,等於我一個月只要付 $5。另外,因為藥費也會算進年度自付額上限,要達到上限完全不是難事,達到之後甚至連藥錢都不用付了,看醫生也是保險全額給付。

我的保險方案是整個家庭自付額 $3000 ,年度自付額上限 $4000 。雖然很莫名其妙的,由廠商付錢的第一次,藥竟然只要 $2700 左右 (大概是因為自己賣給自己,用的是成本價),沒有達到自付額,如果在下次訂藥之前需要看醫生的話,就得自己負擔,但因為公司會放 $1500 到 HSA 帳戶,足夠讓我花到年度自付額上限了,等於一整年看病買藥只需要交每個月的保險費, HSA 帳戶剩下的錢,還可以拿去看牙醫 (美國的牙醫保險和醫療保險通常是分開的)。

我曾經問過藥商代表,為什麼不乾脆把藥價降低,才不用這麼迂迴?她告訴我,如果這樣做,保險公司還是會把藥價砍低,並且得到大部分的利潤,而病人付的錢還是一樣。不得不說,美國的醫療保險雖然糟糕,也是有它適合的玩法。

2013年1月2日 星期三

被汙名化的僵直性脊椎炎

本文主要是用來批判那些,質疑僵直性脊椎炎病友逃兵的無聊酸民。如果你是在找關於僵直性脊椎炎免役體位的資訊,上一篇文章會比較適合你。


很少有病痛所受到的誤解與質疑,比僵直性脊椎炎還要多,甚至被汙名化成一種逃兵的手段。從最近的一篇報導「僵直性脊椎炎比經痛更痛!周杰倫:有人說我閃兵就生氣」下面的留言,可以看出網友對於患者可以練出八塊肌、打籃球及拍武打片這件事,有極大的心理不平衡。他們質疑周杰倫(以下稱周董)以造假病歷的手段逃兵,或認為僵直性脊椎炎患者,根本是可以當兵,卻選擇接受免役體位好「閃兵」。雖然我很希望將一切歸咎於網友們的無知,多數網友卻壓根不理會其他病友的解釋,只把焦點放在「可以練八塊肌,卻沒有當兵」的矛盾上,好像被判為免役體位就必須表現得手無縛雞之力,否則是裝病閃兵。我不喜歡周董,只是就病友的立場,我對這種現象感到非常憤怒,所以決定寫下這篇文章,來回覆一些疑問和網友們的無理取鬧。

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年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月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模式的各種應用,將在之後的文章提到,請耐心等待。

2009年7月23日 星期四

What is “this”?

What is “this”?這是許多物件導向程式語言初學者的疑問。
在許多物件導向語言中都存在著「this」關鍵字,舉凡Java、C++、C#都有這樣一個謎一般的關鍵字,在VB.Net與Ruby中則是以「self」之名存在著。某些非物件導向的程式語言如Javascript,甚至也提供「this」來模擬物件導向的特性。
究竟「this」是天使的化身,還是地獄的使者?是什麼樣的力量促使他在物件導向的世界裡屹立不搖?其實,它沒那麼偉大,充其量只是個「第一人稱代名詞」。
在解釋它存在的必要之前,我們必須先為一個人生難題找出解答:如何在不說「我」的情況下,用合理的文法表達「教授把我當了」?如果你說「教授把XXX當了」,這樣的句子很奇怪,像是說另一個和你同名同姓還被你爆料的可憐同學。如果你說「教授當了」,請問教授到底當了誰?如果你說「被教授當了」,聽起來合理,但這其實是把「我」省略的慣用句型,文法上不合理,因為缺乏主詞。
不知道有沒有人可以找出答案,如果有請留言告訴我。無論如何,代名詞不是我的重點,我只是想證實第一人稱代名詞的必要性:若沒有第一人稱代名詞,我們很難用語言表達自己的存在,也很難做第一人稱敘述。
在物件導向的世界裡也是一樣的。由於一個類別可以產生許多實體,就必須用一個代名詞來讓個別實體表達「我」,以區分和其他實體的不同。一個類別的實體就是用「this」表達「我」。

例如以下程式碼:
class Student{
private int score;
Student(int s){
score = s; //A
}
public void say(){
if( this.score > 59) // B
System.out.println("教授讓我過了!");
else
System.out.println("教授把我當了!");
}
public static void main(String[] arg){
Student 好學生 = new Student(80);
Student 混學生 = new Student(40);
好學生.say();
混學生.say();
}
}

在main中,產生兩個Student類別的實體,兩個實體say的結果不一樣,因為this.score的值不一樣。這樣就可以看出,每個new出來的實體都有自己的this,代表各自的實體。

※B的this是可以省略的,因為是在同一個類別,Java編譯器會在編譯時自動加入this。如A的score也可以改為this.score。

其實看下圖就一目了然:



main方法的Stack Frame中,「好學生」與「混學生」參照分別參考到不同的Student實體,兩個Student實體的this參照也分別參考到自己從屬的實體。如此一來,每個實體就可以用this表達「我」。例如,我的score就是this.score。
那麼你可能會有個疑惑:難道「this」存在的意義只是為了表達像this.score這種可以省略的語法嗎?「this」真正的好處在於,實體可以將自己傳遞給其他實體。

為了解釋這點,我們定義一個Professor類別,並在Student類別裡加入getScore方法,如下:
class Professor{
public void grade(Student student){
student.score = (int)(Math.random()*101);
}
}
class Student{
...
Professor 叫獸 = new Professor();
public int getScore(){
叫獸.grade(this); // C
return score;
}

public static void main(String[] arg){
Student 羔羊 = new Student(0);
羔羊.getScore();
羔羊.say();
}
}

我們先產生一個Student的實體「羔羊」,並呼叫getScore方法。呼叫時,羔羊的實體就會傳入叫獸.grade()方法,並設定score。C的部分,就是Student實體將自身傳入叫獸.grade()方法的部分。其實這行程式碼,翻成中文就是「叫獸給我打分數。」「this」就是「我」!

2009年7月8日 星期三

執行緒同步化之時間怪獸

昨天終於把專題Server端的Database Connection Pool改好了。原本以為是Thread之間的Race condition,造成資料庫記錄取得之前,連線就被回收連線的Thread給close掉。後來才發現是自己沒把 java.sql.Connection的autoCommit設為false,造成Connection在還沒有被commit的狀態下就被放回 Connection Pool裡,時間一到就帶著沒送出的資料一起被回收。問題根本不在Thread的同步,而是我幾乎忘記有autoCommit這東西。
不過,問題改好之後,發現更大的問題:速度超慢。更離譜的是,使用單執行緒的方式執行,竟然還比有快取的多執行緒池快10倍以上。昨天晚上看著 Profiler的分析到凌晨4點,仍然找不出問題在哪,只看到執行緒wait的頻率很高。白天上課一直恍神,完全無法理解老師在上什麼,不過可能也因為恍神太多,讓我在走回宿舍的路上精神飽滿,聽著Claudio Abbado指揮Lucerne Festival Orchestra演出的Mahler交響曲第二號,靈機一動想到了一個可能性:「會不會有人拿著互斥鎖,做一些很耗時間的工作?」回到宿舍看code,果如所料!

試比較下列這兩段程式碼,你比較喜歡哪一種寫法?

Code 1.
synchronized(this){
if(queue.isEmpty()){
if(connectionCount.get()< maxConnectionCount){
conn = new
DBConnection(this,getRawConnection());

connectionCount.incrementAndGet();
}
}
else{...}
}
Code 2.
synchronized(this){
if(queue.isEmpty()){
if(connectionCount.get()< maxConnectionCount){
newConn = true;
connectionCount.incrementAndGet();
}
}
else{...}
}

if(newConn)
conn = new DBConnection(this,getRawConnection());
一般而言,多數人都會選Code 1.,因為兩段程式碼功能一樣,Code1卻顯得比較簡潔直觀。然而,Code1就是我一開始寫的,很花時間的程式碼。

Code2和Code1最大的差別在於Code 2把 conn = new DBConnection(this,getRawConnection()); 從同步區塊取出。這麼做會讓效能增加數倍的原因是getRawConnection()操作太花時間。如果放在同步區塊,代表一次只有1個執行緒能夠呼叫這個方法。這個方法一次得花10秒鐘的時間去和遠端資料庫連繫,如果一次只有1個執行緒呼叫,則20個執行緒至少得花200秒。Code2的getRawConnection()呼叫因為是在同步區塊之外,能夠讓多個執行緒同時取用,就沒有這種問題。
這次的教訓告訴我們,可以放在同步區塊的程式碼越少越好。至少,非必要的話,不要把怪獸放進去。

2009年7月7日 星期二

Digital Sun

今天終於把事情忙完,心血來潮就把上次的Digital Chaos拿來修改,想說改成動態的形式。後來想到不錯的點子,讓圖的半徑交互遞增遞減,動態畫出一張類似太陽的圖形,姑且就稱它為Digital Sun吧。


這是截圖,所以沒有動畫。在Processing上執行會是圓心到圓周的文字交替增加,形成中心越來越密集的太陽。

後來又加上一個細微旋轉,使圖形變成颶風:



程式碼:
int lineSize = 400;
int wide = 5;
int hei = 8;
int fontSize = 16;
int shadow_shift = 2; //文字殘影偏移量
int amount = 50;
int radius = amount;
float angel = 0; // 一行的角度偏移量,設成0.3會變颶風。
PFont font;
boolean sw = true;
void setup(){
size(800,800);
font = createFont("Geogreia",fontSize);
textFont(font);
background(0);
}
void draw(){
if(radius == -amount)
sw = true;
if(radius == amount)
sw = false;
radius += sw? 2:-2;
translate(width/2,height/2);
for(int i = 1 ; i <= 10*abs(radius); i+=5){
int rand = (int)random(2);
int num = (int)random(2);
int shift = (int)random(8) * (i/lineSize >=1 ? -1:1);
int x = i%lineSize+i/100*wide + shift;
int y = (i/lineSize)*hei+ shift;
float trans = 160+95*(shift/7);
fill(255,trans);
rotate((rand == 0? -shift:shift));
rotate(angel);
text(String.valueOf(num), x , y);
if(shadow_shift != 0){
fill(255,trans*0.5);
text(String.valueOf(num), x+shadow_shift,y+shadow_shift);
}
textFont(font,fontSize+(rand == 0 ? -shift:shift));
}
translate(width/-2,height/-2);
}
後來仔細想想,也許它該叫「眾妙之門」。

「無,為天地之始;有,為萬物之母。故常無,欲以觀其妙;常有,欲以觀其徼。此兩者,同出而異名,同謂之玄,玄之又玄,眾妙之門。」-《道德經》

2009年7月1日 星期三

物件繼承的物件觀點-轉型篇

這篇文章是物件繼承的物件觀點一文的續集,將用物件的功能觀點圖來說明物件的強制轉型何時會成功。
首先,我們先釐清一些有關於物件轉型的概念。
在Java中,所有子類別的物件不需要強制轉型就能夠隱含轉換為父類別型態。因此以下程式碼皆能夠通過編譯,也能正常執行。

女人 欣郁 = new 女人();
人類 路人甲 = 欣郁;

人類 阿呆 = new 男人();



2009年6月30日 星期二

物件繼承的物件觀點

「欣郁是個人。」
「欣郁生了孩子。」
欣郁生了個孩子,表示欣郁是女人,會生孩子很正常。即使這兩句話並沒有提到欣郁是個女人,正常人也可以藉由常理推敲出最有可能的結論。但是,如果不加入過去的經驗,純粹從以上兩句話來推敲,則會得到「人會生孩子」這種以偏概全的結論。正常人會從常識中選擇合理的解釋,但對沒有常識的程式語言編譯器而言,這種結論是上述兩句話的正解。這就是自然語言與程式語言之間的鴻溝:有些東西你認為是常識,編譯器卻不懂你的意思。
這篇文章要談的是,你如何正確地將上述兩句話依照物件繼承的類別觀點一文中的類別圖規格轉換成Java語言。
如果我們直接將上述兩句話逐句翻譯成Java程式碼:

「欣郁是個人。」 → 人類 欣郁 = new 人類();
「欣郁生了孩子。」 →
人類 孩子 = 欣郁.生產();

這兩行程式碼大有問題!
首先,我們並沒有說清楚欣郁究竟是男人還是女人。如果欣郁是男人,那麼欣郁.生產()就不合理。因此我們必須把程式碼改為:

人類 欣郁 = new 女人();
人類 孩子 = 欣郁.生產();

這段程式碼看起來較合理,起碼我們已經說明了欣郁是個女人的事實。然而,這樣的程式碼仍然無法編譯成功!
大家可能認為現在欣郁「事實上」是個女人,因此欣郁.生產()理應合法,那為什麼仍然無法編譯通過?理由是,當你把「欣郁」變數宣告成「人類」型態時,我們就把欣郁的「女人」的特性忽略掉了。這就好像我們說「人類是一種動物,所以人類會覓食」時,是把人類一般化成動物,而忽略掉人類獨有的特性(例如交談)。反之,我們不會說「人類是一種動物,所以人類會交談」因為這句話代表「動物會交談,而人類是一種動物,所以人類會交談」這樣的邏輯當然是錯誤的。同樣的道理,「女人會生產」並不代表人類都會生產。現在如果把欣郁一般化成人類看待,則我們就不能去看她獨有的「生產」功能,只能看到人類共有的「交談」功能。人類的定義裡並沒有生產的功能,而欣郁是人類,欣郁.生產()當然不合理。要讓這段程式碼能夠成功編譯,我們必須把欣郁當成女人看待,因此修改成:

女人 欣郁 = new 女人();
人類 孩子 = 欣郁.生產();

這樣的程式碼就合法了。
要理解上面的解釋或許很費腦筋,畢竟不是每個人的邏輯都相當清楚,幸好我們可以使用物件繼承的類別觀點一文中的功能觀點來清楚解釋每一個步驟。

new 人類();

可以用下圖表示:

※表示物件的功能觀點圖為了與表示類別的功能觀點圖作區分,使用較具立體感的顏色來表達「照類別規格產生的實體物件」的效果。
※本文將不會去區別參照與指標,因「指標」講起來比較傳神。
人類 欣郁 = new 人類();

則可以表達為「欣郁指標」指向「人類物件」:

現在就可以清楚看到欣郁.生產()是不合法的,因為圖中的人類物件根本沒有「生產」功能。

那麼,如果是這行程式碼,該怎麼解釋呢?

人類 欣郁 = new 女人();

如下圖:



※在此不管JVM內是否這麼實作,先從邏輯的角度剖析。

new 女人()產生的雖是女人物件,人類型態的欣郁指標卻是指向女人物件中屬於人類的部分,欣郁.生產()也就不合法,因為指標所指向的物件仍然不具有「生產」功能。
根據上述畫圖的原則,很自然可以推出下面這行程式碼代表的意義:

女人 欣郁 = new 女人();



由於欣郁指標現在是女人型態,指向女人物件的整個區塊,欣郁.生產()就變得合法。
同樣的道理也能套用在物件的轉型上,將在下篇文章說明。

2009年6月28日 星期日

物件繼承的類別觀點

從物件導向的術語來看,物件繼承是一種 「is a」的關係,但我個人比較偏好解釋成「is a kind of」,也就是「是一種」的關係,例如,男人是一種人類;女人是一種人類;人類是一種動物。這樣的形容比較精確,否則很容易和物件混淆。例如我們說,「欣郁是個人」,並不是說欣郁是人的子類別,而是說欣郁是人的一個物件,因此有必要釐清語意上的模糊。
將「男人是一種人類」、「女人是一種人類」、「人類是一種動物」的關係與特性以UML類別圖描述,則如下:


從類別圖的表示中,我們可以很清楚看見類別的階層關係。

一般而言,我們會很直觀的把上述類別圖轉換為如下的定義域圖,將每一個類別的定義視為一個集合。
但是我得告訴你一個壞消息:從這種角度思考,對物件導向概念的釐清沒什麼幫助。原因是:你最多只能從這張圖看出男人與女人處於類別階層的何種位階。物件導向著重的是類別所提供的操作,因此你最好將你的角度顛倒過來,像下圖一樣改為從功能性的角度去思考每個類別提供什麼樣的操作。


從這張圖你可以看見,男人與女人的功能裡包含著男人或女人特有的功能,加上人類及動物的功能。這樣你就不會期望男人與女人在交談上必須有一樣的表現,因為雖然交談是「人類」類別提供的功能,現在男人與女人都有自己的「人類」功能區塊,雖然都有「人類」的功能,卻可以提供不同的實作。這種Overriding(覆載)的概念,從功能性的角度才看得出來。

Dependency Injection

Dependency Injection(依賴注入,簡稱DI)是物件導向技術中常被用來降低模組間耦合度的做法,Martin Fowler首先在他的Inversion of Control Containers and the Dependency Injection pattern一文中使用了這個詞,並定義了三種型式的DI,分別是type 1:Interface Injection、type 2:Setter Injection及type 3:Constructor Injection,後來又由picocontainer定義了更多種類的DI。那些DI,其實我也不知道詳細情形為何,也懶得知道。無論如何,DI的用途就是「將模組之間的相依性從程式實作中抽離」。這樣說或許很抽象,但我們生活中其實充滿DI的影子。例如,每個MP3 Player一定需要電池才能運作,有些MP3的電池是內建的,無法更換;有些使用3號或4號鹼性電池,替換很方便。使用內建電池的MP3 Player,由於電池相依於MP3 Player的內部實作,如果之後電池壞掉,就只能買一台新的。而使用標準電池的MP3 Player,即使電池壞掉,只要到7-11買一副新的電池,馬上就能夠使用。同樣的道理也適用於軟體設計:萬一某天發現軟體中的一個類別有bug,究竟是要整個軟體都改過,還是只需要改有bug的類別?這樣我們就能歸納出一個結論:類別和MP3Player的電池一樣,最好能夠想換就換。廢話不多說,我們直接寫個Java MP3Player程式來看看。
class MP3Player{
private NormalBattery battery = new NormalBattery();
public void play(){
battery.usePower();
}
public int getBatteryPower(){
return battery.getPower();
}
public static void main(String... arg){
MP3Player player = new MP3Player();
/** 使用至沒電為止 */
while(player.getBatteryPower()>0){
player.play();
}
}
}
class NormalBattery{
private int power = 100; //預設電力100
public int getPower(){
return power;
}
public void usePower(){
power--; //每使用一次就遞減
}
}
Program A.

以上是代表MP3 Player與電池的類別。注意在MP3Player類別裡,battery欄位的初始值已經指名了要使用的電池是NormalBattery。這意味著如果哪天我們發現NormalBattery類別已經無法滿足我們的需求,想替換的話就必須連MP3Player類別一起更改。現在這麼看可能覺得只是小事,但試想若有10個類別同時使用到NormalBattery,你得花多少時間在這種猴子也能做的瑣碎工作上?
為了不讓身為人類的你退化成猴子,我們還是將上面的程式修改一下,並加入一個Battery介面。
interface Battery{
int getPower();
void usePower();
}
class MP3Player{
private Battery battery;
public MP3Player(Battery battery){
this.battery = battery;
}
public static void main(String... arg){
/** 建構 player時將NormalBattery傳入當參數 */
MP3Player player = new MP3Player(new NormalBattery());
....
}
}
class NormalBattery implements Battery{
....
}
Program B.

這Program B.裡,我們先將電池所共有的功能抽象成一個介面,並讓NormalBattery去實作它。在MP3Player類別裡,則讓battery欄位的值在建構時才由傳入的參數決定。如此一來,使用何種Battery介面的實作的決定權,便從MP3Player類別的實作者手中,轉移到MP3Player使用者的手上。就好比使用者可以隨意更換MP3 Player的電池,而不是取決於MP3 Player的製造商。
但是萬一MP3 Player用到一半,突然想把電池拆掉,又或者,一開始就不想要裝電池,該怎麼辦?如果是用建構子傳入參數的方式,因為沒有提供修改battery的方法,也強制MP3Player的使用者在建構MP3Player時就必須把battery的實體傳入。這種方式在某些情況下顯然不適用,因此我們改採另一種方法:不使用建構子傳參數,而是增加Setter方法。
class MP3Player{
private Battery battery;
public MP3Player(){}
public void setBattery(Battery battery){
this.battery = battery;
}
...
public static void main(String... arg){
MP3Player player = new MP3Player();
/** 設定電池 */
player.setBattery(new NormalBattery());
....
}
}
class NormalBattery implements Battery{
....
}
Program C.

Program C.中把改為不在建構子傳參數,而是去將參數傳入新增的setBattery()方法。這種作法在建構子參數太多的時候相當有用,特別是那些可以省略的參數。

介紹到這裡,看似頗為人滿意,終於可以快樂大結局了。但是,在這個能源耗竭的時代,我們必須思考著如果有一天,MP3 Player的價錢會跟電池差不多,只更換電池就顯得沒有意義,連MP3 Player也必須要可以替換才行。為了因應這種變態的要求,MP3 Player廠商終於決定不再販賣MP3 Player,宣布轉型為MP3 Player出租商,改成以服務的方式收取費用。
為了因應石油不足對產業結構產生的衝擊,物件導向技術也有一套作法,讓你不需要去理會程式碼寫了什麼或怎麼使用,只要改一改訂單,MP3 Player和電池就送到府上供你使用。這種做法必須仰賴介面,將各個實作類別抽象化,因此必須新增 Player介面。
class NormalBattery implements Battery{....}
interface Battery{
....
}
interface Player{
void setBattery(Battery batery);
void play();
int getBatteryPower();
}
class MP3Player implements Player{
private Battery battery;
public MP3Player(){}
public void setBattery(Battery battery){....}
public void play(){....}
public int getBatteryPower(){....}
}
class NormalBattery implements Battery{....}
import java.io.*;
import java.util.*;
class Main{
public static void main(String... arg)throws Exception{
Properties props = new Properties();
/** 從config.txt檔案中讀出屬性 */
props.load(new FileInputStream("config.txt"));
/** 動態載入類別 */
Class playerClass =
Class.forName(props.getProperty("player"));
Class batteryClass =
Class.forName(props.getProperty("battery"));
Player player = (Player)playerClass.newInstance();
Battery battery = (Battery)batteryClass.newInstance();
/** 設定電池 */
player.setBattery(battery);
/** 使用至沒電為止 */
while(player.getBatteryPower()>0){
player.play();
}
}
}

config.txt→
player:MP3Player
battery:NormalBattery
Program D.

由於Program D.要凸顯不需要知道MP3Player內部實作的特性,因此把main方法移到Main類別裡。現在整個主程式完全看不到MP3Player和NormalBattery的蹤影,但程式還是會呼叫MP3Player的setBattery()方法,並將NormalBattery的實體傳入。程式依賴於config.txt的設定,如果想要修改Player或Batter的實作,只需要將實作類別編譯好,接著修改config.txt的內容就可以了。這種方法很明顯要比前面的方法還要有彈性的多,然而如果實作是一些現成的框架提供的介面,在抽離框架時就必須大量修改,代價挺高。由於這種方法有很高的侵入性,大多框架還是使用前面兩種作法。

一開始曾經提到,Martin Fowler曾經定義出三種DI的型態。 Program B.是屬於type 3,Constructor Injection; Program C.是type 2, Setter Injection; Program D.為type 1, Interface Injection。定義得那麼複雜,其實一點也不難。

2009年6月27日 星期六

Digital Chaos

原本Logo右邊那個數碼圖是打算用Fireworks來畫的,直到開始畫才覺得這樣太浪費時間,又不見得好看,就想到用Processing來做,結果效果出乎意料的好。

原始碼:
int lineSize = 200;
int wide = 4;
int hei = 8;
int fontSize = 16;
int shadow_shift = 0;
void setup(){
size(800,800);
PFont font = createFont("Geogreia",fontSize);
textFont(font);
background(0);
translate(width/2,height/2);
for(int i = 1 ; i <= 5000; i+=5){
int rand = (int)random(2);
int num = (int)random(2);
int shift = (int)random(8) * (i/lineSize >=1 ? -1:1);
int x = i%lineSize+i/100*wide + shift;
int y = (i/lineSize)*hei+ shift;
float trans = 160+95*(shift/7);
fill(255,trans);
rotate((rand == 0? -shift:shift));
text(String.valueOf(num), x , y);
fill(255,trans*0.5);
text(String.valueOf(num), x+shadow_shift,y+shadow_shift);
rotate((rand != 0? -shift:shift));
textFont(font,fontSize+(rand == 0 ? -shift:shift));
}
}

言式法則

一直以來,我都想要開一個網誌來存放我那些用完即刪除的小程式,以及那些突然出現的關於程式設計的好點子。這個願望在教授冷酷無情的摧殘與自己無窮無盡的怠惰的雙重夾攻之下,每次都胎死腹中,不了了之。今天在500cc的咖啡因刺激之下,我終於下定決心按下建立網誌的選項。
總而言之,這網誌產生了,而這裡將會是我存放一些程式設計心得以及小作品的地方,可能偶爾會出現一些評論。如果你對文章不甚同意或有疑問,你第一件可以做的事情就是留言,然而喜歡保持網誌整潔的我,不能忍受網誌被留言霸佔,因此你的留言將不會出現在網誌上,系統會直接寄送到我的信箱,到時我會看到。如果你完全不想留言,你可以選擇馬上離開,這是乾脆又容易,同時也最被鼓勵使用的選項。
至於網誌的名字為什麼叫做言式法則?主要原因是作者個人對名字的喜好,次要原因則是這網誌裡的所有文章都是試驗性質的。如果你把言式兩個字合併起來看,就成為了「試」。沒錯,這個網誌就是秉持著試試看的精神創建的,因此若你發現這裡的方法對你無效,或者根本就錯了,請不要大驚小怪,就當和我一起試試看那些鬼東西,然後把問題回報給我知道,不然就乾脆離開,當作沒這回事。
這網誌裡的所有文章都不會標示「版權所有,翻印必究」的噁心標示,但這並不表示這裡的 一字一句都可以任意擷取,而是著作權根本是常識,如果要轉載或引用請一定要註明出處。其餘法律規章請參考Wikipedia之合理使用條文

很好,它終於誕生了。