好久没写博客,转管理后技术有些荒废, 貌似现在流行知乎live和微信公众号, 博客已经有些过时了,但关键是你对技术的思考和总结, 采用什么方式其实不重要。

现代大型客户端软件,考虑到各种终端类型(Windows, Mac, ios, Android...),大部分采用分层设计,用C++封装跨平台的SDK/Framework, UI层采用各平台Native语言实现(C++/Java/objective-c),典型的分层如下:


最近工作要对UI层进行重构,对UI层的设计进行了一些思考,UI层是客户端直接和用户交互的那层,它的好坏直接影响到整个 产品的用户体验, 重要性不言而喻。但是很多时候我们却很难将这层代码写好,给用户良好的用户体验, 主要下面一些因素:
(1)逻辑复杂性, 因为这层是整个客户端所有feature的最终集成地
(2)各种屏幕尺寸的适配,这个Mobile上更明显
(3)Accessibility的支持: tab导航,高对比,高DPI, 读屏...
(4) 换肤和多语言

上面问题(2)(3)(4)都是和具体平台系统相关的,这里我们重点关注如何解决问题(1), 也就是如何通过合理的设计降低UI层的复杂性, 因为UI层的代码往往写到最后变成了"一坨屎"。

其实UI层的设计已经有很多成熟的模式,我们依次看下:

(1) Original MVC

这是最早的的MVC模式, 据说最早的计算机鼠标键盘不是直接关联在view上的, 因此它们的输入事件会先到达contorller端, 然后由controller去修改model, model再通知view更新数据(view更新时会从Model取数据)

(2) Modern MVC

这是现在大家对MVC模式的理解,和上面的original MVC类似, 只不过鼠标键盘事件的输入源转成了View, 这也符合大家对窗口理解

(3) MVP

MVP模式割断了Model和View端的直接通讯,让Controller(Presenter)当作中介者,View接收到鼠标键盘事件后通过event通知controller, controller把新数据更新到model, model数据更改后通知订阅它事件的controller, controller再把新数据更新到view端

(4) Dialog MVP

这种方式是简化版的MVP, 把View和Controller写在一个dialog里面,虽然UI和逻辑写在了一起,但是使用起来很简单。

(5) MVVM

MVVM (Model-View-ViewModel) 是MVP的改进,它讲Presenter改成了支持数据绑定的ViewModel, 这是WPF特有的一种数据模型,依赖语言和库对数据绑定的支持。

通过观察,我们发现上面无论那种模型,都有几个基本概念:
a. View, 展示界面
b. Controller, 控制逻辑
c. Model, 广域的model指的是领域建模的业务模型层,因为我们这里已经将业务模型封装在了Framework(SDK)及其下层, 所以我们这里的model主要指的是UI层的一些数据模型。

我们发现每个view都有一个controller来控制它, 但是很多时候一个feature我们需要协调多个controller/view, 比如IM里收到一条消息, 你可能既要在消息窗口中显示, 同时也要在右下角像QQ那样出个提示, 另外可能还需要播放提示音, 这么多事情需要有人来协调, 所以我们需要引入Manager的概念, Manager以feature/模块为划分单位。

另外有些view可能会很复杂, 所以需要引入sub-view的概念, 也就是说一个复杂的view可能是一颗树, 它由很多sub-view和, 甚至grandchild-view组成, 同时每个view/child-view/grandchild-view都有自己的controller来处理它的事件。

另外一个view是由多个控件元素(Element Control)组成,很多时候我们需要对控件元素进行定制, 我想这是大家知道的一个常识。

UI层基本的结构大概如下:


另外有些时候我们需要在Manager, Controller和Data Model之间进行一些事件广播, 因此我们需要引入消息中心(message center)的概念。

这样最终的层次结构图大概类似这样:

我们看下各层的访问规则:
UI Eelement Control, 是界面的基本组成元素, 它的事件会通知到自己的view (Windows/Panel);
UI View, 可以完全控制自己的Element control, 它收到控件事件后会通知到自己的 UI Controller;
UI Controller, 可以完全控制自己的UI View, 并且响应UI View的事件消息, UI Controller可以调用自己的UI Data model存储数据, 也可以调用某个UI Manager的接口处理事件;
UI Manager, 和底层Framework/SDK打交道,可以完全控制controller, 也可以通过message center广播重大事件;

我们可以看到, Manager->Controller->View->Eelement, 我们逐层都是基于类完全访问, 反之则是基于接口事件消息, 这也符合设计中的单向依赖原则。

另外我们再说下UI Data Model的设计, UI Data Model实际上分成public和private两种类型, 其中public的数据是共享的, 可能是多个controller/manager都要访问的, private则是某个controller私有的数据。

这样整个UI层的层次结构就很清楚了:
首先有个总的UI Manager, 它下面包含三种角色: UI Manager Manager, UI Controller Manager, UI Data Manager。
(1)UI Manager Manager根据feature/模块分成多个子UI Manager, 子UI Manager根据需要也可以包含自己的子UI Manager;
(2)UI Controller Manager根据窗口数量管理多个窗口的UI Controller, 其中复杂窗口的UI Controller可能包含树状的子UI Controller;
(3)UI Data Manager根据数据类型,管理多个public/Share 的UI Data Model; private的UI Data Model是每个Controller私有的,外面没法直接访问,由它的controller暴露接口访问。


另外对于复杂的UI, 我们可能还需要引入UI Layout Manager的角色, 因为复杂UI在不同的情况下整个UI layout会有巨大改变, 比如会议系统中共享桌面时你可能需要把整个主窗口隐藏,在单屏和双屏时整个UI又完全不一样, 所以我们需要借助UI Layout Manager协调多个窗口的显示和隐藏。

最后我们总结下上面这种架构设计的优势:
(1) 界面和逻辑的分离, view和controller可以由不同的人分别实现, 独立变动
(2) 基于接口分层设计, 单向依赖且低耦合
(3) UI层数据类型的分类,共有共享的还是私有的
(4) 消息中心,同时支持同步/异步的消息广播机制, Manager/Controller/Data Model可以各自订阅自己感兴趣的消息
(5)每个Manager主要关注自己的那块feature, 单一职责
posted on 2018-05-19 19:50 Richard Wei 阅读(3578) 评论(1)  编辑 收藏 引用 所属分类: C++架构体系

FeedBack:
# re: 客户端UI层设计的思考
2018-06-11 13:57 | molasses
学习了。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理