从〇开始构架前端(NLDV框架)

从〇开始构架前端(NLDV框架)

框架 设计模式前端


摘要:一个普通应用,大到微信, 小到豆瓣FM,必不可少的都包括四部分:Network、Logic、Data、View(NLDV)。如何把他们组合起来,结构清晰、又协做便利,是前端主程的基本修养。本文用通(有)俗(点)易(啰)懂(嗦)的语言,界定了这四个模块的职能范围,同时提供了一种简单易用的组织方式。知者可互动,不知者可参考。java



目录

先唠点嗑

说说本身吧:毕业三年,经历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等。

咱们用最少的接口来定义它:异步

 
 
 
 
// Real msg struct will implement this interface, adding some getter/setters.interface IMessage{ uint getMsgId();// unique id for a type of msg byte[] toBytes();// serialize to bytes. void parseBytes( byte[] bytes); //deserialize to useful info.}//Generally, "Logic" will implement this interface for recieving datainterface INetworkHandler{ void onMessageRecv( IMessage msg);}interface INetwork{ void send( IMessage msg ); void registHandler( uint msgId, INetworkHandler handler ); void unregistHandler( uint msgId, INetworkHandler handler );}

Logic

Logic是一个应用中核心实现业务逻辑的部分。主要功能有:

  • 响应来自用户(View)的功能请求。或经过写入Data来改变状态,或构造消息体发送到服务器。
  • 收到网络消息后,作相应的逻辑处理,将数据的改变写入Data,最后广播本地事件。
    • 这里的本地事件一般用一个 字符串或者整数指代,称为本地事件ID。
    • 这个过程一般会用到观察者模式。有一个本地消息中心LocalMessageCenter,监听者(一般是View)用本地事件ID来向LocalMessageCenter注册,而Logic调用LocalMessageCenter.dispatch( localEventId ) 便可广播此本地事件。
    • 是的,Logic直接调用View的方法也能达到一样的目的。可是,为了减少Logic和View之间的耦合性,仍是选用LocalMessageCenter做为中间层。随着需求的改变,View类可能面目全非,但LocalMessageCenter的接口却能够长久不变。

Logic的大体结构以下:

 
 
 
 
class SomeLogic implemets INetworkHandler{ SomeLogic() { Network.getInstance().registHandler(1, this); Network.getInstance().registHandler(2, this); } void onMessageRecv( IMessage msg) { switch( msg.getMsgId() ) { case 1: logicHandler1(IMessage msg); break; case 2: logicHandler2(IMessage msg); break; ... } } //请求操做 void logicReq1(...); void logicReq2(...); ... //消息处理 void logicHandler1(IMessage msg) { //TODO: 收到消息,逻辑处理 //TODO: 向Data写入数据 //TODO: 广播本地事件 LocalMessageCenter.getInstance().dispatch( "Logic1Compelete" ); }; void logicHandler2(IMessage msg); ...}ILocalMessageHandler{ void onLocalMessage( String msg );}LocalMessageCenter{ static LocalMessageCenter getInstance(); // Singleton void dispatch( String msg ); void regist( String msg, ILocalMessageHandler handler ); void unregist( String msg, ILocalMessageHandler handler );}

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的适用场景(下回分解)

由于各类缘由,这篇博客断断续续写了两个星期了,再不发布就要胎死腹中了。因此,最后两节放在这篇日志的续集中写。若是您感兴趣,请私信我,我会尽快补上。

相关文章
相关标签/搜索