MVC模式在j2me項目中的應用(一) - 中國WEB開發者網絡 (http://www.webasp.net) -- 技術教程 (http://www.webasp.net/article/) --- MVC模式在j2me項目中的應用(一) (http://www.webasp.net/article/17/16891.htm) |
| -- 作者:未知 -- 發佈日期: 2005-03-09 |
| MVC模式在j2me項目中的應用(一)
作者:FavoYang Email:favoyang@yahoo.com 歡迎交流 Keywords:MVC j2me UI模式 內容提要: 本文簡要的介紹了MVC模式的思想,並分析了MVC模式的利弊,最後結合MIDP平台給出幾種常見的MVC模式實踐。相信此文對任何一個使用midp平台的商務程序開發者都或多或少的有所幫助。 版權聲明: 本文同時發表在www.j2medev.com和我的Blog(blog.csdn.net/alikeboy)上,如果需要轉載,有三個途徑:1)聯繫我並經我同意;2)和www.j2medev.com有轉載文章合作協議的 3)通過Rss聚合我的Blog。另外網上轉載需要全文轉發(包括文章的頭部的聲明),不要斷章取義。 正文: 初識MVC模式 第一次認識到MVC模式是從Microsoft MFC框架所採用的「文檔-視圖」模型開始的。第一次接觸到這個概念讓我興奮不已,很長時間困擾我的程序框架問題似乎迎刃而解了。而後我翻閱了GOF一書中對MVC模式的描述,增進了對這個模式的一些理解。應該說MVC框架是程序設計領域的常青樹,也是GOF模式中最為重要的模式之一。這一經典的模式被廣泛的使用,有太多的程序構架在這一框架之下,從早期的卓面Application到現在流行的Web。並因各自的需求不同,MVC有了很多的變種。瞭解MVC是每個程序設計人員的必修課,最好能夠達到熟練運用的程度。 我並不打算詳細介紹這一模式,因為細節比較複雜,我口舌拙笨也不容易說清楚,大家應該參閱一下有關模式的書籍,任何一本都比我要講的清楚。所以此處就一帶而過。MVC模式是Model-View-Controller的縮寫,中文譯為「模型-視圖-控制器」。MVC的核心思想是分離。Model就是對實體類的抽像;View就是Model在屏幕上的表示;Controller就是協調者。可能有朋友發現Controller的描述多少有些含糊,不要著急,這個一會還要談到。大概因為太過有名,MVC模式的每個實現都出處很大,但他們卻都叫做MVC!!搞得初學者一頭霧水。往往濫用,最後搞得M.V.C.三者之間的協調很混亂。這其實並不是他們的錯,理清思路的關鍵還是剛剛提到的一個詞「分離」。儘管MVC實現不同,但是思想是一致的。 MVC模式的利與弊 先談優點: 1)將M.V.C.分離可以讓不同的專家負責不同的模塊,一般情況下,M部分由熟悉數據庫,網絡傳輸的專家來負責;V則交給對UI有研究的專家。這對於項目的管理者而言是多麼的誘人,分工意味著可以提高效率並可以按照傳統的責任劃分來處理軟件開發過程。對開發者而言也可以專心於一個領域。這樣做的前提是接口要明確,MVC的分離思想正為其提供了基礎。 2)一旦V的部分發生變化,可以迅速的重構而不必引起整個工程的返工。如今的軟件表現層的部分變化實在是太快了… 3)M的部分,因為足夠抽像,可以方便的重複利用,符合OO的思想。另一方面我們可以利用JUnit等單元測試工具對M進行測試,保證工程質量。 談完了優點再來看看缺點: 1)利用MVC模式(也包括近代的其他一些模式)暗示我們通過多產生一些類,來提高程序的可讀性與健壯性。附帶來的缺點就是類的數量的膨脹。說句笑話,MVC就好像是發面時用的速效粉一樣,是最為方便的代碼膨脹劑,相信大家都深有體會:) 2)MVC雖然定義了M.V.C.個個部件的含義,但並不具體,而且沒有非常明確的固定三者之間的聯繫。所以一直以來除了View沒有爭論外,其他方面都有很多爭論,大家都想把自己的理解作為正解。尤其是「Model到底是屏幕數據的集合還是實體數據」、「控制器的作用」是兩個經常爭論的問題。前面提過MVC變種很多,這也給初學者留下了不少的陷阱。後面結合實例將會分析幾種常見的做法。 3)MVC的實現成本偏高。但請注意是這是相對的,一般而言項目越大,越可以看出其優勢。 常見的MVC模式實踐 下面將會介紹在midp平台幾種常見的實踐,最後是我習慣的做法 M—V形式(或者MC—V、M—VC) 這也是在j2me中一種慣用的方法,精煉的說這種方法是以屏幕為組織單位的,因而很適合RAD工具的開發思路。一個屏幕及其控制被抽像成一個VC類,而這個類中有一個私有的Model對像來代表屏幕上要用到的數據元素。屏幕對象並不保存任何的實體數據,這些數據被組織在了Model對像中。大概因為屏幕對像很直觀,控制器的作用也不明晰(它絕大部分的功能被view或是model取代,具體取決於你的實現),所以也常常稱呼為model-view模式。形式如下: class MyFrame extend Frame{ private Model model; private StringItem name; MyFrame(Model model){ this.model=model; name=new StringItem(model.getName());//請求模型的數據 append(name); } } class Model{ private String name="M-C pattern"; public String getName(){//這是一個服務接口 return name; } } 上面看到的是個典型的M—V模型,我們可以理解這種以屏幕為核心的分離的含義。Model組織起屏幕的數據,view向Model索要其希望顯示的數據,注意這一操作一定要通過預先協商好的接口訪問,而不是直接操作。如果出現複雜的事務邏輯(用戶選擇的某種操作),有人將其放在Model端,也有人放在View端,但一般上放在Model端,這時Model帶有嚴重的Controller的色彩。 這種形式的優點是非常的直觀,也有限的分離了顯示和數據。如果常看j2medev.com站長Mingjava的文章,可以看到大部分他寫的例子都是這種模式。並且這種模式也常常用於RAD工具。 這種模式的缺點是它與RAD工具一樣鼓勵你從屏幕開始思考問題,這往往讓你陷入RAD的陷阱——不先考慮事務的流程,而是從用戶接口直接下手去分析問題,這往往扼殺了你的全局構思。 Sun blueprints: Smart Ticket中使用的MVC模式 著名的藍圖程序Smart Ticket中使用了MVC模式,並且這一模式幫助Sun的程序員在MIDP2發佈時,快速的將Smart Ticket的view部分從MIDP1.0 更新到MIDP2.0。 Sun針對MIDP的特點,設計並改進了這一模式,在SUN的解決方法中是一個很標準的方法,只是 Controller變成了一個巨大的事務處理器,所有由UI對像收集到的用戶的需求都轉發給Controller處理。Controller內部保存了一組常量。在一個dispose(int id)形式的方法裡一個巨大的switch case語句根據比較不同的常量,處理不同的請求。這種技術有時也將Controller稱為處理器,或者屏幕導航器。這種模式的提出者主要是要集中處理j2me裡頻繁的畫面導航。 很多人都覺得,在j2me中將Controller改造成巨大的事務處理器是一個很好的方法。我對此持保留意見。 iFeedback 中簡化的MVC 為了大大減少類的數量,iFeedback的作者,將MVC封裝到一個類中,用不同的方法來代表對這三者的分離,這種舉動證明對減少類的數量又很大幫助。 public abstract class MVCComponent implements CommandListener { // Set from outside at beginning public static Display display; // Returns the screen object from the derived class public abstract Displayable getScreen(); public Displayable prepareScreen () throws Exception { if ( getScreen() == null ) { initModel(); createView(); } else { updateView(); } getScreen().setCommandListener ( (CommandListener) this ); return getScreen (); } public void showScreen() { try { display.setCurrent( prepareScreen() ); } catch (Exception e) { e.printStackTrace(); Alert a = new Alert("Error in showing screen"); a.setTimeout(Alert.FOREVER); display.setCurrent(a); } } // Initialize. If a data member is not backed by RMS, make sure // it is uninitilzed (null) before you put in values. protected abstract void initModel () throws Exception; protected abstract void createView () throws Exception; protected abstract void updateView () throws Exception; public abstract void commandAction(Command c, Displayable s); } 因為都在一個類裡面,你在也不必被MVC三者之間的關係操心了,這種退化的做法,是對MIDP有限資源的妥協。 我的習慣做法 下面結合我對MVC的理解和大家交流一下。我使用的是一種UML標準的做法,最大程度上對的體現分離的思想。首先和大家交流一下詞彙表: View代表屏幕。 View通過預先商定好的接口向Controller索要數據,View同時收集用戶的輸入,View並不處理這些輸入,而是根據不同的輸入回調Controller不同的方法。通常View的子類使用UI後綴。 Controller 控制器 提供View調用的接口,負責和model交流。控制器和View共同擔負起和用戶交流的作用。 Model 泛指一系列的實體對像 需要注意的是我理解的Model並不是屏幕數據的組織單位。Model代表一系列的實體對象。由Controller跟Model交流。我覺得RAD工具中常常將Model代表屏幕數據的集合正式導致MVC概念混亂的一個原因。RAD工具中Model,大體相當於這裡的Controller所起的作用。 控制器並不總是聯繫著Model,有時只是依賴關係。並且Controller往往通過Model的對應的生命期類來獲得Model對象。在這種形式中,層層隔離,View與Controller緊密相連,而Model有很高的獨立性,可以很好的重用。 一般的結合UML設計的過程,對MVC的各個類有相應的命名習慣。 View 稱為Boundary類(邊界類) 以UI結尾 Controller 稱為 Controller類(控制類) 以Workflow結尾 Model 稱為Entity類(實體類) 以Entity結尾或者沒有尾綴 Model對應的Lifecycle類(生命週期類) 以Locator結尾 邊界類和控制類的基礎類如下 BaseView.java /** * @author Favo * * 視圖類 */ public abstract class BaseView { public abstract Display getDisplay(); /** * 簡單的返回包裝的屏幕對像,不要做任何準備屏幕的操作! */ public abstract Displayable getScreen(); /** * 創建屏幕 */ protected abstract void createView() throws Exception; /** * 更新屏幕 */ public abstract void updateView() throws Exception; /** * 返回控制器 */ public abstract BaseController getController(); /** * 準備屏幕 * 返回準備好的屏幕對像 */ public Displayable prepareScreen() throws Exception { if(getScreen()==null){ createView(); } else { updateView(); } return getScreen(); } /** * 顯示當前屏幕 */ public void displayScreen(){ try{ getDisplay().setCurrent(prepareScreen()); } catch (Exception e) { e.printStackTrace(); Alert al=new Alert("Error",e.toString()+'\n'+e.getMessage(),null,AlertType.ERROR); al.setTimeout(Alert.FOREVER); getDisplay().setCurrent(al); } } } BaseController.java /** * @author Favo * * 控制類 */ public abstract class BaseController { public abstract BaseView getView(); public abstract void setView(BaseView view); } 注意到這些基礎的類並沒有向MFC框架那樣產生完整的框架,而是設計成了抽像類,一來希望強迫大家實現抽像類(防止出錯);二來希望增加一點靈活性。所以兩個類之間的通信就要靠大家撰寫的子類的構造函數了。一般我的習慣是,初始化好控制器,然後將控制器作為參數傳給邊界類的構造函數,由邊界類的構造函數來回調控制器的setView()來實現的。這些步驟是一定要有的,不然會NULLpointerExcpetion哦。 儘管理論上可能很清晰,但實踐帶來的複雜性是驚人的。這正是軟件開發的問題,太多的細節困擾這開發者對大局的把握。本文接下來,將結合最後這種設計思想,給出一個完整的設計實例。幫助大家從實踐的角度理解運用這一模式。敬請大家期待。 |
| webasp.net |