從〇開始構架前端(NLDV框架)


從〇開始構架前端(NLDV框架)

框架 設計模式


摘要:一個普通應用,大到微信, 小到豆瓣FM,必不可少的都包括四部分:Network、Logic、Data、View(NLDV)。如何把他們組合起來,結構清晰、又協作便利,是前端主程的基本修養。本文用通(有)俗(點)易(啰)懂(嗦)的語言,界定了這四個模塊的職能范圍,同時提供了一種簡單易用的組織方式。知者可互動,不知者可參考。



目錄

先嘮點嗑

說說自己吧:畢業三年,經歷4個項目,前兩個主做功能開發,后兩個全面負責。因為大學對UI交互深感興趣,夢想成為優秀的交互設計師,所以畢業就做了前端,想着至少也離得近點。不料一入代碼深似海,以前看過的十幾本交互書也隨風而去,腦子里剩下的只有:幾行干巴巴的代碼、幾個平台的API、和一點不成熟的總結。借此機會,整理一下。

我把他名為NLDV,也就圖一叫着方便(首字母的組合),並不是想標新立異。你或許會想到MVC,其實本質上跟MVC沒啥區別。只是與時俱進,現在App幾乎都是連着網的,也就把Network提出來了。

從第三個項目開始,這個NLDV的結構在我意識里漸漸清晰。第四個項目(游戲),因為是從零開始,我把NLDV的結構應用到了項目中。經歷了渲染引擎更換和網絡引擎更換,過程中其它模塊兒做得改動極少,切身體會到它的妙處,忍不住前來分享。

從賬號登陸談起

我們從一個最簡單的登陸請求談起。按照NLDV框架的思想,步驟如下:

  1. Logic告訴Network:以后你收到來自遠方服務器的消息都跟我打聲招呼,不然我告訴程序員整死你丫。(Logic向Network注冊網絡監聽函數。)
  2. 用戶點擊登陸按鈕,此時View調用Logic的登錄方法。Logic趕緊寫封信,告訴Network把這封信送到服務器。(Logic根據傳入的參數,構造相應的消息體,Network負責把消息發出去)
  3. 漫長的等待。。。
  4. Network終於等到服務器發來的消息,就急急忙忙遞給Logic。Logic打開信封一看:“尼瑪,居然能一次成功了!32個贊!”
    1. 可是信上還有性別,年齡,頭像,郵箱等等等信息,頭都大了,不記下來恐怕是隔夜就忘啊。於是,找來Logic的御用秘書Data(只有Logic能寫入),這些信息就交給你了,臨時存儲還是永久存儲我不管,反正我和View來取的時候,你要能給我。
    2. 好消息要和大家分享,於是Logic全應用廣播:“我們已經出色的完成了登陸任務,大家再接再厲”
  5. View收到此廣播消息,用界面告訴用戶“親愛的,你登陸成功了”。到此,完成一次登陸。
    PS:對於大多數界面實例,Logic剛剛發的這條廣播是可以當耳邊風的,因為跟自己業務無關。但,對於登陸界面來說,他必須時刻豎起耳朵監聽這個消息,要不然就是失職。所以一般情況下登陸View會在發送登錄請求之前就向系統注冊監聽這個消息的函數,以確保萬無一失。這是后話,沒看懂也沒關系

職責分配

首先,看一幅圖:
NLDV Diagram

通過上面的例子和圖示,可以來總結一下NLDV四大家族各自的職能范圍了:

Network

Network的是數據交流的基礎。在這里並不單指socket,並且不暴露任何底層網絡的實現。而是一個更加完整、穩定,有糾錯功能的職能單位。主要功能:

  • 響應Logic的調用,將構造好的消息體轉換成服務器能識別的字節串,發送出去。
  • 接收服務器發過來的字節串,轉換成前端可識別的結構體,通知Logic。
    • 前后端在定義消息的時候,一般會用一個消息號(/或 主消息號-子消息號對)來唯一標識一種消息。
    • 而Logic也不止一個,賬號系統,業務系統等,每個系統有對應的Logic,分別處理相關的業務邏輯。
    • 一個Logic僅僅會對某一些消息感興趣,所以,它只向Network注冊自己感興趣的消息號。
  • 容錯處理。比如有限次的自動重發,需要的時候自動重連,網絡徹底不可用時通知Logic等。

