偉大的創意少之又少,多數時候只是一些小改進。小的改進也是好的。 什麼是重構可以運作的程式跟可以維護的程式之間,還有一道難以言說的鴻溝。 一個程式設計之初,是用來解決特定問題。就像在前面章節的學習中,我們也已經寫好了一個可以運作的 BMI 程式。但是對程式設計來說,當我們寫越多程式,我們會希望可以從這些程式之中,找到一個更廣泛適用的法則,讓每個程式都清晰易讀,從而變得更好修改與維護。 讓程式清晰易讀有什麼好處呢?當一段程式被寫出來,之後我們所要做的事,就是修改它與維護它。一旦程式越長越複雜,混亂到無法維護的境界時,就只好砍掉重練。 所以若我們能透過某些方式,例如重新組織或部分改寫程式碼,好讓程式容易維護,那麼我們就可以為自己省下許多時間,以從容迎接新的挑戰。 我們回過頭來看看前面所寫的 Android 程式。Android 平台的開發者已經先依照 MVC 模式,為我們將顯示介面所用的 XML 描述檔、顯示資源所用的 XML 描述檔從程式碼中區隔開來。將與程式流程無關的部份分開來組織,讓程式流程更清楚,相對易於維護。 而在主要程式碼(Bmi.java)方面,雖然程式碼量很少,還算好讀,但整體上並不那麼令人滿意。例如,假使我們要在這段程式碼中再多加上按鍵、適用於多種螢幕顯示模式、或是再加入選單等等內容,很快地程式碼就開始變得複雜,變得不容易閱讀,也開始越來越不容易維護。 因此,在繼續新的主題之前,我們先來重構這個 BMI 應用程式。在重構的過程中,也許我們能學到的東西,比學任何新主題還重要哩。 MVC我們打算重構 BMI 程式的部份 java 程式碼。既然我們已經照著 Android 平台的作法,套用 MVC 模式在我們的程式組織上,那麼,我們不妨也試著套用同樣的 MVC 模式在 Bmi.java 程式碼上。 如何套用 MVC 模式到Bmi.java 程式碼上哩? 原來的程式片段是這樣的 1 @Override
2 public void onCreate(Bundle savedInstanceState) {
3 super.onCreate(savedInstanceState);
4 setContentView(R.layout.main);
5
6 //Listen for button clicks
7 Button button = (Button) findViewById(R.id.submit);
8 button.setOnClickListener(calcBMI);
9 }上面的程式片段中,包含了所有 Android 程式共用的標準內容, 整個程式的大致架構在前面章節中已經講解過,現在我們從中取出我們感興趣的部分來討論: Button button = (Button) findViewById(R.id.submit); button.setOnClickListener(calcBMI); 在第7行我們看到一段程式碼來宣告按鈕物件,與針對該按鈕物件作動作的程式碼。 button.setOnClickListener 程式碼的意義是指定一個函式,來負責處理"按下"這個"按鈕"後的動作。 我們可以想像,在同一個畫面中,多加入一些按鈕與欄位後,"onCreate" 這段程式將變得壅腫,我們來試著先對此稍作修改: 首先,我們可以套用 MVC 模式,將宣告介面元件(按鈕、數字欄位)、指定負責函式等動作抽取出來,將 onCreate 函式改寫如下 @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
setListeners();
}接著我們將宣告介面元件的部份寫成一個獨立的「findViews」函式: private Button calcbutton;
private EditText fieldheight;
private EditText fieldweight;
private void findViews()
{
calcbutton = (Button) findViewById(R.id.submit);
fieldheight = (EditText) findViewById(R.id.height);
fieldweight = (EditText) findViewById(R.id.weight);
}順便將原本很沒個性的按鈕識別參數「button」改名成「calcbutton」,以後在程式中一看到「calcbutton」,就知道是一個按下後將開始處理計算工作的按鈕。 同樣地,我們也將指定特定動作(按按鈕)的負責函式獨立出來: //Listen for button clicks
private void setListeners() {
calcbutton.setOnClickListener(calcBMI);
}如此一來,我們就將程式邏輯與介面元件的宣告分離開來,達成我們重構的目的。 完整程式如下: package com.demo.android.bmi;
import java.text.DecimalFormat;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class Bmi extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
setListeners();
}
private Button button_calc;
private EditText field_height;
private EditText field_weight;
private TextView view_result;
private TextView view_suggest;
private void findViews()
{
button_calc = (Button) findViewById(R.id.submit);
field_height = (EditText) findViewById(R.id.height);
field_weight = (EditText) findViewById(R.id.weight);
view_result = (TextView) findViewById(R.id.result);
view_suggest = (TextView) findViewById(R.id.suggest);
}
//Listen for button clicks
private void setListeners() {
button_calc.setOnClickListener(calcBMI);
}
private Button.OnClickListener calcBMI = new Button.OnClickListener()
{
public void onClick(View v)
{
DecimalFormat nf = new DecimalFormat("0.0");
double height = Double.parseDouble(field_height.getText().toString())/100;
double weight = Double.parseDouble(field_weight.getText().toString());
double BMI = weight / (height * height);
//Present result
view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));
//Give health advice
if(BMI>25){
view_suggest.setText(R.string.advice_heavy);
}else if(BMI<20){
view_suggest.setText(R.string.advice_light);
}else{
view_suggest.setText(R.string.advice_average);
}
}
};
}同樣是「calcBMI」 函式,在完整程式中,改將「calcBMI」 函式從原本的「OnClickListener」宣告成 「Button.OnClickListener」。這個改變有什麼差別呢? 閱讀原本的程式碼,在匯入(import)的部分可以看到,「OnClickListener」是來自於「android.view.View.OnClickListener」函式: import android.view.View.OnClickListener; 改成 「Button.OnClickListener」後,「Button.OnClickListener」就變成來自於「android.widget.Button」中的「OnClickListener」函式,在查閱程式時,整個「Button」與「OnClickListener」之間的關係變得更清晰。 另外,我們偷偷將「OnClickListener」中其他會存取到的介面元件識別參數,也補進 findViews 宣告中: private void findViews()
{
button_calc = (Button) findViewById(R.id.submit);
field_height = (EditText) findViewById(R.id.height);
field_weight = (EditText) findViewById(R.id.weight);
view_result = (TextView) findViewById(R.id.result);
view_suggest = (TextView) findViewById(R.id.suggest);
}同時,我們也把識別參數的命名方法做了統一:按鈕的識別參數前加上 「button_」前綴,可輸入欄位的識別參數前加上 「field_」前綴,用作顯示的識別參數前則加上「view_」前綴。將變數名稱的命名方法統一有什麼好處呢? 好處在於以後不管是在命名新變數,或是閱讀程式碼時,都能以更快速度命名或理解變數的意義,讓程式變得更好讀。 我們也把原本在程式中直接寫進的字串 TextView result = (TextView) findViewById(R.id.result);
result.setText("Your BMI is "+nf.format(BMI));改寫成 //Present result view_result.setText(getText(R.string.bmi_result) + nf.format(BMI)); 並將「TextView view_result」宣告改放到 findViews 中一次處理好。 現在,整個程式流程是不是清爽了許多呢? 對於本章,您還期望知道什麼樣的內容呢?請在下方提出建議! | |
受益匪浅!
期待继续拜读续作,关注中...
多謝你花時間寫這本書~讓我學到很多呢
希望能就繼續看到你的續作摟
十分十分尊敬大大您這偉大無私的貢獻... 請繼續
獲益良多
Android Coding Style Guide http://source.android.com/submit-patches/code-style-guide
Code Conventions for the Java Programming Language http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html
你的介紹非常詳盡,深入淺出。對我這個完全不懂JAVA的人,受益良多。
看了您的文章,获益良多! 关于重构,我发表一下自己的方法:) 先是 public class Bmi extends Activity implements OnClickListener? 然后 public void setListeners()
最后 public void onClick(View v)这样子可以集中根据传入的onClick方法中不同的View参数来调用相应的处理方法:) 冒昧修改,如有不合适,可以删除:)
lion0...., 這方法也很好呀,只要把握住了重構的目的即可。寫書求的是大家都看的懂,跟自己寫 Code 還是會多少有差異的。
讲解的浅显易懂,不仅解释了Android应用开发的基本方法, 更是对设计模式进行了探讨,非常感谢!
非常感谢你的讲解,文字浅显易懂,让我受益匪浅!!
請問view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));這段裡面
getText是哪來的? 是view_result的function?還是別的object?
謝謝
bigDav:
把滑鼠擺到 getString 上面,eclipse 就會告訴你這個 getString 是來自
android.content.Context.getString
作用是讀取資源中的特定字串內容。
原來如此 謝謝您的解釋以及分享:)
非常感谢你的讲解!
找到你是我的福气,谢谢
謝謝你的教學!
抱歉,因為我看書頗慢,加上如果每找到一個疑問就要寫一次問卷的話頗怪,所以選擇在線上版這裡回應。文本書在本章節最末有新增一段解釋getText的段落(11-10頁),裡面有一小段提到〝只要把滑鼠擺到「getText」這個函式上面,Eclipse開發環境就會告訴你「getText」這個函式來自於「android.content.Context.getString」〞,請問在最末的getString這裡是否有誤植?因為我在Eclipse上看到的它是顯示「android.content.Context.getText」。
Kenny, 沒問題, 在哪反應都可以. 當然以您實際看到的為準, 謝謝您的指正
great thanks!
您好,我是个JAVA初学者,有一个问题就是: “同樣是「calcBMI」 函式,在完整程式中,改將「calcBMI」 函式從原本的「OnClickListener?」宣告成 「Button.OnClickListener?」。這個改變有什麼差別呢?
閱讀原本的程式碼,在匯入(import)的部分可以看到,「OnClickListener?」是來自於「android.view.View.OnClickListener?」函式:
改成「Button.OnClickListener?」後,「Button.OnClickListener?」就變成來自於「android.widget.Button」中的「OnClickListener?」函式,在查閱程式時,整個「Button」與「OnClickListener?」之間的關係變得更清晰。” 这段程序在eclipse中运行,当将鼠标放置于Button.OnClickListener?中的OnClickListener?上时还是提示来源于android.view.View.OnClickListener?,为什么不是想文章所说的来自android.widget.Button.OnClickListener?呢?
因為 Button.OnClickListener? 是直接繼承自 android.view.View.OnClickListener? 的函式. 剛開始學的話沒有必要在這種細節裡打轉, 先繼續學下去, 改天再看就能領會.
Thanks a lot
so good .
回應樓上:lion0neo 講到的方法是成立的, 多個 button 可以用 view id 來區別, 例如 case R.id.submit/case R.id.xxx
回應作者:恩,是我回得太急了,並不是不能作,而是會影響到「重構」這件事情,若以C的角度來看,是沒什麼問題的,但以物件導向的方式來看,其實是作者您的寫法比較容易維護、管理喔。(雖然這不是什麼大問題就是了)
非常感謝您的文章,對我幫助很大;不過以我只讀過1/4本"JAVA由初學邁向認證" 跟1/5本"Android開發入門與實戰"的基礎上來吸收您的文章,說真的還是讀的 有點盲,當然英文單字每次都要翻譯也是困難度之一;最後我還是有些疑惑麻煩您 指點: 問1.findViews(); 我的理解能力是findView="發現視圖";但後面那對"()"用處是啥? 感謝您的指點
您的問題很明顯是對程式語言還不熟悉, 建議先針對程式語言學習一下, 再來進入手持設備開發....
确实是非常好的android学习资料!就算不会java也很容易看懂~THX!
一个问题:在运行“double height = Double.parseDouble(field_height.getText().toString())/100;”时, field_height处会不会报NullPointerException??
应该不会, 编译时不会有NullPointerException?的事, 而在实际运用时,当执行上面代码时,findViews肯定已经执行过了。
虽然知道自己的提问自己已经解决了,还是写在这里记录下吧。
感謝大大的分享,讓我搞清楚了整個Android程式運行架構。
請問一下 Bmi.java的內容,完全照大大所提供的複製貼上 可是跑模擬時會跑出以下內容 Sorry! The application BMI(processcom.demo.android.bmi)has stopped unexpectedly.Please try again. 這時候我該怎麼解決呢?
不好意思,新手想問一下蠢問題..這一章說的: "改成 「Button.OnClickListener?」後,「Button.OnClickListener?」就變成來自於「android.widget.Button」中的「OnClickListener?」函式", 我查Android developer 網頁裡面的 android.widget.Button下面沒有"OnClickListener?"(在android.view.View下面)... 是不是因為Botton繼承自TextView,TextView繼承自View的關係?
不好意思,我看到已有人問了..
我是想儘量自己從線上確認這些class跟method..新手對於這些東西感覺很混亂..(一條一條的程式) 也許可以用圖形把物件之間的關係畫出來..會更清晰..
不好意思,我記得android在1.6之後,已經可以不用設onclicklistener了。在按鈕屬性的OnClick?修改值後,在程式中實作同名函數即可。應該可以修正一下。
最近正好碰到這個問題. 我發現不用onclicklistener改設onclick按鈕屬性的話, 在release時用proguard做混淆後, 當按下按鈕時就會出錯. 應該是在progurad處理時對應的名稱被改掉了. 所以我還是建議使用onclicklistener
非常不错,有幸研读!
thanks to your simple and inducive introduction
您好, 我是個android初學者, 最近在寫的東西就有一堆介面元件, 每次改程式碼時光找那些就找到快暈了 看您這樣配置感覺好簡明易懂, 只是我很好奇一點就是, 元件都宣告成全域變數, 這樣沒關係嗎? 就會不會很占記憶體? 還是其實手機很厲害無所謂呢?
Fout..., 我也不知你問題的答案, 但Android會自動管理記憶體, 目前一般使用狀況下這樣寫沒什麼問題
Fout..., 我也不知你問題的答案, 但Android會自動管理記憶體, 目前一般使用狀況下這樣寫沒什麼問題
原來如此, 謝謝您回答我, 我還有一個問題(對不起我很煩>"<), 就是觸發事件函式的部分 , private OnClickListener? calcBMI = new OnClickListener?() ... 我看到好多程式都是長這樣, 這和直接讓程式實作OnClickListener?有什麼差別嗎? 如果有很多顆按鈕, 每顆按鈕要執行的動作都不一樣時, 那是就寫很多款觸發事件函式分別給每顆按鈕比較好, 還是寫一款觸發事件函式裡面再判斷是哪顆按鈕比較好, 還是讓程式實作OnClickListener?再慢慢判斷是哪顆按鈕比較好呢?
Fout..., 兩種都可以, 看個人偏好
請問view_suggest.setText(R.string.advice_heavy);為什麼不用加入getText()? 而view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));就有加入 getText(), 一樣都是TextView,有什麼差別呢?
可以看API有沒有直接支援從R.string取出字串,沒有的話只好用getText取出。這是Android API不太一致的地方