Model View Whatever
发布于 - 修改于 - 大约需要 4 分钟 - 1970 字序
最近在调研各类前端的模式,包括MVC、MVP、MVVM、MVU、FRP等,在这里略略记录一下。只不过我也未能完全理解,十分遗憾。
MVW
MVW就是标题的Model View Whatever,因为有很多组合都是Model+View+一个什么别的东西。虽然一般来说Model都是指数据,View都是指显示出的内容,但偶尔也会略有区别。例如MVC中的Model指的是领域模型,在表格中的全部数据是领域模型,而过滤器的选项则与领域无关了,属于是视图模型,但是在MVU中这也在Model范围内。
MVC
MVC指的是Model View Controller,是一个久经考验的架构。本质上应该是来自于分层的思想,分为领域模型、视图模型和控制器。控制器根据用户的输入等修改领域模型,领域模型则通知视图模型进行修改,视图模型再读取数据并修改。
当然这也只是我个人的理解,毕竟根据Martin Fowler所说:
Different people reading about MVC in different places take different ideas from it and describe these as ‘MVC’.
就像传话游戏,传着传着就变样了,大家对于MVC就有了不同的理解。
同时我也看见过不少不同的实现。例如,理论上说视图是通过观察者模式监控领域模型,但是也有直接用控制器修改领域模型的例子。怎么样算真正的MVC,我也不太清楚。
MVP
MVP指的是Model View Presenter,是基于MVC演化的一个架构。这也是我不太能完全理解的一个架构,因为在有的描述中看起来和MVC差不多。这也难怪,毕竟最初的作者认为这只是对于MVC的一般化。
MVP主要在于有一个Presenter,负责把用户的输入翻译成给模型的命令。关于和MVC的区别Martin Fowler提过MVP一般是在表单级别,而不是控件级别。
MVVM,也就是Model View ViewModel,是从MVP演化而来。按照微软一篇博客的说法,最初可能只是对WPF特化的实现。主要特点在于视图所展示的数据和行为都在ViewModel中定义好了,视图只需要绑定ViewModel中的数据即可。Vue框架就是一个经典的MVVM的架构,View只是一个带有简单逻辑的模板,复杂逻辑都在JS中定义,包括computed计算值与行为等,模板只需要绑定即可。
MVU
MVU指的是Model View Update,出自Elm。最基本的结构如下:
init : Model
view : Model -> HTML Msg
update : Msg -> Model -> Model
其中init定义最初的模型,view则根据模型生成视图,而update则根据用户或者其他事件生成的事件更新模型。因为是从FRP演化而来,因此是纯函数式的,非常的简洁易懂。除了Elm以外,其他不同语言也有类似的实现。
FRP
FRP的核心思想就是两点:简单的记号和连续的时间。
先说连续的时间。FRP的核心概念是行为Behavior a ~ time -> a
,即一个连续时间的函数,相当于一个信号流,每时每刻都有一个值。我们所要做的便是在特定时间读取它的值。另一个核心概念是事件Event a ~ [(time, a)]
,即一组时间与值对,表明哪个时间点发生了什么事情。如果我们要表达行为被事件影响后的新的行为,比如说计数器原本每时每刻的值都是a,用户输入改变了这个值,使得某一个时刻以后的值都变成了b,那么就要用到另一个函数:switcher : Behavior a -> Event (Behavior a) -> Behavior a
。可以理解在某一时刻生成新的行为,替换了原有的行为,和原来的行为合为一个新的行为,即在此时间节点前为原有行为,时间节点后为新的行为。
再说简单的记号。可以看出对于FRP而言,所做的不过是函数之间的组合,生成一个新的函数,因此是十分声明式的行为。而对于函数之间的组合,有很多现成的组合子可以用。所谓组合子即用于组合函数,但是不通过中间值。例如常见的f compose g
或者f andThen g
,即直接组合了两个函数,而不是通过写h(x) = f(g(x))
这种形式。通过组合子,可以写出十分简洁的表达,当然有的时候也十分晦涩难懂。
关于FRP可以参考提出者Conal Elliott的演讲(他的个人网站或github)。
FRP并不是十全十美的。它也有很多问题。举例来说就有空间与时间的泄露。时间的泄露指,用户完全可以声明使用未来的行为,而这时候我们完全不知道它的值。空间的泄露指我们可能需要保存过去每时每刻所有的值,因为我们不知道哪些值会再被利用。为了解决这个问题,就有Arrowized FRP这一新的抽象。通过描述信号函数(SF a b = Signal a -> Signal b
,此处Signal
即为原版Behavior
)之间的组合,同时限制时间上的利用(仅允许微分与积分两个操作),来解决之前的问题。现在最新的研究似乎还有Monadic FRP。
总结
前端的模式各种各样,都是为了解决具体问题以及范式所存在的缺陷而提出的。在使用过程中,情况发生了改变,例如编程所用的语言发生了变化(MVC最初是基于Smalltalk提出的)等,范式也都或多或少发生了改变,以适应具体场景。使用的时候,也还是要具体情况具体分析,不能削足适履。