三思系列:"声明式UI"和"命令式UI",你的理解可能是错的

三思系列是我最新的学习、总结形式,着重于:问题分析技术积累视野拓展关于三思系列

前言

最近,Jetpack Compose 发布了 Beta 版本,可以说是千呼万唤始出来。

在一个 现象级事物 出现时,关于它的讨论会很热。我注意到,最近有一大批关于Compose的文章涌现了出来,并且有很多的讨论,这很棒。

但是,值得一提的是,我发现很多人对于 声明式UI命令式UI 的认知,可能出现了一点偏差。

当然,我也 不是马丁老爷子 或者 Jack Wharton 那样的 专家,对于这个问题,只能是同大家 一道探索

声明式 和 命令式 含义探索

按照经验,中文往往比英文具有 更强的表达力 ,这也意味着:中文的 凝练度更高,也更容易产生理解误差,我们结合英文一起看

  • 声明式 declarative

a declarative sentence has the form of a statement.

statement: 4条释义,看第一条

  • something you say or write, especially publicly or officially, to let people know your intentions or opinions, or to record facts
  • 其他三条略

technical, an imperative verb is one that expresses an order, such as ‘stand up’

如果从 语境 上来看,声明 是面向一个环境、一个群体表达某些规则,而 命令 所面向的,是一个明确的对象群,在指使他们做一些事情。

举个可能不太恰当的例子:

  • 我军优待俘虏,缴枪投降不杀,这是对敌军的声明

  • 营长命令各战斗单位,打扫战场,一连收缴武器弹药,二连负责收押俘虏。这是命令式

我发现,讨论 声明式命令式 时,需要先界定好 讨论范围。这一点非常重要,一旦超越了范围,讨论就会出现错误。

放在计算机领域中讨论这两者时,需要先界定好

我们知道,程序设计中,以及在计算机设计中,都是 分层 的。下层 的内容交给 上层 使用时,需要使用到 接口

这个接口,不仅仅是我们编程中的API的概念

甚至,接口的表现形式是一种 语言

接口被定义时,其表意即已固定,当其表意:

  • 越倾向于 现实表达 时,呈现为 声明式
  • 越倾向于 执行过程 时,呈现为 命令式

UI

User Interface 的简称,直译为用户界面

UI系统需要处理两个范畴的事情:

  • 内容呈现、反馈
  • 用户交互

另外,还需要程序 构建 出UI。

我们需要从这 三个角度 来看。

构建UI时的 命令式 和 声明式

我们在Android原有的知识领域中看这个问题。

我们知道,这部分内容设计时是遵守 OOP 的,存在一整套 View 类簇。而界面由具体的功能类组建出 视图树

我们需要讨论的,就是 视图树构建。我们知道:

ViewGroup 存在一系列 addView 方法,通过它们,最终实现 树结构

这套 接口 ,倾向于 执行过程,直接使用这套接口去构建,就是 命令式 的构建。

而在此基础上,封装了 LayoutInflater,将 xml语法 表达的 结构树 转换为 视图树, 此时我们使用的 接口 ,其表现形式是 xml语言

按照特定的规则,我们 直接描述期望的结果,这更倾向于 现实表达,这样的构建方式,就是 声明式

View 存在一系列的 布局属性,一些直接持有,一些被LayoutParams封装而间接持有。

视图树 的节点构建过程中,还需要处理 布局属性

按照刚才的讨论,我们很容易得出:

  • 直接使用 View的API 或者 LayoutParams的API,或者直接对属性赋值,可以断言是 命令式 范畴。
  • 而在布局文件中,声明对应的属性值,可以断言是 声明式 范畴

小结:本段内容,我们讨论了构建UI的两种做法。并讨论了其所属的范畴。

Android体系最基础的内容中,内容呈现&交互

在最基础的内容中,讨论 内容呈现交互 部分使用的API,是 命令式 的,还是 声明式 的,这 非常的无聊

内容呈现时的 命令式 与 声明式

View 提供的 接口 中,例如:

  • TextView#setText(Charsequence cs)
  • ImageView#setImageDrawable(Drawable d)

等,更加偏向于 执行过程

但是注意,此时 执行过程现实表达 的界限已经有点模糊了 。我们不必 牵强附会

读一下这两句话:

  • 我们通过调用相应API设置了需要显示的内容
  • 我们在布局文件中指明了视图显示时需要呈现的内容

交互时的 命令式 与 声明式

我们知道,Android View 体系中,封装了一系列 Listener,通过 接口回调,让程序可以 监听 到用户发出的 交互指令

除此之外,在 最基础内容 中,仅有简陋的 click 属性可以声明点击事件的 消费函数


