搜尋部落格文章

顯示具有 設計模式 標籤的文章。 顯示所有文章
顯示具有 設計模式 標籤的文章。 顯示所有文章

2012年9月20日 星期四

[轉] Flash 務實主義 - Flash中的 MVC

FLASH與傳統環境的不同點
MVC最早在1979年的時候第一次被人提出。 不過,當時還不存在網絡應用的概念。 之後當萬維網誕生之後,又過了很長時間……
它並不是自誕生就開始流行的,而改變的原因很簡單——因為兩個極其流行的開發框架包含了這種模式,它們就是:Struts 和Ruby on Rails。 之後,模仿者蜂擁而至。 所以,在人們眼裡看來,實際上是先有的Struts,然後才有的MVC,也無怪乎MVC的概念會始終沾染著Web概念,乃至和一些框架附加內容牽涉不清。
因為Struts很好用,別的不說,至少讓HTML顯得乾淨了很多。 所以很多人都在用Struts,這未必是因為需要MVC模式,而是因為他們需要Struts。 因此,當環境變化後,我們不使用Struts而是在使用一些其他的框架的時候,是否還應該像以前那樣使用MVC框架就成為了一個問題。 因為環境不同,即使在其他語言中使用MVC框架很普遍,也不代表在新環境裡同樣應該是如此。
AS3與傳統語言的不同點:
• AS3是單一語言環境,多層代碼混在一起問題沒那麼嚴重。
• AS3正常情況都是一次性編譯全部代碼,即使用了MVC框架還是需要一起編譯。 單獨編譯一個模塊減少編譯時間有別的辦法,不需要依賴MVC。
• AS3本身的事件和動態特性和一些框架的功能重複。
• AS3目前的框架還很不成熟,沒有提供比較醒目的功能。

結果是,至少,目前AS3的MVC框架比起傳統語言並沒有那麼突出的作用,就算用了,也不會像Struts那樣有質的變化。 而且,至少在我看來,AS3的框架使用成本卻不見得比Struts低。 兩者相減,結果就很麻煩了。
而且,AS3在不使用框架的時候有它自己的優勢,使用框架會毀掉這些優點:
• 有一個相對還可以的調試器,使用了框架會調試上產生麻煩,主要體現在單步調試步驟變多的問題上。
• 阻礙使用IDE的功能。 以Flex Builder為例,你可以通過Ctrl+單擊(F4)跳轉到指定方法的具體實現,通過搜索引用面板從方法的實現跳轉到調用方法的位置。 使用框架後,這些功能都會失效。
• Flex framework相關功能會難以使用,諸如綁定。 而且,Flex Builder支持拖拽式的將數據接口綁定到視圖的功能,可以部分實現零代碼編程,框架也會阻礙這個過程。

此外,企業應用和網站還好說,遊戲還有另一種情況。 遊戲的結構並不同於原來的專門用於呈現數據的結構,可能也就是其中的用戶界面(User Interface)部分和以前的結構比較類似,其他的諸如地圖,諸如人物,無論怎麼想也無法套用MVC框架,首先從效率上就說不過去。 舉個例子,一個項目有3個客戶端人員在開發,一個在做地圖,一個在做戰鬥,一個在做UI。 前兩者都和MVC沒什麼關係,結果只有一個人在用MVC框架開發界面……而且,開發前兩者的時候,開發以及協作難度其實是比開發界面要高的,既然他們都搞定了,為什麼開發界面的人還必須靠框架輔助才能解決這個問題?
這使得FLASH比起一般的情況,會更加不適合使用MVC框架。

