【PDI模式】
前文中,我们详细讲解了为实现一个涉及UI的功能所必须得三者,简称PDI:
- Panel类:主要实现交互逻辑、显示逻辑的地方以及保存界面相关的数据的地方
- Data类:数据管理类,主要是业务相关的数据
- Inter类:Panel类和Data类之间数据联通的中间者
根据功能的复杂程度来决定这三者是拆开还是合在一块。功能简单的情况下,直接一个Panel类搞定,复杂时要拆开成三个。
如果要实现ABCDE五个复杂的功能,那我们是不是一共需要15个类?
这要看五个功能的业务相关性,业务相关性越高所需的Data类和Inter类越少,也就是我们前文所说的多个Panel类的数据可能来自同一个Data类。
根据这个可以看到游戏开发和互联网开发的一个重要的不同地方:
游戏开发中绝大部分业务数据都在客户端中,数据几乎以标准的树状图呈现,一般一个或多个Panel类的数据来自一个Data类
互联网开发中绝大部分业务数据都在服务端中,数据分布在不同根的树中,一般一个或多个Panel类中的数据来自多个Data类
后者在界面的开发上会复杂一些,因为多了些数据异步获取的情况,但遵循前文所说的方式,都能顺利解决。
【MVC】
MVC指的是模型Model、视图View、控制器Controller,他们的关系图如下:
View从Model中Get数据,Model数据变化时通知View,用户与界面的操作产生Action被发到Controller,Controller处理后将操作更新到Model和View,两者再做各自的处理。
那么能说PDI和MVC的对应关系为Model是Data、View是Panel、Controller是Inter吗?
这两者不是一回事。
MVC是整个UI框架层级上的模式,PDI是在已有的UI框架模式下(不一定是MVC)的具体功能上的模式,他们的核心都是实现逻辑和数据的分离。
我们将从以下几个方面区别:
交互
以屏幕为输入设备的基础交互只有点击和移动,结合UI组件,可以拓展出双击、长按、拖拽、多指点击等更为丰富的交互操作。
输入设备还有鼠标、键盘、手柄等,每种输入设备都有基础的交互事件
一般来说,输入设备会将基础交互事件传递给操作系统,操作系统将其传递给应用程序,应用程序自己处理基础交互。
视图
我们可以将常用的一系列逻辑分离出来做组件化形成基础的UI组件(即View组件),这些组件是:文本Text、图像Image、按钮Button
由这些基础组件可以拓展形成常用组件,包括:
组件 | Unity | Android | iOS | Web (Element UI) |
可输入的文本 | InputField | EditText | UITextField | <el-input> |
可切换的按钮 | Toggle | CheckBox | UISwitch | <el-checkbox> |
滑动条 | Slider | ProgressBar SeekBar | UISlider UIProgressView | <el-progress> |
滑动列表 | SrollView | ListView | UIScrollView | <el-list> |
下拉列表 | DropDown | Spinner | UIPickerView | <el-select> |
当然,还有一些常用组件没列举出来,不同项目组也会根据项目需要做自定义的组件。后来,还有用于调整布局的ViewGroup组件
这些常用组件会构成的组件库,有些开源的组件库,例如:
35+前端UI组件库一览(按star数排名) - 掘金 (juejin.cn)
Android最全UI库合集_android ui库-CSDN博客
在Unity中,UI组件库有UGUI、NGUI、FairUI,但绝大多数都用UGUI。
这些成熟的组件库一般会帮我们处理好用户与View的交互,我们通过接收或者监听得到交互结果。
假设我们只有最基础的组件,要实现前文中的一个显示项,按照逻辑和数据耦合的方式,我们可能在一个类中做完以下事情:
- 处理操作系统传递的基础交互事件
- View组件的逻辑,也即视图相关的逻辑,基本是和渲染相关的逻辑
- 前文中有View组件后的其他各种逻辑
如果有8个不同的显示项,那么这3个部分岂不是要写8遍。作为后来者的我们,肯定可以想到1和2是重复的,其中的逻辑可以提取出来
有个Manager会处理操作系统传递的基础交互事件,生成常用交互事件,决定交互事件与哪个View组件交互。这属于很底层的功能,虽然不属于操作系统的范畴,但也会提供,我们在项目中一般不会自己做。
随后View组件会将交互结果传递出去。
UI框架中的MVC
应用程序中的界面是很多的,我们无法预测界面是什么样的,以及有多少个界面
一个界面通常是由多个View组件组合成的,在业务逻辑中会去持有所有需要的View组件,其是整个框架中的View层。
一个界面的消失和另一个界面的出现是和用户交互有关的,操作系统提供的会保证交互从View组件,也即View层中传递出来。
我们需要管理控制界面之间的切换和界面的生命周期,这就是UI框架中,UIManager的最重要的职责,其相当于Controller。
我们针对每个界面的业务逻辑和数据就是Model。
View和Modle以及部分Controller的实现和具体业务是息息相关的,无法在框架层面上去实现,但在框架上可以去规定不同部分应该在哪实现。
而Controller的核心职责却可以提出来,可以自己实现,一般在Web开发和游戏开发中,要项目自己去实现。
而在Android的架构中,其Activity相当于一个界面,界面的切换和生命周期Android提供管理。iOS的UIView和UIViewController类似
【 MVC职责划分】
View层
- 负责获取界面上的View组件,PanelView,这部分代码比较固定,可以做成自动化生成
- 负责处理不同View之间的布局逻辑,PanelViewLayout,同样可以自动化生成
Controller层
- 负责接收不同组件上的交互事件和其他消息,PanelController,同样可以自动化生成
- 负责接收UIManager传来的Panel生命周期调用,PanelLifeCycle,同样可以自动化生成
Model层
- 负责业务和界面逻辑实现,PanelLogic,需自定义实现
- 负责管理业务和界面数据,PanelData,需自定义实现
对比而言,文中说的LDI更像是Model,也不仅仅用于UI开发,用于其他方开发可以叫LDI,其中L是logic
【MVC的优缺点】
优点
1.实现业务和视图的分离,提高了Model的复用性。
2.职责划分明确,便于拓展维护
缺点
1.错误将业务逻辑放在Controller中导致其越加臃肿,框架只是规定了代码要写在哪里,实际个人在哪都可以写,如果对MVC理解不清楚,很容易认为业务逻辑属于Controller、
2.View和Modle直接交互可能会导致引发连锁反应而难以查证。(在游戏开发中这种情况较少)
【MVP】
P是Presenter,View 和 Model 不相互持有,都通过 Presenter 做中转,如下图所示:
在MVP中,P相当于Controller,V需要的数据通过P从M中获取,为此有如下改动:
- 需要把原来V直接从M中获取数据的方法都转移到P中
- 原来M中的Logic部分中与View相关的逻辑也需要转移到P中
同时,为了规范每个地方的职责,需要用接口约束,具体的M、V、P类都要基础M、V、P接口
MVP的优点是通过接口做到了进一步解耦和规范,缺点是会引入更多的代码,增加接口复杂性。也就是使用接口的优缺点。
【MVVM】
VM是ViewModel,主要指视图的相关数据和逻辑,前文说过Panel自己的与界面相关的数据,包括交互数据和业务数据,如下图所示:
在MVC中,业务数据直接就是界面所需的交互数据,但在MVVM中,会有一个业务数据和界面数据的映射,例如业务数据中的UserName字段界面数据中可能仍是原样显示,但地区需要从“中国”显示为“China”
在MVC中,与界面有关的逻辑在M中,MVP中,与界面有关的逻辑在P中,但在MVVM中,这些逻辑会放在VM中。
在MVVM中,View中需要新增一个绑定逻辑,为PanelViewBind,其将V中的组件的数据与VM中的数据绑定,以便于双方中有变化时会自动通知对方取修改值。
更进一步的,需要绑定逻辑。
此时,View会持有VM,VM不会持有V,VM通过绑定逻辑将数据和逻辑同步到V,各种给View组件赋值的逻辑可以省略。
这是MVVM的一大特点,在MVC和MVP中,实现界面交互和显示逻辑时都需要持有View组件,如果View组件没有事先创建好,就不好先实现逻辑。
而在MVVM中,两者是可以分开的,并行进行。在MVC中,可能需要程序去完成界面,而在MVVM中界面可以由交互完成。
注意,PanelViewBind中只需要调用接口实现单向或者双向绑定即可,以便于自动化生成。
调用的接口需要框架去实现,即在框架层面上支持数据绑定。一般情况下,在视图中只显示而无需编辑的数据用单向绑定,需要编辑的数据才用双向绑定。
【MVC框架:GameFrameWork的UI模块】
这里只做简述,详细的请看他人解析和源码
在GF中,一个Panel对应一个UIForm,定义了界面的9种状态,也即生命周期:
- OnInit和OnRecycle 界面初始化和回收,其会界面打开和关闭会被调用到,(个人感觉在界面资源被加载和卸载时调用即可)
- OnOpen和OnClose 界面打开和关闭,会在用户主动打开和关闭界面的流程种被调用到
- OnCover和OnReveal 界面被遮挡和从遮挡种恢复,例如A界面先打开,B界面后打开,那么A界面被遮挡,A界面的OnCover会被调用到,随后B界面再关闭,A界面的OnReveal会被调用
- OnUpdate 界面更新
- OnPause和OnResume 界面暂停和从暂停种恢复,主要是界面被遮挡时会否还做Update
多个UIForm可以在一个UIGroup中,UIGroup中根据Depth去管理界面的在OnCover、OnReveal、OnPause、OnResume之间的状态变化,代码在Refresh方法中
UIManager会管理不同的UIGroup,通过OpenUIForm和ColseUIForm打开关闭界面,再从UIManger到UIGroup到UIForm做层层调用
这九种状态足以应对觉大多数情况,其他不同UI框架的定义和处理大差不差。
在UGF中,有一个UIForm继承IUIForm接口,其持有UIFormLogic,在界面不同的状态时,即而调用到UIFormLogic中的OnInit至OnRecycle等九个方法。
UGuiForm继承自UIFormLogic,其中的方法和字段基本和UIFormLogic相同,我们自己的界面继承自UGuiForm即可。
这个UI框架,主要是实现了生命周期的管理,MVC的职责并没有划分明确,也没做自动化生成,感兴趣可以按照上文所说的职责划分拓展实现一下。
【MVVM框架:LoxodonFramework的UI模块】
核心在于看看其数据绑定的实现,这块比较复杂些,之后单独说说,可以先参考下源码
【参考】
正确认识 MVC/MVP/MVVM - 掘金 (juejin.cn)
https://zhuanlan.zhihu.com/p/99443196