此时,我们 放大着眼点 。我们处理视觉呈现和交互时,面向的两个重要对象

          信息展示
   | ================ |
   |     内容呈现     \|/
|=====|           |=====|   
| 实体 |           | UI  |
|=====|           |=====|
  /|\      交互       |
   | ================ | 
          状态变更
            

这两者之间存在两条线:

  • 代表信息实体 -> UI, 这是 内容呈现 ,实质是:在UI上做 信息展示
  • UI -> 代表信息实体 ,这是 交互 ,实质是:在信息实体上做 状态变更,即变更信息

我们在实现这两条线时:

  • 如果直接利用 了倾向于 现实表达 的高阶框架封装,那这个框架就是 声明式的框架;
  • 如果仅使用 低阶view层api ,自行实现了逻辑,那就是 命令式 的编程,毕竟也没用啥框架;

评价一套 UI工具体系 是否是 声明式 的,要从这三个方面看,如果在三个方面均进行了封装,提供的 接口 都偏向于 现实表达,那是成熟完善的 声明式UI 框架。

看几个例子:

  • 直接使用 View体系的接口 构建UI、处理显示和交互,这是 命令式编程
  • 使用 LayoutInflater 工具和 xml表达的布局文件。绝大多数情况下,还需要通过映射关系,找到View对象;并进一步 根据业务逻辑 ,操作 View对象 实现内容呈现和交互逻辑。 这是 声明式构建命令式编程 处理显示和交互。这套工具并不是完善的 声明式UI开发工具
  • 使用 LayoutInflater 工具 + DataBinding工具包xml表达的布局文件DataBingding + LayoutInflater是一套完善的 声明式UI框架
  • Compose,完善的 声明式UI框架

思考:为什么当初XML布局方式受到官方推荐

很大一方面是Java语言特性的原因,最开始,都是使用Java语言编写业务逻辑,而基于Java,很难直接定义DSL

注:Java语言很难直接定义DSL 这一点我并没有做严格的调研考证,如果有谬误,请指出。

而基于Groovy等语言开发符合 DSL声明式UI框架,必然要引入Groovy,这在当时也和 主流 格格不入,虽然Google很喜欢创新。

所以,官方提出了 布局业务 分开,这样也有很多好处:

  • 业务逻辑的 模块内聚性复用度 可提升
  • 开发者可以提升专注度
  • xml树view树 之间 关系明了,借助成熟内容,快速开发出 预览工具

思考:为什么XML布局方式走在被淘汰的路上

因为使用XML布局的方式存在 先天弱点 ,它和 业务逻辑 天生生活在两个世界。

这意味着,必然存在一个 加载器 或者 转换器,在 运行时 或者 编译时,将 xml语法 所描述的 声明式布局 转换到 业务逻辑 所在的世界。

所以,并不是 DataBinding 这一 声明式UI框架 不够优秀,而是 xml 和 业务逻辑代码 之间天生的屏障不够 人道主义

而推行 kotlin first 也有一段时间了,基于kotlin,开发 DSL 工具包非常的方便。

延伸:响应式UI框架

这也是 Modern Development 中非常热门的一个词,响应式 是对特点的一种描述,方法论是:事件驱动,Event-Driven发布订阅者模式

这个概念,和前面提到的 声明式UI框架命令式UI编程 没有必然的联系,它所表达的是: 代表信息的实体UI 之间的互操作是符合 响应式 特征的。

但可以想象,要实现响应式,其接口变现特征,天然倾向于 现实表达;如果是倾向于 操作过程,可以断言其抽象程度非常低。

扣题:勘误

再看到这些说法,我们可以判断出它们存在 谬误

  • 基于xml无法实现声明式UI
  • xml被淘汰是因为无法实现声明式UI
  • 基于xml布局方式都是命令式
  • 声明式一定会取代命令式,你看xml都要被淘汰了
  • 用代码写的就是命令式UI

等。

再多聊两句,一套曾经热门的技术,一定有其热门的原因和背景,它走向消亡,往往是其 面向的问题场景 ,在事物发展过程中,越来越少见。

声明式命令式 各有优劣。按照规律,高阶封装更 人道主义 ,解决高复杂度问题时,也容易实现 降维打击。 但这也意味着机器付出的代价更高一点。

总之,脱离了环境背景讨论谁胜谁负,往往是耍流氓。

总结

这一篇,我们用比较 随性 的文字,对 声明式UI 进行了一次脑暴,进而讨论其概念的实质。我相信,经过这次脑暴,对这一套概念体系的理解会更加 深刻准确 。相应的,学习新内容时也会 更加轻松事半功倍

进一步延伸, 在学习Compose,思考Compose的优劣时,势必会上升到 OOPFP 两大编程范式的讨论。这一篇不再展开。

点赞关注收藏,从此不迷路。