不使用現有框架並非無法實現MVC
既然我在說框架不好用。 那麼不用框架,我們又該怎麼做呢?
實際上,如果你只是想實現單純的模型—視圖—控制器(Model View Controller)分工職守,它只是一個架構模式而已。 將模型和視圖的代碼分開,並提出控制器的代碼,然後互相調用各自方法就算完事了。 Model的全部引用放在固定的位置,View的引用使用靜態屬性儲存或者用管理類管理,Command可以作為函數或者類直接初始化並執行,亦可以通過反射。 這並不需要專門的工具類來輔助,附加成本也比較小,自然就可以適用於任何規模的項目。
當然,你可以實現一個簡單的通信框架,提供必要的功能,如果你需要的話。 這和使用一些專門的MVC框架需要的成本是完全不同的。
然而,我的意見則是——MVC是非常好的架構模式,不管什麼樣的項目都建議嘗試使用,但是用框架的話,請務必謹慎。
關於最簡的MVC,最近看到一個讓人很囧的例子。 不過這個例子對大家理解MVC是有幫助的。
【Flash <wbr>务实主义】Flash中的 <wbr>MVC
這玩意的確……基本算是MVC,只差一點而已。 MVC是架構模式,至少結構上要分開,即使不分文件至少要讓能看得出來誰是誰(原文可沒有紅字),所以只需要把這些代碼分成三個文件,那就可以稱得上是最簡的MVC了。
這是我在他的下面補充的代碼。
【Flash <wbr>务实主义】Flash中的 <wbr>MVC
結果是,View只關心與自己相關的Model和Command,Command只關心與自己相關的View和Model,Model誰都不關心,這和一般情況需要的解耦目標是一致的。 雖然這樣並不算完全解耦,但是至少在思路和邏輯分離上是做到了,僅僅是協作方面存在問題,比如無法實現自由的並行開發,而這個加入簡單的反射也可以解決。
所以,單純的MVC並不困難,沒什麼要不要放棄一說。 還有就是上面只是極端例子,但就算是這種東西,比起完全不實現MVC,也至少實現了50%以上的內容。

是否使用框架應當理性對待 程序員都是理科生,應當用理科生的思考方式(當然我並沒有讓你們都去模仿Sheldon)。 在使用MVC框架的過程中,不管是覺得好,還是差,都要考慮清楚問題的源頭在哪。
覺得MVC框架不好用,降低效率,是否曾經有過平行對比的例子,你能否確認不用它效率就確實能提高? 效率低有沒有可能是框架之外的原因?
覺得MVC框架好用,提高效率,是否有平行對比的例子? 你怎麼就知道是使用了框架的功勞,而不是規範了代碼結構,制定了新的協作流程,甚至是開發人員水平提高的功勞? 怎麼知道MVC框架並沒有起了反效果?
使用了框架,看到了結果,然後根據結果的好話直接判定框架的好壞,這太武斷了,作為一個理科生,我們絕對不能這樣做。 至於那類連比較都沒有,而是以“我用了框架,項目依然完成了,沒有因為用了框架而失敗”這種理由來支持使用某個框架的人,我無言以對。

推薦MVC框架 我並沒有完全反對使用MVC框架。 這要看你的項目類型,規模,人數。 滿足條件的時候當然可以使用。 尤其是在企業應用裡,如果你有幸出現六個客戶端的話,沒MVC框架可能還真是會出問題。
pureMVC和Cairngorm是兩個較早出現的框架,目前我不建議再使用它們。 pureMVC的問題在於過於強調分離而缺乏實際功能,提供的便利很難抵消它本身的消耗,性價比較低。 Cairngorm的問題則在於過於強調模型更新視圖的流程,限制太多,靈活程度不夠。
後出的幾個框架就好多了,Mate使用了一個全局事件定義,配合FLEX寫法非常簡略。 Swiz則是用控制反轉+依賴注入,也就是Spring的做法,而且元標籤注入的方式很有趣,感興趣的可自行查閱資料。
我這裡要說的是Robotlegs。 這是一個和Swiz非常相似的框架,但也有一些自己的特點。 首先它是基於pureMVC的,你依然可以像pureMVC這樣來使用它,對於相信pureMVC的團隊它是很容易接受的代替品。 他讓pureMVC也同樣擁有了控制反轉和依賴注入,包裝了大部分功能,配置代碼大大減少,而且不管用不用FLEX framework都可以很自然地使用它。
Robotlegs的教程可以看這裡:
但我得提醒大家,雖然我覺得Robotlegs很便利以及有趣,但是並沒有在項目裡使用它,因為我的項目規模不大,而且是遊戲。 實際上,我甚至自己實現了一個依賴注入框架,可以很簡單的加入到現在的項目中,成本幾乎為零,卻依然沒有去用。 使用一個東西要看是否需要去用,而不是可以用就用,更不是“因為用了沒有遇到問題所以就用”。 用一個東西必須有收益才可以,尤其是在明明看到有損失的時候。 僅僅是用“看起來更正規”這類自我滿足的理由來決定自己的行為,太愚蠢了。
當然,如果你需要它,那就應該毫不猶豫的使用。 不要受到抱怨框架的人影響,他們大部分都有自己的問題,提出的理由也未必是正確的,你並不一定會赴他們後塵——前提是你真的需要它,而且,要將使用框架需要的條件全部補齊。