我們用最少的接口來定義它:

 
 
 
         
  1. // Real msg struct will implement this interface, adding some getter/setters.
  2. interface IMessage
  3. {
  4. uint getMsgId();// unique id for a type of msg
  5. byte[] toBytes();// serialize to bytes.
  6. void parseBytes( byte[] bytes); //deserialize to useful info.
  7. }
  8. //Generally, "Logic" will implement this interface for recieving data
  9. interface INetworkHandler
  10. {
  11. void onMessageRecv( IMessage msg);
  12. }
  13. interface INetwork
  14. {
  15. void send( IMessage msg );
  16. void registHandler( uint msgId, INetworkHandler handler );
  17. void unregistHandler( uint msgId, INetworkHandler handler );
  18. }

Logic

Logic是一個應用中核心實現業務邏輯的部分。主要功能有:

  • 響應來自用戶(View)的功能請求。或通過寫入Data來改變狀態,或構造消息體發送到服務器。
  • 收到網絡消息后,做相應的邏輯處理,將數據的改變寫入Data,最后廣播本地事件。
    • 這里的本地事件通常用一個 字符串或者整數指代,稱為本地事件ID。
    • 這個過程通常會用到觀察者模式。有一個本地消息中心LocalMessageCenter,監聽者(通常是View)用本地事件ID來向LocalMessageCenter注冊,而Logic調用LocalMessageCenter.dispatch( localEventId ) 即可廣播此本地事件。
    • 是的,Logic直接調用View的方法也能達到同樣的目的。但是,為了減小Logic和View之間的耦合性,還是選用LocalMessageCenter作為中間層。隨着需求的改變,View類可能面目全非,但LocalMessageCenter的接口卻可以長久不變。

Logic的大致結構如下:

 
 
 
         
  1. class SomeLogic implemets INetworkHandler
  2. {
  3. SomeLogic()
  4. {
  5. Network.getInstance().registHandler(1, this);
  6. Network.getInstance().registHandler(2, this);
  7. }
  8. void onMessageRecv( IMessage msg)
  9. {
  10. switch( msg.getMsgId() )
  11. {
  12. case 1:
  13. logicHandler1(IMessage msg);
  14. break;
  15. case 2:
  16. logicHandler2(IMessage msg);
  17. break;
  18. ...
  19. }
  20. }
  21. //請求操作
  22. void logicReq1(...);
  23. void logicReq2(...);
  24. ...
  25. //消息處理
  26. void logicHandler1(IMessage msg)
  27. {
  28. //TODO: 收到消息,邏輯處理
  29. //TODO: 向Data寫入數據
  30. //TODO: 廣播本地事件
  31. LocalMessageCenter.getInstance().dispatch( "Logic1Compelete" );
  32. };
  33. void logicHandler2(IMessage msg);
  34. ...
  35. }
  36. ILocalMessageHandler
  37. {
  38. void onLocalMessage( String msg );
  39. }
  40. LocalMessageCenter
  41. {
  42. static LocalMessageCenter getInstance(); // Singleton
  43. void dispatch( String msg );
  44. void regist( String msg, ILocalMessageHandler handler );
  45. void unregist( String msg, ILocalMessageHandler handler );
  46. }

Data

這個模塊是全應用的數據中心。提供兩個功能:

  • 存儲。前面已經提到,基本只有Logic對Data有寫入權限。由於沒有想到好的辦法,這個規范暫時只能通過編碼習慣來約定,沒有做框架級的約定,如果大家有好的辦法,歡迎補充。Logic 不關心數據的存儲方式:同步OR異步,臨時OR永久;這些都由Data自己決定。
  • 讀取。Logic和View都會使用到Data中的數據。但是需要注意異步讀取的問題。一般App要求View對操作的響應速度要在0.1s級別。所以,若涉及大量的數據存儲或讀取,便需要借助異步處理。對於存儲,我們可能不是那么關心異步存儲什么時候結束,只需要知道它成功了既可。但對於讀取,經常遇到的情況是:頁面上加個菊花,數據完全讀取成功之后,移除菊花。這就需要一個通知機制。此時,我們也會用LocalMessageCenter作為通信的橋梁。

View

