<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    憨厚生

    ----Java's Slave----
    ***Java's Host***

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      165 隨筆 :: 17 文章 :: 90 評論 :: 0 Trackbacks
    轉 http://www.infoq.com/cn/articles/thoughtworks-practice-partiii

    RichClient/RIA原則與實踐(上)

    作者 陳金洲 發布于 2009年3月10日 上午4時8分

    社區
    .NET,
    Agile,
    Java
    主題
    RIA,
    富客戶端/桌面
    標簽
    原則

    Web領域的經驗在過去十多年的不斷的使用和錘煉中,整個 開發領域的技術、理念、缺陷已經趨于成熟。JavaEE Stack, .NET Stack, Ruby On Rails等框架代表了目前這個技術領域的所有經驗積累。這樣我們在開始一個新的項目的時候,只需要選擇對應語言的最佳實踐,基本上不會犯大的錯誤。例 如,如果使用Java開發一個新的Web應用,那么基本上Spring/Guice+Hibernate/iBatis/+Struts /SpringMVC這種架構是不會產生重大的架構問題的;如果使用RoR那么你已經在使用最佳實踐了;系統的分層:領域層,數據庫層,服務層,表現層等 等;為了保證系統的可擴展性,服務器端應當是無狀態架構,等等。總而言之,web開發領域,它豐富的積累使得開發者逐漸將更多的精力投入到應用本身。

    來看富客戶端,或者富互聯網應用。在我看來,今天的RichClient與RIA已經沒有分別:只要代表著豐富界面元素和豐富用戶體驗,需要與服務器進行 交互的應用都可以稱為RichClient或者RIA,雖然感覺上RichClient更“企業化”一些(服務器往往在企業內部),RIA更“個人化”一 些(服務器往往處于公網)。從最小的層面來說,我現在正在使用的離線模式的GoogleDoc就是一個RichClient應用──雖然它沒有那么 Rich,采用和microsoft office一樣土的界面; 我現在正在聽音樂的Last.fm客戶端顯然是一個非常典型的RIA──它所有的個人喜好信息、音樂全都來自遠在美國的服務器。本地的這個界面,只是提供 收集個人和音樂信息,以及控制音樂的播放和停止;目前擁有1150萬玩家的魔獸世界,則是一個掙錢最多的,最“富”的客戶端,10多G的客戶端包含了電影 品質的廣闊場景,華麗的魔法效果和極其復雜的人機交互。

    如今的用戶需求已經達到了一個新的高度,那些灰色的,方方正正的界面已經逐漸不能夠滿足客戶的需求。從我們工作的客戶看來,他們除了對“完成功能”有著基 本的期待外,對于將應用做得“酷”,也抱有極大的熱情。我工作的上一個項目是一個CRM系統,它是基于.NET Framework 3.5的一個RichClient應用。它的主窗口是一個帶著紅色漸變背景的無邊框窗口,還有請專業美工制作的圖標,點擊某一個菜單還有華麗的二級菜單滑 動效果。我們在這個項目中獲得了很多,有些值得借鑒,有些仍然值得反思。我仍然記得我們在項目的不同階段,做一個技術決定是如此的彷徨和忐忑:因為在當時 的RichClient企業開發領域,幾乎沒有任何豐富的經驗可以借鑒,我們重新發明了一些輪子,然后又推翻它;我們偏離了UI框架給我們提供的各種便利 而自己實現種種基礎特性,只是因為他們偏離了我們所倡導的測試性的原則。在寫下本文的時候,我嘗試搜索了一下,仍然沒有比較深入的實踐性文章來介紹企業環 境下RichClient開發。大多數的書,如Swing、JavaFX、.NET WPF開發等等,偏向于小規模特性介紹,而在大規模的企業應用中,這些小的技巧對于架構決策往往幫助很小。

    我的工作經歷應當是和大多數開始進行RichClient開發的開發者類似:有著豐富的Web開發的經驗之后開始進行RichClient開發。加入 ThoughtWorks之后參加了多個不同的RichClient項目的開發工作,使用/嘗試過的語言包括Java Swing, Flex/Adobe Air, .NET WinForm/.NET WPF. 對于不同平臺之間的種種有些體會。在這里我將這些實踐和原則總結如下。例子很可能過時,畢竟華麗的界面框架層出不窮,但原則應當通用的。使用和遵循這些原 則將會幫助你少犯錯誤──至少比我們過去犯的錯誤要少。如果你擁有一定的web開發經驗,那么這篇文章你讀起來會很親切。

    這些原則/實踐往往不是孤立的,我嘗試將他們之間用圖的方式關聯起來,幫助你在使用的過程中進行選擇。例如,你遵循了“一切皆異步”的原則,那么很可能你 需要進行“線程管理”和“事件管理”;如果你需要引入“緩存與本地存儲”,那么“數據交互模式”你也需要進行考慮。希望這張圖能夠幫助讀者理解不同原則之間的聯系。

    下面列出的這些原則或者實踐沒有嚴格意義上的區分。按照上面的圖,我推薦是,一旦你考慮到了某一個實踐,那么與它直接關聯的實踐你最好也要實現。它會使得你的架構更全面,經得起用戶功能的需求和交互的需求。

    為了讓這些實踐更加通用,我采用偽代碼書寫。相信讀者能夠轉化成相應的語言──Java, C#, ActionScript或者其他。這些實踐并非與某一種語言相關。在某些特定的例子中,我會采用特定語言,但大多數都是偽代碼描述的。

    1 一切皆異步

    所有耗時的操作都應當異步進行。這是第一條、也是最重要的原則,違背了這條原則將會導致你的應用完全不可用。

    考慮這樣的一個功能:點擊一個"更新股票信息"按鈕,系統會從股票市場(第三方應用)獲得最新的股票信息,并將信息更新到主界面。絲毫不考慮用戶體驗的寫法:

    void updateStockDataButton_clicked() {
        stockData = stockDataService.getLatest(); // 從遠程獲取股票信息
        updateUI(stockData); // 這個方法會更新界面
    }

    那么,當用戶點擊updateStockDataButton的時候,會有什么反應?難說。如果是一個無限帶寬、無限計算資源的世界,這段代碼直觀又易 懂,而且工作的非常好:它會從第三方股票系統讀到股票數據,并且更新到界面上。可惜不是。這段代碼在現實世界工作的時候,當用戶點擊這個按鈕,整個界面會 凍結──知道那種感覺嗎?就是點完這個按鈕,界面不動了;如果你在使用Windows, 然后嘗試拽住窗口到處移動,你會發現這個窗口經過的地方都是白的。你的客戶不會理解你的程序實際上在很努力的從股票市場獲得數據,他們只會很憤怒的說,這 個東西把我的機器弄死了!他們的思路被打斷了。于是他們不再使用你的程序,你們的合作沒了。你沒錢了。你的狗也跑了。

    出現界面凍結的原因是,耗時操作阻塞了UI線程。UI線程一般負責著渲染界面,響應用戶交互,如果這個線程被阻塞,它將無法響應所有的用戶交互請求,甚至 包括拖拽窗口這樣簡單的操作。所有的界面框架,無論是Java/.NET/ActionScript/JavaScript, 都只有一個UI線程,這個估計永遠都不會變。

    用戶看到的應用通常與程序員大相徑庭。用戶對應用的期待級別分別是:能用、可用、好用、好看。而我觀察到的大多數程序員停留在第一階段:能用。“一切皆異步”這個原則說來簡單,做起來也不會很難。把上面的代碼稍作改動,如下:

    void updateStockDataButton_clicked() {
        runInAnotherThread( function () {
            stockData = stockDataService.getLatest(); // 從遠程獲取股票信息
            updateUI(stockData); // 這個方法在UI線程更新界面
        }
    }

    注意加粗部分。runInAnotherThread是跟語言平臺特定的。對于.net C#,可以是一個Dispatcher+delegate或者ThreadPool.QueueUserWorkItem;對于Java,可以干脆是一個Runable。對于AJAX, 可以是XMLHttpRequest或者把這個計算扔到一個IFrame中;對于ActionScript, 似乎沒有什么好的方法,把獲取數據的部分交給XML.load然后通過事件回調的方式來進行界面刷新吧。

    耗時操作一般兩種來源產生:網絡帶來的延遲以及大規模運算。兩者對應的異步實現方式有所不同。前者往往可以通過特定語言、平臺的獲取數據的方式來進行異步,特別是缺乏多線程特性的動態語言。例如典型的AJAX方式:

    xhr = new XmlHttpRequest()
    xhr.send("POST", '/stockData/MSFT', function() {
        doSomethingWith(xhr.responseText);  // 只有當數據返回的時候,才會調用
    })

    大規模運算帶來的耗時在Java/C#等支持多線程的語言環境中很容易實現,而對于JavaScript/ActionScript等很難,折衷的方式是 將復雜運算延遲到服務器端進行;或者將復雜運算拆解成若干個耗時較少的小運算,例如ActionScript的偽多線程實現方式。

    “一切皆異步”這個原則說來容易,但要在企業應用中以一種一致的方式進行實現很難。上例中runInAnotherThread的方式貌似簡單,也可能出 現在各種GUI框架的介紹中,但絕不是一個稍具規模的RichClient應當采用的方式。它很難作為一種編程范式被遵循,你絕不會希望看到在你的代碼中 所有用到異步的地方都new Runnable(){...}。這樣帶來的問題不僅僅是異步被不被管理的到處亂扔,還帶來了測試的復雜性。為了解決這些只有在至少有點規模的 RichClient中才出現的問題,你最好也實現了“4 線程管理”(見下篇),能夠實現“3 事件管理”(見下篇)更好。終極方式是將這些抽象到應用的基礎框架中,使得所有的開發人員以一種一致的方式進行編程。

    2 視圖管理

    2.1 視圖生命周期管理

    視圖這個概念在WEB開發中幾乎被忽略。這里所說的視圖是指頁面、頁面塊等界面元素。在WEB開發中,視圖的生命周期很短:在進入頁面的時候創建,在離開頁面的時候銷毀。一不小心頁面被弄糟了,或者不能按照預期的渲染了,點下刷新按鈕,整個世界一片清凈。

    WEB下的視圖導航也是如此自然。基于超鏈接的方式,每點擊一次,就能夠打開一個新的頁面,舊的頁面被瀏覽器銷毀,新的頁面誕生。(這里不考慮AJAX或者其他JavaScript特效)

    如果把這種想法帶入到RichClient開發,后果會很糟糕。每當點擊按鈕或者進行其他操作需要導航到新的窗口,你不加任何限制的創建新窗口或者新的視 圖。然而CPU不是無限的。創建一個新的視圖通常是很耗CPU和內存的。系統響應會變慢。用戶會抱怨,拒絕付錢,于是因為饑餓,你的狗再次離開了你。

    每次新創建視圖產生的嚴重后果并不僅僅是非功能性的,還包括功能性的缺失。如果你用過Skype,當你在給張三通話的時候,再次點擊張三并且進行通話,你 會發現剛剛的通話界面會彈出來,而不是開啟新窗口。在我們的一個項目中,有一個功能:點擊軟件界面上的電話號碼就能開啟一個新窗口,并直接連到桌上的電話 撥號通話。可以想象,如果每次都會彈出新的窗口,軟件的邏輯是根本錯誤的。

    如何解決這個問題?最簡單的方式是將所有已知的視圖全都保存到本地的一個緩存中,我們命名為ViewFactory,當需要進行獲取某個視圖的時候,直接從ViewFactory拿到,如果沒有創建,那么創建,并放到Cache中:

    class ViewFactory {
         cache = {}
         View getView(Object key) {
            if cache.contains(key) {
                return cache[key]
            }
            cache[key] = createView(key)
            return cache[key]  
        }
    }

    需要注意的是,ViewFactorykey的選擇。對于簡單的應用,key可以干脆就是某個單獨窗口的類名。例如整個系統中往往只有一個配置窗口,那 么key就是這個類名;對于需要復用的窗口,往往需要根據其業務主鍵來創建相應的視圖。例如代碼中只有一個UserDetailWindow, 需要用來展示不同用戶的信息。當需要同時顯示兩個以上的用戶信息的時候,用同一個窗口實例顯然不對。這時候key的選擇可以是類名+用戶ID。

    2.2 視圖導航

    上面的方案并沒有解決導航的問題。導航需要解決的問題有兩個,如何導航以及如何在導航時傳遞數據。這時候不得不羨慕WEB的解決方式。我要訪問ID1的用戶信息,只需要訪問類似于users/1的頁面就好;需要訪問搜索結果第5頁,只需要訪問/search?q=someword&page=5就好。這里/search是視圖,q=somewordpage=5是傳遞的數據。目前我還沒有發現任何一本書來講述如何進行視圖導航。我們的方式是實現一個Navigator類用來導航,Navigator依賴于前面提到的ViewFactory

    class Navigator {
        Navigator(ViewFactory viewFactory) {
            this.viewFactory = viewFactory;
        }
        void goTo(Object viewKey) {
            this.viewFactory.getView(viewKey).show()
        }
    }

    (這個類看起來跟ViewFactory沒什么大的差別,但他們邏輯上是完全不同,并且下面的擴展中會增強)

    這樣是可以解決問題的。如果要在不同的視圖之間傳遞數據,只需要對Navigator.goTo方法稍加擴展,多添加一個參數就能夠傳遞參數了。例如,在用戶列表窗口點擊用戶名,發送一條消息并打開聊天窗口,可以寫為:

    void messageButton_clicked() {
        Navigator.goTo("ChatWindow#userId", "聊天消息")
    }

    然而這種方式并不完美。當你發現大量的數據在窗口之間交互的時候,這種將主動權交給調用方控制的方式,會給狀態同步帶來不少麻煩;如果你使用了本地存儲,它越過存儲層直接與服務器交互的方式也會帶來不少的不便之處。更好的方式是使用“3 事件管理”(見下篇)。當然,如果窗口之間導航不存在數據傳遞,基于Navigator的方式仍然簡單并且可用。

    相關閱讀:

    [ ThoughtWorks實踐集錦(1)] 我和敏捷團隊的五個約定

    [ ThoughtWorks實踐集錦(2)] 如何在敏捷開發中做好數據遷移


    作者介紹:陳金洲,Buffalo AJAX中文問題 Framework作者,ThoughtWorks咨詢師,現居北京。目前的工作主要集中在RichClient開發,同時一直對Web可用性進行觀察,并對其實現保持興趣。

    給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家加入到InfoQ中文站用戶討論組中與我們的編輯和其他讀者朋友交流。

    posted on 2009-03-18 09:52 二胡 閱讀(144) 評論(0)  編輯  收藏 所屬分類: JSweb系統開發
    主站蜘蛛池模板: 永久免费无码网站在线观看| 亚洲色爱图小说专区| 日韩精品无码永久免费网站| 亚洲色婷婷综合久久| 999国内精品永久免费视频| 国产亚洲精品国产福利在线观看| 亚洲色婷婷综合久久| 日本精品人妻无码免费大全 | 亚洲综合色一区二区三区小说| 在线观看的免费网站| 野花高清在线观看免费完整版中文 | 久草视频在线免费| 精品国产日韩亚洲一区91| 亚洲男人的天堂在线播放| 暖暖免费高清日本中文| 久久免费区一区二区三波多野| 日韩国产欧美亚洲v片| 久久久无码精品亚洲日韩按摩| 免费人成激情视频在线观看冫| 亚洲乱码中文论理电影| 久久久久亚洲AV成人网| 成人片黄网站A毛片免费| 你懂的免费在线观看| 亚洲av纯肉无码精品动漫| 亚洲精品美女在线观看| 亚洲线精品一区二区三区 | 亚洲一区二区三区AV无码| 成在人线AV无码免费| 久久精品无码专区免费青青| 亚洲免费网站在线观看| 亚洲毛片αv无线播放一区| 国产小视频免费观看| 91九色精品国产免费| 精品国产免费一区二区三区香蕉| 亚洲视频在线观看| 久久亚洲AV无码西西人体| 在线观看免费人成视频色| 最近免费视频中文字幕大全| a级毛片毛片免费观看久潮 | 成人免费视频69| 久久国产精品免费观看|