Leobert's Blog

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

作者:Leobert

三思系列:“声明式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的优劣时,势必会上升到 OOP 和 FP 两大编程范式的讨论。这一篇不再展开。

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