<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-ii

    RichClient/RIA原則與實踐(下)

    作者 陳金洲 發布于 2009年3月11日 下午10時7分

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

    3 事件管理

    事件管理應當是整個RichClient/RIA開發中的最難以把握的部分。這部分控制的好,你的程序用起來將如行云流水,用戶的思維不會被打斷。任何一 個做RichClient開發的程序員,可以對其他方面毫無所知,但這部分應當非常熟悉。事件是RichClient的核心,是“一切皆異步”的終極實現。前面所說的例子,實際上可以被抽象為事件,例如第一個,獲取股票數據,從事件的觀點看,應該是:

    • 開始獲取股票數據
    • 正在獲取股票數據
    • 獲取數據完成
    • 獲取數據失敗

    看起來相當復雜。然而這樣去考慮的時候,你可以將執行計算與界面展現清晰的分開。界面只需要響應事件,運算可以在另外的地方 悄悄的進行,并當任務完成或者失敗的是時候報告相應的事件。從經驗看來,往往同樣的數據會在不同的地方進行不同的展示,例如skype在通話的時候這個人 的頭像會顯示為占線,而具體的通話窗口中又是另外不同的展現;MSN的個人簽名在好友列表窗口中顯示為一個點擊可以編輯控件,而同時在聊天窗口顯示為一個 不能點擊只能看的標簽。這是RichClient的特性,你永遠不知道同一份數據會以什么形式來展現,更要命的是,當數據在一個地方更新的時候,其他所有 能展現的地方都需要同時做相應的更新。如果我們仍然以第一部分的例子,簡單采用runInAnoterThread是完全不能解決這個問題的。

    我們曾經犯過一些很嚴重的錯誤,導致最終即便重構都積重難返。無視事件的抽象帶來的影響是架構級別的,小修小補將無濟于事。

    事件的實現方式可以有很多種。對于沒有事件支持的語言,接口或者干脆某一個約束的方法就可以。有事件支持的語言能夠享受到好處,但仍然是語法級別的,根本 是一樣的。觀察者模式在這里很好用。仍然以股票為例,被觀察的對象就是獲取股票數據對象StockDataRetriver,觀察的就是StockWindow

    StockDataRetriver {
    observers: []
    retrieve() {
    try {
    theData = ...// 從遠程獲取數據
    observers.each {|o| o.stockDataReady(theData)}  // 觸發數據獲取成功事件
    } catch {
    observers.each { |o| o.stockDataFailed() }  // 觸發事件獲取失敗事件
    }
    }
    }
    StockDataRetriver.observers.add(StockWindow)  // 將StockWindow加入到觀察者隊列
    StockWindow {
    stockDataReady(theData) {
    showDataInUIThread(); // 在UI線程顯示數據
    }
    stockDataFailed() {
    showErrorInUIThread(); // 在UI線程顯示錯誤
    }
    }
    

    你會發現代碼變得簡單。UI與計算之間的耦合被事件解開,并且區分UI線程與運算線程之間也變得容易。當嘗試以事件的視角去觀察整個應用程序的時候,你會更關注于用戶與界面之間的交互。

    讓我們繼續抽象。如果把“獲取股票數據”這個按鈕點擊,讓StockDataRetriver去獲取數據當作事件來處理,應該怎么寫呢?將按鈕作為被觀察 者,StockDataRetriver作為觀察者顯然不好,好不容易分開的耦合又黏在一起。引入一個中間的Events看起來不錯:

    Events {
    listeners: {}
    register(eventId, listener) {
    listeners[eventId].add(listener)
    }
    broadcast(eventId) {
    listeners[eventId].observers.each{|o| o.doSomething(); }
    }
    }
    

    Events中維護了一個listeners的列表,它是一個簡單的Hash結構,key是eventId,value是observer的列表;它提供了兩個方法,用來注冊事件監聽以及通知事件產生。對于上面的案例,可以先注冊StockDataRetriver為一個觀察者,觀察start_retrive_stock_data事件:

    Events.register('start_retrive_stock_data', StockDataRetriever)

    當點擊“獲取股票數據”按鈕的時候,可以是這樣:

    Events.broadcast('start_retrive_stock_data')

    你會發現StockDataRetriver能夠老老實實的開始獲取數據了。

    需要注意的是,并非將所有事件定義為全局事件是一個好的實踐。在更大規模的系統中,將事件進行有效整理和分級是有好處的。在強類型的語言(如 Java/C#)中,抽象出強類型的EventId,能夠幫助理解系統和進行編程,避免到處進行強制類型轉換。例如,StockEvent

    StockDataLoadedEvent {
    StockData theData;
    StockDataLoadedEvent(StockData theData);
    }
    Event.broadcast(new StockDataLoadedEvent(loadedData))
    

    這個事件的監聽者能夠不加類型轉換的獲得StockData數據。上面的例子是不支持事件的語言,C#語言支持自定義強類型的事件,用起來要自然一些:

    delegate void StockDataLoaded(StockData theData)

    事件管理原則我相信并不難理解。然而困難的是具體實現。對一個新的UI框架不熟悉的時候,我們經常在“代碼的優美”與“界面提供的特性”之間徘徊。實現這 樣的一個事件架構需要在項目一開始就稍具雛形,并且所有的事件都有良好的命名和管理。避免在命名、使用事件的時候的隨意性,對于讓代碼可讀、應用穩定有非 常大的意義。一個好的事件管理、通知機制是一個良好RichClient應用的根本基礎。一般說來,你正在使用的編程平臺如Swing/WinForm /WPF/Flex等能夠提供良好的事件響應機制,即監聽事件、onXXX等,但一般沒有統一的事件的監聽和管理機制。對于架構師,對于要使用的編程平臺 對于這些的原生支持要了熟于心,在編寫這樣的事件架構的時候也能兼顧這些語言、平臺提供給你的支持。

    采用了事件的事件后,你不得不同時實踐“線程管理”,因為事件一般來說意味著將耗時的操作放到別的地方完成,當完成的時候進行事件通知。簡單的模式下,你可以在所有需要進行異步運算的地方,將運算放到另外一個線程,如ThreadPool.QueueUserWorkItem, 在運算完成的時候通知事件。但從資源的角度考慮,將這些線程資源有效的管理也是很重要的,在“線程管理”部分有詳細的闡述。另外,如果能將你的應用轉變為 數據驅動的,你需要關注“緩存以及本地存儲”。

    4 線程管理

    在WEB開發幾乎無需考慮線程,所有的頁面渲染由瀏覽器完成,瀏覽器會異步的進行文字和圖片的渲染。我們只需要寫界面和JavaScript就好。如果你認同“一切皆異步”,你一定得考慮線程管理。

    毫無管理的線程處理是這樣的:凡是需要進行異步調用的地方,都新起一個線程來進行運算,例如前面提到的runInThread的實現。這種方式如果托管在 在“事件管理”之下,問題不大,只會給測試帶來一些麻煩:你不得不wait一段時間來確定是否耗時操作完成。這種方式很山寨,也無法實現更高級功能。更好 的的方式是將這些線程資源進行統籌管理。

    線程的管理的核心功能是用來統一化所有的耗時操作,最簡單的TaskExecutor如下:

    TaskExecutor {
    void pendTask(task) { //task: 耗時操作任務
    runInThread {
    task.run(); // 運行任務
    }
    }
    }
    RetrieveStockDataTask extends Task {
    void run() {
    theData = ... // 直接獲取遠程數據,不用在另外線程中執行
    Events.broadcast(new StockDataLoadedEvent(theData)) // 廣播事件
    }
    }
    

    需要進行這個操作的時候,只需要執行類似于下面的代碼:

    TaskExecutor.pendTask(new RetrieveStockDataTask())

    好處很明顯。通過引入TaskExecutor,所有線程管理放在同一個地方,耗時操作不需要自行維護線程的生命周期。你可以在TaskExecutor中靈活定義線程策略實現一些有趣的效果,如暫停執行,監控任務狀況等,如果你愿意,為了更好的進行調試跟蹤,你甚至可以將所有的任務以同步的方式執行。

    耗時任務的定義與執行被分開,使得在任務內部能夠按照正常的方式進行編碼。測試也很容易寫了。

    不同的語言平臺會提供不同的線程管理能力。.NET2.0提供了BackgroundWorker, 提供了一序列對多線程調用的封裝,事件如開始調用,調用,跨線程返回值,報告運算進度等等。它內部也實現了對線程的調度處理。在你要開始實現類似的TaskExecutor時,參考一下它的API設計會有參考價值。Java 6提供的Executor也不錯。

    一個完善的TaskExecutor可以包含如下功能:

    • Task的定義:一個通用的任務定義。最簡單的就是run(),復雜的可以加上生命周期的管理:start()end()success()fail()..取決于要控制到多么細致的粒度。
    • pendTask,將任務放入運算線程中
    • reportStatus,報告運算狀態
    • 事件:任務完成
    • 事件:任務失敗

    寫這樣的一個線程管理的不難。最簡單的實現就是每當pendTask的時候新開線程,當運算結束的時候報告狀態。或者使用像BackgroundWorker或者Executor這樣的高級API。對于像ActionScript/JavaScript這樣的,只能用偽線程, 或者干脆將無法拆解的任務扔到服務器端完成。

    5 緩存與本地存儲

    純粹的B/S結構,瀏覽器不持有任何數據,包括基本不變的界面和實際展現的數據。RichClient的一大進步是將界面部分本地持有,與服務器只作數據通訊,從而降低數據流量。像《魔獸世界》10多G的超大型客戶端,在普通的撥號網絡都可以順暢的游戲。

    緩存與本地存儲之間的差別在于,前者是在線模式下,將一段時間不變的數據緩存,最少的與服務器進行交互,更快的響應客戶;后者是在離線模式下,應用仍然能 夠完成某些功能。一般來說,凡是需要類似于“查看XXX歷史”功能的,需要“點擊列表查看詳細信息”的,都會存在本地存儲的必要,無論這個功能是否需要向 用戶開放。

    無論是緩存還是本地存儲,最需要處理的問題如何處理本地數據與服務器數據之間的更新機制。當新數據來的時候,當舊數據更新的時候,當數據被刪除的時候,等 等。一般來說,引入這個實踐,最好也實現基于數據變化的“事件管理”。如果能夠實現“客戶機-服務器數據交互模式”那就更完美了。

    我們犯過這樣一個錯誤。系統啟動的時候,將當前用戶的聯系人列表讀取出來,放到內存中。當用戶雙擊這個聯系人的時候,彈出這個聯系人的詳細信息窗口。由于 沒有本地存儲,由于采用了Navigator方式的導航,于是很自然的采用了Navigator.goTo('ContactDetailWindow', theContactInfo)。由于列表頁面一般是不變的,因此顯示出來的永遠是那份舊的數據。后來有了編輯聯系人信息的功能,為了總是顯示更新的數 據,我們將調用更改為Navigator.goTo('ContactDetailWindow', 'contactId'),然后在ContactDetailWindow中按照contactId把聯系人信息重新讀取一次。遠在南非的用戶抱怨慢。還 好我沒養狗,沒有狗離開我。后來我們慢慢的實現了本地存儲,所有的數據讀取都從這個地方獲得。當數據需要更新的時候,直接更新這個本地存儲。

    本地存儲會在根本上影響RichClient程序的架構。除非本地不保存任何信息,否則本地存儲一定需要優先考慮。某些編程平臺需要你在本地存儲界面和數 據,如Google Gears的本地存儲,置于Adobe Air的AJAX應用等,某些編程平臺只需要存儲數據,因為界面完全是本地繪制的,如Java/JavaFX/WinForm/WPF等。緩存界面與緩存 數據在實現上差別很大。

    本地存儲的存儲機制最好是采用某一種基于文件的關系數據庫,如SQLite、H2(HypersonicSQL)、Firebird等。一旦確定要采用本地存儲,就從成熟的數據庫中選擇一個,而不要嘗試著自己寫基于文件的某種緩存機制。你會發現到最后你實現了一個山寨版的數據庫。

    在沒有考慮本地存儲之前,與遠端的數據訪問是直接連接的:

    我們上面的例子說明,一旦考慮使用本地存儲,就不能直接訪問遠程服務器,那么就需要一個中間的數據層:

    數據層的主要職責是維護本地存儲與遠程服務器之間的數據同步,并提供與應用相關的數據緩存、更新機制。數據更新機制有兩種,一種是Proxy(代理)模式,一種是自動同步模式。

    代理模式比較容易理解。每當需要訪問數據的時候,將請求發送到這個代理。這個代理會檢查本地是否可用,如果可用,如緩存處于有效期,那么直接從本地讀取數 據,否則它會真正去訪問遠端服務器,獲取數據,更新緩存并返回數據。這種手工處理同步的方式簡單并且容易控制。當應用處于離線模式的時候仍然可以工作的很 好。

    自動同步模式下,客戶端變成都針對本地數據層。有一個健壯的自動同步機制與服務器的保持長連接,保證數據一直都是更新的。這種方式在應用需要完全本地可運行的時候工作的非常好。如果設計得好,自動同步方式健壯的話,這種方式會給編程帶來極大的便利。

    說到同步,很多人會考慮數據庫自帶的自動同步機制。我完全不推薦數據庫自帶的機制。他們的設計初衷本身是為了數據庫備份,以及可擴展性 (Scalability)的考慮。在應用層面,數據庫的同步機制往往不知道具體應用需要進行哪些數據的同步,同步周期等等。更致命的是,這種機制或多或 少會要求客戶端與服務器端具備類似的數據庫表結構,遷就這樣的設計會給客戶端的緩存表設計帶來很大的局限。另外,它對客戶機-服務器連接也存在一定的局限 性,例如需要開放特定端口,特定服務等等。對于純粹的Internet應用,這種方式更是完全不可行的,你根本不知道遠程數據庫的結構,例如 Flickr, Google Docs.

    當本地存儲+自動同步機制與“事件管理”都實現的時候,應用會是一種全新的架構:基于數據驅動的事件結構。對于所有本地數據的增刪改都定義為事件,將關心 這些數據的視圖都注冊為響應的觀察者,徹底將數據的變化于展現隔離。界面永遠只是被動的響應數據的變化,在我看來,這是最極致的方式。

    結尾

    限于篇幅,這篇文章并沒有很深入的討論每一種原則/實踐。同時還有一些在RichClient中需要考慮的東西我們并沒有討論:

    • 純Internat應用離線模式的實現。像AdobeAir/Google Gears都有離線模式和本地存儲的支持,他們的特點是緩存的不僅僅是數據,還包括界面。雖然常規的企業應用不太可能包含這些特性,但也具備借鑒意義。
    • 狀態的控制。例如管理員能夠看到編輯按鈕而普通用戶無法看見,例如不同操作系統下的快捷鍵不同。簡單情況下,通過if-else或者對應編程平臺下提供的綁定能夠完成,然而涉及到更復雜的情況時,特別是網絡游戲中大量互斥狀態時,一個設計良好的分層狀態機模型能夠解決這些問題。如何定義、分析這些狀態之間的互斥、并行關系,也是處理超復雜
    • 測試性。如何對RichClient進行測試?特別是像WPF、JavaFX、Adobe Air等用Runtime+編程實現的框架。它們控制了視圖的創建過程,并且傾向于綁定來進行界面更新。采用傳統的MVP/MVC方式會帶來巨大的不必要的工作量(我們這么做過!),而且測試帶來的價值并沒有想象那么高。
    • 客戶機-服務器數據交互模式。如何進行客戶機服務器之間的數據交互?最簡單的方式是類似于Http Request/Response。這種方式對于單用戶程序工作得很好,但當用戶之間需要進行交互的時候,會面臨巨大挑戰。例如,股票代理人關注亞洲銀行板塊,剛好有一篇新的關于這方面的評論出現,股票代理人需要在最多5分鐘內知道這個消息。如果是Http Request/Response, 你不得不做每隔5分鐘刷一次的蠢事,雖然大多數時候都不會給你數據。項目一旦開始,就應當仔細考慮是否存在這樣的需求來選擇如何進行交互。這部分與本地存儲也有密切的關系。
    • 部署方式。RichClient與B/S 直接最大的差異就是,它需要本地安裝。如何進行版本檢測以及自動升級?如何進行分發?在大規模訪問的時候如何進行服務器端分布式部署?這些問題有些被新技術解決了,例如Adobe Air以及Google Gears,但仍然存在考慮的空間。如果是一個安全要求較高的應用,還需要考慮兩端之間的安全加密以及客戶端正確性驗證。新的UI框架層出不窮。開始一個新的RichClient項目的時候,作為架構師/Tech Lead首先應當關注的不是華麗的界面和效果,應當觀察如何將上述原則和時間華麗的界面框架結合起來。就像我們開始一個web項目就會考慮domain 層、持久層、服務層、web層的技術選型一樣,這些原則和實踐也是項目一開始就考慮的問題。

    感謝

    感謝我的同事周小強、付瑩在我寫作過程中提供的無私的建議和幫助。小強推薦了介紹Google Gears架構的鏈接,讓我能夠寫作“本地存儲”部分有了更深的體會。

    這篇文章是我近兩年來在RichClient工作、網絡游戲、WebGame眾多思考的一個集合。我嘗試過JavaFX/WPF/AdobAir 以及相關的文章,然而大多數的例子都是從華麗的界面入手,沒有實踐相關的內容。有意思的反而是《大型多人在線游戲開發》這本書,給了我在企業 RichClient開發很多啟發。我們曾經犯了很多錯誤,也獲得了許多經驗,以后我們應當能做得更好。

    參考

    相關閱讀:

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

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

    [ ThoughtWorks實踐集錦(3)] RichClient/RIA原則與實踐(上)


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

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

    posted @ 2009-03-18 09:46 二胡 閱讀(156) | 評論 (0)編輯 收藏

    僅列出標題
    共165頁: First 上一頁 94 95 96 97 98 99 100 101 102 下一頁 Last 
    主站蜘蛛池模板: yellow视频免费在线观看| 国产日韩久久免费影院| 久久亚洲中文字幕无码| 曰曰鲁夜夜免费播放视频| 亚洲精品人成电影网| 国产精品久久永久免费| 亚洲人成在线中文字幕| 中国videos性高清免费| 亚洲欧洲日产国码无码久久99 | a拍拍男女免费看全片| 亚洲国产成人久久综合碰| 草久免费在线观看网站| 亚洲人成网站免费播放| 自拍偷区亚洲国内自拍| 国产免费小视频在线观看| 一个人免费观看www视频| 亚洲精品无码成人AAA片| 免费女人高潮流视频在线观看| 国产一级高清免费观看| 成人免费夜片在线观看| 日本免费一区二区三区最新| 久久午夜羞羞影院免费观看| 午夜在线亚洲男人午在线| 亚洲国产香蕉人人爽成AV片久久 | 亚洲综合伊人制服丝袜美腿| 两性色午夜视频免费播放| 亚洲va久久久噜噜噜久久| 免费福利网站在线观看| 免费一区二区三区在线视频 | 国产一区二区三区无码免费| av午夜福利一片免费看久久| 免费一级毛片清高播放| 亚洲国产综合AV在线观看| 青青视频观看免费99| 国产精品无码亚洲精品2021 | 亚洲国产精品成人精品小说| 99re免费在线视频| 国产成人亚洲综合网站不卡| 亚洲精品无码专区2| 91在线品视觉盛宴免费| h片在线播放免费高清|