就算是使用MVC框架也不需要完全解耦 解耦是一個擴展性要求,但擴展性要求並不是越多越好的。
這是一個普遍的誤區。 諸如使用pureMVC的人,很多都糾結於完全的解耦,以至於用了Command,在Command中改變View的時候還是必須要發一遍Notification。
Command這種類,一般都是在相關的View,Model完成後才開始編寫的。 比如普通的StartupCommand,OpenWindowCommand,沒有對象又如何編寫? 它在編寫順序上應該是,就算不是也是可以放在View和Model之後的。 那麼在協作關係上,他就可以直接訪問所有相關類,不需要為了這種原因而解耦。
雖然pureMVC將消息全局化了,但是消息實際上是分局部和全局的。 比如一個Proxy發生變化要求所有監聽某個消息的View更新,那麼當然應該發一個叫做I_AM_CHANGED的Notification,並由不同的View來監聽這個消息並更新,這個就應該是全局消息。 但有些消息就是局部的,是一對一的,比如一個叫做SEND_DATA_TO_WINDOW1的消息,按它的字面意思就應該是刷新WINDOW1,那麼由它來觸發的Command,就應該直接耦合WINDOW1這個View來設置值,而不是再發個類似REFRESH_WINDOW1的消息,因為SEND_DATA_TO_WINDOW1的名字已經確定是針對這個View了,如果最終它卻沒有操作這個View,那才是有問題的吧?
解耦歸解藕,但是對於已經有了意義上的聯繫的模塊,結果卻不耦合,在任何時候都是沒有意義的。 即使需求變化,邏輯變化了,使得SEND_DATA_TO_WINDOW1最終不是改變WINDOW1的數據,而是WINDOW2的數據,那麼這個Command連帶相關Notification的名字就必須修改,也就是說,意義上的緊密聯繫,在實際操作上和耦合了是一回事。 既然已經是這樣了,再做成不耦合,給自己製造麻煩的又有什麼意義呢?
除了上面的情況,我們也要考慮,真的有必要將項目拆得那麼細緻麼,有沒有必要為了1%以下的可能性來解耦兩個相關性很強的部分? 比如一個叫做ShopPanel的View和一個叫做ShopModel的Model,到底在什麼情況下,ShopPanel會不去調用ShopModel,而是別的東西? 而且Panel上可能還有各種文字,使得自己意義上只能調用商店的數據。 真是要調別的東西,應該重新製作一個新的View吧? 而且別忘了pureMVC是可以多個Mediator套用一個View的。 這種情況下,我們直接在Mediator中耦合ShopModel,有什麼不可以的? 當然,反過來,ShopModel被多個View調用的情況很普遍,所以我們不能讓它來耦合ShopPanel。 這些規則實際上是很明確的,是完全可以預知的。 就算預知錯誤,也是很容易修正的。
解耦是手段,而不是目的。 盲目的最求擴展性,只會讓自己的程序變成一盤散沙。 正是適量的耦合,程序才能擁有一個確定的形態,才不會讓人感到茫然。

普遍誤解 其實現在使用框架的人群裡,真的能夠發揮框架長處的確實比較少,尤其是在水平層次較低的Action Script開發人員之中。 一方面,這污染了框架的名聲,同時也是不建議使用框架的理由之一,因為人員水平限制也是實際項目中不可迴避的現實問題。
如果你決定使用MVC框架,就必須提高自己的認識。 我揀幾個最常見的問題來說吧。
• 並不是用了消息通信就算用了MVC
消息通信只是一個手段,只使用框架的通信功能在View之間發送消息的話,而將其他功能全部拋棄的話,直接使用事件更好,那還套一個MVC框架就是在沒事找事了。
MVC關鍵還是在於代碼邏輯的分配,通信只是個附贈品而已。 要了附贈品而扔了原來的商品——咱們買的不是小浣熊乾脆面,對吧。
但是如果你的目的就是贈品,其實也沒什麼。 比如你就是想用的這個通信框架來發發消息,就不打算用它的MVC,又或者MVC部分是自己實現的。 那麼我還是建議你把消息部分乾脆也自己實現了,別人的始終沒有自己的好。
• 既然用了MVC框架,就不要圖省事要清楚,鬆散耦合不僅僅是一個形式,目的在於減少模塊間的聯繫。 因此,如果你一方面在解除兩個模塊之間的耦合,一方面自己又沒頭沒腦的將其他模塊的內容耦合進來,就會使得你的行為變得沒有意義。
現在一些人一方面在硬套框架,一方面又圖省事而隨意引入其他類,就屬於這樣的行為。 那些類是可以引入,但你這樣做,框架本身的意義就沒有了。 要不你就不用框架,要不就別這樣幹,這裡只能二選一。
• 是Mediator知道View的一切,View完全不知道Mediator,而不是相反對於使用pureMVC的同僚們,我真是不明白你們到底是怎麼把這個反過來理解成“View知道Mediator的一切,而Mediator完全不知道View”的,因為​​官方實例上寫的很明白。 估計是把mediator當成通信專用的模塊類了吧。 但是如果你放棄了mediator分離View代碼的特性,只是用來通信的話,至少要保留原來的通信功能,就是讓Mediator依然可以直接訪問View。否則既然Mediator是用來通信的,它卻不能操作View,結果還得設法和View再通信一次……
pureMVC要求“View完全不知道Mediator”是為了能夠在不修改View的情況下更換Mediator,但這種需求並不多(多的是在Mediator不變的情況更換View,這個需要用接口或者條件判斷解決),所以可以放寬點讓他們互相引用,這樣兩者通信都能暢通。
pureMVC實現“View完全不知道Mediator”的方法是用Mediator直接去監聽View的某個組件的鼠標事件。 這只需要監聽一次,也不需要傳遞消息。 Mediator存在的期間,按pureMVC的標準View應該是沒有任何監聽邏輯的。

[轉] Flash 務實主義 - 構建易維護的程序:高效修改

一般程序開發完成後就進入了繁瑣無趣的後期維護階段,請不要以為一個不停更新的項目後期維護是一件輕鬆的事情,它會暴露出開發過程中的所有硬傷,不規範的寫法、混亂的邏輯結構、高耦合導致地牽一發而動全身。 雖然開發內容實際上減少了​​,但人力成本反而更高。
要提高這方面效率有很多技巧,本文介紹的內容只是起點--如何快速找到項目中需要修改的代碼。
一般出現問題首先看到得是表現部分,例如對話框,關係到一些具體邏輯或某個服務端請求,即使不是很清晰的部分也一定有臨近的區域。 根據表現找到其對應代碼,我將其稱為定位。

搜索關鍵字:泛用但低效 搜索關鍵字是廣泛使用的方法。 例如,你在節目上看到某個圖片,找到圖片標誌,在所有代碼中搜索圖片標誌,一定可以找到調用這個圖片的代碼。 再如,屏幕中顯示的文本,也能找到對應的語言包標識,找到相關代碼。 然而,這種做法效率很低,因為你要找到標識的具體拼寫,搜索項目代碼查找關鍵字也需要時間。 所以下面主要介紹如何不借助搜索直接找到目標代碼。

包結構 類一般可以從兩個維度分類,一個維度是結構類別,如模型、視圖、控制器,甚至工具類、組件,另外一個維度是業務類別,如商店、人物、戰斗等各種不同模塊。
目錄裡的文件只有一個根,類似單繼承,所以你只能用一個分類做為大類別。 一般情況都是用結構類別做為大類別,因為結構類別一般是固定不變地,而業務類別可能會經常變化。 例如下圖所示情況:
【Flash <wbr>务实主义】构建易维护的程序:高效修改
這樣做是為了避免大量類混雜在一起,只有分到不同目錄才能徹底解決這個問題(目錄可折疊,必要時子目錄或文件命名可重複)。 開發模塊時,只需展開關心的目錄,避免其他文件干擾。

文件命名 推薦文件名 ​​根據結構類別做前綴,如視圖以UI開頭、後台請求以Rpc開頭等。 如果沒有前綴,命名時很容易遇到重複的情況。 再以業務類型設定第二個前綴,使得沒有目錄時,按字母排序時同一系統中的類被排到一起,當然也可以防止重名。
【Flash <wbr>务实主义】构建易维护的程序:高效修改
恰當的命名會在利用代碼提示引入類時提供便利,而且在打開類(ctrl+shift+T)對話框裡也比較容易找到需要的類。 當然,主要還是在查看包資源時,列表會比較整齊,方便找到特定文件。
以上是為了幫助你在知道類的功能和類別卻不確定具體命名時使用的,你可能不記得具體名字,但應該可以判斷出它所在的包,但僅僅這樣是不夠的。

邏輯分離是前提 邏輯要按一定規則分開到不同類中,否則你的查找目標本身就不存在。
MVC 是實現邏輯分離的方法之一。 根據MVC 思想將類分開,你就會很清楚知道,與用戶交互、顯示有關的類是在視圖中,與數據格式轉換、獲取特定數據(諸如獲得圖標實例)、判斷(諸如isPropTask() 之類)、修改數據邏輯(諸如修改經驗值觸發升級)是在模型中,而製定特定服務器請求、設置模型數據、引起數個視圖更新的代碼一定是在服務器請求類的result 中。 這樣就能確定目標位置,即使不能確定具體某個類,也能界定到某個包範圍內。
當然這是需要事先約定的,但只要開發者理解並遵守這個約定,就可以做到不依賴搜索關鍵字也能立即找到需要修改的代碼。 毫無疑問這種做法是值得的。

從視圖著手 視圖是最容易找到的部分,然後根據交互事件模擬用戶操作過程,通過調用關係一層層查找,最後就能找到需要的部分。
無論你的代碼結構如何,這種方法都是通用可行的。 如果你的目標就是視圖,需要修改的是諸如佈局、顏色、數據填充邏輯,這樣做就可以了。 但如果你的目標不是視圖,這並不是最好的辦法,因為畢竟需要從視圖一層層中轉,會多幾個步驟而不是直接找到,這自然影響效率。

模型 模型不只是數據。
很多人不明白為什麼要在數據之外套一層模型。 代碼放哪裡比較好要以可複用性做為標準,放在模型裡的邏輯應該是和數據密切交流的,最基本的是數據序列化和反序列化。 這些內容不少人認為應該寫在請求完成函數里,而實際應該寫在模型中,因為同一個模型可能會由不同請求生成,它們傳入的數據格式是一樣的,只有在模型中解析才能重複利用這段功能代碼。
此外模型還需要負責和數據相關的邏輯,以某個RPG 遊戲為例:
• 人物屬性變化:包括金錢變化(錢不夠會失敗並提示充值),經驗變化(可能需要升級),還有數據更新後對應視圖更新;
• 道具管理:增加/刪除道具,獲得道具數量,判斷道具滿;
• 計算:諸如保存地圖模型可以提供計算A* 的方法;

上述邏輯是針對遊戲玩家的,只會存在一個模型,數據模型是固定的,所以無論放在哪裡只要集中都容易查找,但顯然放在模型裡最好理解,而且這樣即使出現多角色需求,需要修改的地方也很少。
模型通常也會提供一些簡單的數據轉換方法,例如:
• 獲得圖標,類似的方法有獲取格式化文本、獲得ToolTip 信息等;
• 校驗,一般一組條件的與或關係或者大於等於小於判斷,也可能有循環遍歷數據進行統計等複雜表​​達式;

如果按照約定編碼,將這些邏輯存放在模型中,這樣就不能通過視圖快速找到代碼,但模型數量少、邏輯少,會比放在視圖裡找快很多。 你也可以用查找引用方法找到調用模型方法的代碼片段,以確定這個邏輯的入口,方便逆向追踪。 這樣只需修改一處,所有相關部分都會發生變化。

命令(Command/Action)
所謂命令,其實很大一部分都是用來請求服務端數據的,設計中有result 方法可以在返回結果後處理一些事情,例如整理數據格式、設置模型、調用視圖更新方法。 雖然很多人將這些功能發明放在視圖中,但顯然放在Command 裡重用率更高且更易於定位。
服務端返回的數據會經常變換,雖然具體解析是在模型中完成,但返回的數據常常是多個數據拼湊在一起,可能是數組或者一些簡單數據類型,這部分可以有Command 處理,做特殊解析並給相關模型賦值。 由於這部分需要與服務端溝通,是容易出錯的地方,放在Command 裡容易找到,修改時也可以省去不少麻煩。
命令除了給模型賦值,也還會有一些觸發操作。 如果確定某個邏輯是請求返回一定執行的也可以放在這裡。 例如更新視圖、觸發附加邏輯、首次購買某物品的彈窗。 這部分常發生變化,放在Command 執行也易於調整。當然Command 也可以與服務端請求無關,但道理是一樣的。
總而言之,原則是盡可能不要將代碼放在視圖,而是放在聯繫更緊且數量少、代碼少的一方,這樣就能更快找到修改代碼位置。 Command 一般用來調用模型和視圖方法,是其他邏輯的入口,自己只有少量代碼。

常量類 配置類數據只可能存在於三個地方:服務端,本地配置文件,常量類。
對於服務端數據配置,客戶端只單純接受,本地配置文件一般都保持經常更新的大量數據,所以零散數據配置通常都存在常量類裡,例如等級上限、各級經驗分配、特定功能花費。
很多開發者都喜歡偷懶,例如一個功能需要花費5元開啟,開發者直接在代碼裡寫5。 這樣看起來算不算神秘數字先不談,要知道,這種數據即使說絕對不會變而它未來變化的機率也會超過30%。 所以常量是必須有的,哪怕數值是1,也應該寫成常量,因為未來可能變成2,在代碼裡保留數字始終會有隱患。 如果你設置為常量,對於這類內容就可以直接找到常量修改,而不用關心其他部分,也很容易找到使用這個常量的代碼片段。 如果沒有常量​​的話,就只能藉助和這個數值相關的內容引導並藉助關鍵字搜索以確保修改無遺漏了。
此外,有多版本時配置可能會變化,如果硬編碼寫死在程序中,需求會很難實現。

其他 這裡介紹是以通用MVC為例,因為熟悉的人較多,容易理解。 遊戲中除用戶界面外,結構會更複雜,雖然也有類似模型視圖這樣的概念,但層次需要分得更細緻。 其實這些分離方法都是約定的,既然要分離,就要更合理、更易於理解、更具有可複用性。 高效修改首先要容易找到問題癥結點,要達到這個目的,就要求代碼結構整理。 良好的結構也可以讓約定更加簡潔,易於記憶和理解。
上述這些內容,框架並不能幫到你,因為框架大多是限制你分成幾部分,而沒有也無法限定這幾部分具體是什麼內容。 因此你需要刻意地約定,而這個刻意地行為,對於減少修改維護時的人力成本,比框架重要百倍。