NLDV框架對View的限制,相比以上3個模塊,非常少。因為,對於NLDV框架來說,View跟特定的平台無關,即它是對各種不同平台顯示框架的抽象。在IOS里它是UIFramework,在Android里它是xxx,在游戲里它是openGL,甚至,在下面會講到的機器人模擬器里它是一堆測試代碼。

在這個框架里,View只在兩個過程中出現:

  1. 調用Logic的方法,發送邏輯請求。
  2. 監聽本地事件,讀取Data,顯示內容。

只補充一點:在View顯示過程中,需要用到很多二級數據(二級數據,就是跟原始數據相對,對原始數據進行整合或者篩選后得到的數據),這些二級數據的處理過程最好在View中處理。因為這些代碼大多跟特定的界面有關,而跟App的主要邏輯關系不大,為了以后更改方便,最好寫在View里。

WHY NLDV?

看到這里,讀者大概能隱約的感受到NLDV的一些優點,但是又不那么清晰,要不要看下去呢?下個里程碑就到了看這個人扯淡有用么?再看下去兩盤Dota的時間可就沒了丫。。。

別急,舉幾個栗子提提神。

引擎更換

最早,因為兼容PC版本的斗地主,我們采用了原始的字節對齊的方式進行網絡傳輸。又因為設計失誤,網絡層字節的pack和unpack以及異步讀取的處理,都各種出問題。(因為需要跨平台,所以我們用了C++語言,采用了最基礎的BSD socket進行socket連接。)

結果是,游戲在網絡不穩定的情況下各種閃退。這下產品經理不高興了。又因為當初開發網絡層的同學接手了其他事情,只剩下我各種修修補補,最終也沒能徹底解決問題。

於是,我決定重寫網絡層。(媽蛋,早看這段代碼不爽了)。於是我花了兩天封裝了一個帶自動重連和容錯功能,支持異步接受和發送的GameSocket,簡單測試可以發送和接受字節。5分鍾替換游戲中的舊網絡層,你猜,怎么着?一次Run就登陸成功了!點了幾下,所有功能完好如初!尼瑪,世界上有比這還幸福的事情么?

其實,別聽我說的挺牛逼的,其實替換過程就改了不到10行代碼。因為實在是跟Logic、Data、View沒啥耦合的地方。

制作機器人

一般在線游戲,都會有一兩個用戶沒問題,大量用戶就有問題的時候。所以,機器人測試總是必要的。

當我把前端代碼交給后端,簡單介紹了一下結構之后,后端的小伙伴兒們都驚呆了。不是因為他看到這框架有多么優秀,而是:“這樣,我只要寫一個while循環,500行代碼就能完成一個機器人了啊。我還申請了一個星期來做這個事情呢!”(這是他的原話)。

說的更具體點,制作一個機器人就這么幾步:

  1. 把所有的View代碼文件刪掉。
  2. 寫一個AndroidLoop類。在這個類里,監聽斗地主主流程里必須處理的LocalMessage,在適當的時候發送主流程中的請求。(對於斗地主來說,主流程包括登陸、選房間、搶地主、出牌、退房間。每個應用有所不同,靈活自便。)
  3. NLD(NLDV去掉V)部分都不變,幾乎不用改一行代碼。

邏輯清晰

框架的作用,理論層面上規范了整個軟件的結構;而在實現層面,通俗一點,它就規范了什么代碼該寫在哪里,不要隨地亂放

作為一個針對性很強的框架,在上述過程中,NLDV約束了很多可能不需要約束的規范。其中大部分是項目中的干貨經驗。我知道他並不總是好的,我考略了很久要不要把他們加進來。最終,我還是寫下來了,考慮到剛開始從零開始寫應用的讀者來說,這些可能避免他們走很多彎路;而對有經驗的讀者來說,可能他們有判斷的能力,可以取舍自如。

我想,如果嚴格按照NLDV框架來編寫程序,顯而易見的好處就是:

  1. 層次清晰,出現問題容易定位。
  2. 主程再也不用擔心同事們把代碼寫得到處都是了。。。

框架之外(下回分解)

NLDV的適用場景(下回分解)

因為各種原因,這篇博客斷斷續續寫了兩個星期了,再不發布就要胎死腹中了。所以,最后兩節放在這篇日志的續集中寫。如果您感興趣,請私信我,我會盡快補上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM