刚过去的周五 (3-14) 例行地主持了技术会议, 主题正好是UI 层的设计模式 -- 从 Script,Code Behind 到 MVC,MVP,MVVM, 是前一天晚上才定的, 中午花了半小时准备了下就开讲了.
今天看到了大家在为 MVVM knockout.js 友 (ji) 好(lie)地交流 http://www.cnblogs.com/xueduanyang/p/3601471.html , 所以就整理下然后更扩展地分享.
主要目的也不是为了争论, 毕竟只是正巧主题相近, 原本的打算也就是一次技术分享并且记录下来.
那么我们就按照大致的历史进程将这些概念进行划分:
- Script
- Code Blocks,Code Behind
MVC,MVP,MVVM
我们知道的是现实的历史发生顺序并不如上, 因为思想都是相似的, 比如 MVC 很早很早就出现了, 解释型语言至今基本上也有很多分支而且在互联网时代大行其道.
但我要说的是: 不要在意这些细节!
当然了, 这是玩笑, 我的意思是, 这些内容我懒得应该在另外独立的主题探讨, 篇幅也有限.
Script
这里脚本的意思不是指当时是用脚本开发, 而是像写脚本一样开发. 它们都有一个特点: 功能单一, 管理单一, 入口单一.
我们最早的程序是汇编, 当时的码农的工作是兼职, 工作内容是编写一套寿命不长的机器控制指令, 有些甚至是命令, 比如至今依然保留的 Command(亮点自寻):
到后来计算机被用于科学计算等, 需求推动了需要更高的开发效率, 所以我们有了高级语言.
那个时候码农其实多是数学家, 程序的作用很简单, 就是执行一些数学计算, 类似今天 ICPC 的一些算法问题, 比如 Hello World:
- main()
- {
- printf("hello,world\n");
- }
这时候, 程序还可以被归结为输入到输出的过程, 我们还能去讲讲冯诺依曼模型.
在这个时代, 开发是指编写机器指令, 甚至都不配用 "开发" 这个词来描述这项工作.
软件, UI 和 Markup Language
在那个时代讲 UI 等于放屁, 根本不存在这种概念. 但全赖我们有神器 -- 摩尔定律.
但我个人认为摩尔定律是不足以让一个敲命令行的时代在几十年间转变成这种各类框架技术架构实践模式的时代, 真正推动计算机形成自有的工程学体系的是还有两样东西就是:
人的能力并没有变强, 至少没有在同级数下变强.
人类一定会物尽其用
因为人的能力并没有 "跟上" 机器, 所以才会出现各种模式, 方法, 工具等等来补足人的不足, 以最大地透支机器性能. 就像我前几天在闪存无聊时突然想到的一句: 架构是对客观不足的妥协, 规范是对主观不足的妥协.
当我们需要机器做的事情多了起来, 我们就没办法在一个芯片上解决所有事情, 所以才会有冯诺依曼模型, 计算机架构, 没办法用一台机器解决, 所以才要互联网, 分布式, 云计算.
同样, 随着计算机的发展, 要做的事情多了, 就出现了软件的概念. 当 "开发" 正式化, 我们需求的软件就变得: 功能繁杂, 管理统一, 多入口.
真正变化的不是客观本质, 而是需求. 就像这里说的 "软件入口" 在客观上我们还是只有一个, 原理上始终都只有一个启动程序, 一个启动代码片段. 但 "软件的入口", 已经从指代 Main 函数变成了指代起始 UI, 用户已经从指代专业人士变成了指代一般消费者, 先有软件的需求, 才有软件的定义, 而需求是在变化的.
一个软件需要比当时多几个数量级的代码:
客观上我们没办法做一个能显示所有代码的显示器
主观上我们没办法在超快速滚动中看清所有代码
所以我们需要添加索引, 用多个文件组织代码.
机器的发展和软件的需求扩大和细化, 我们开始出现了用户界面 (User Interface) 的概念和最适合用于界面的语言 -- 标记语言(Markup Language). 当然, ML 不是为 UI 而生的, 它只是十分适合 UI, 所以才和 UI 坠入爱河.
因为有了更高 UI 的需求, 所以代码才正式被分化为描述做什么 (业务逻辑) 和有什么 (UI) 的两部分, 因为我们开发时没办法在两种思维方式下同时工作, 开发时的人脑是单线程的. 我们所看到的同时进行 UI 和逻辑开发只不过是我们学会了在两种模式下快速切换, 看起来像同时进行, 而不是真正的同时进行. 同样的情况也发生在不同的代码片段的开发中.
分化的情况除了 UI, 还发生在方方面面, 比如数据操作, UI 的对象和样式分离, 我们还是继续从 UI 讲下去吧.
Code Block 和 Code Behind(其实还有 Code Inside, 比如 onclick="javascript:alert('哎呀我 *')")
UI 和逻辑分开了两种语言来写, 但是它们也要放在同一个项目中, 因为它们原本就是要一起工作的. 即使是分开, 也需要相连, 因为这是它们本来要解决的问题.
这时候我们出现的 (通常的) 解决方案就是 Code Block 和 Code Behind. 虽然从时间上似乎 Code Block 比 Code Behind 要早, 有种感觉就是越新越好, 但实质上它们正交替地发展着, 因为谁也没办法解决 UI 和逻辑代码分化后的一个哲学问题 --UI 和逻辑是一起的, 但是它们却不是一起的.
Code Block 能很好地处理 UI 和逻辑间在一起的关系, 你在同一个地方可以同时维护 UI 和逻辑:
@model GM.OA.Finance2.web.Models.FinancialBase.CurrencyModel
- @{
- ViewBag.Title = "EditCurrencyDrawer";
- Layout = "~/Views/Shared/_DrawerLayout.cshtml";
- }
- @section styles {
- <link href="/Content/base/table-form.CSS" rel="stylesheet" />
- <link href="/Content/base/drawer.bigtitle.css" rel="stylesheet" />
- }
- <a href="#" class="addcurrency oa-btn" oa-style="green">添加新币别</a>
- <script type="text/javascript">
- $(document).ready(function () {
- $('.addcurrency').click(function () {
- $.oa.drawer.openUrl('AddCurrencyDrawer/', 'addcurrency', {
- width: 300
- });
- });
- });
- </script>
Code Behind 能很好地处理 UI 和逻辑各自分开的关系, 你可以让 UI 和逻辑各自做好各自的事情:
- <asp:Button ID="RemoveSelectedCurrency_Button" runat="server" Text="删除选中币别" OnClick="RemoveSelectedCurrency_Button_Click" />
- <asp:Button ID="RemoveAllCurrencies_Button" runat="server" Text="删除所有币别" OnClick="RemoveAllCurrencies_Button_Click" />
- protected void RemoveSelectedCurrency_Button_Click(object sender, EventArgs e)
- {
- var currencyId = Currencies_ListBox.SelectedItem.Value;
- currencyManager.Remove(currencyId);
- }
- protected void RemoveAllCurrencies_Button_Click(object sender, EventArgs e)
- {
- currencyManager.RemoveAll();
- }
因为存在两种实现方式, 所以就存在了对比, 因为存在了对比, 所以就存在了争论. 就像是 Java 和. NET,PHP 和. NET,WebForm 和 MVC,Mac OS 和 Windows,iOS 和 Android, 腾讯和所有其他互联网公司, 等等.
问题不在哪个更好, 而是我们要解决什么问题. 当然, 这听 (ben) 着(lai)像 (jiu) 是客气话了.
真正在 UI 和逻辑分化后带来的实质问题是:
是按逻辑和 UI 划分地管理, 还是按单界面的事务进行划分地管理
至少 UI 和逻辑刚分化的时代, 在软件上, 我们还认为同一项事务是基于同一个 UI 的一系列操作完成的.
在摩尔定律还持续发挥作用的时候, 计算机领域依旧高速发展, 所以通常我们还在为一样事物争论, 思考, 辩证的时候, 它已经发生了质变了, 不变的只是我们要解决的问题.
事务, 以及界面, 数据, 事件, 业务
在之前说到过了, 当需求变得庞大, 解决方案也会变得庞大; 当解决方案变得庞大, 就会出现细分; 当出现细分, 就会出现按哪种方式管理的问题.
软件从处理一件事务发展到了要处理许多事务, 各事务间有包含, 顺序, 主次等等的关系, 变得越来越复杂. 因为数据与逻辑庞大了, 所以数据与逻辑就分离了, 然后事件和业务分离了.
它们的关系已经在我们还理得清之前持续发展而变得更加难理得清, 但在一个时间点上, 它们 UI 的领域大致分化成这些原子:
界面
数据
事件
业务
你要细化的话会有更多繁杂的细节, 但相信这么写的话争议性比较小.
当一个问题出现一次的时候它是一个问题, 当一个问题出现了无数次的时候它会成为历史, 当一个问题将会出现无数次的时候, 它将需要一个明确的定义和解决方案.
其中, 数据的更新和界面的更新这一特殊事件的问题被放大了无数倍, 因为它出现了无数次.
Model-View-Controller
在 ASP 还在奋斗的时候 WebForm 突然到来, 正如 WebForm 还在奋斗的时候 MVC 突然到来. 当然, 我这里讲的 MVC 还是最原始的 MVC, 因为 MVC 在我们还在争论的时候已经发展了许多不同分支了.
有一点相信大家同意的就是, 我们今天讨论争论的 MVC,MVP,MVVM,Code Behind 等等都源自于职能分化和规划的思想与目的, MVC 不是它们的开始, 但是一个很好的开始.
相信 MVC 的模型大家很熟悉, 也很容易找到, 我们这里用一下某百科的图:
我们可以看到的是, 界面被分到了 View, 数据分到了载体 Model 上由 Model"携带", 业务集中在 Controller 中, 而推动业务的事件由用户与 View 交互, 通过 View 向 Controller 发动.
当然, 实现由很多种, 每种细节上都有不同, 所以我才只讲也只能讲大致的 MVC.MVC 的其中一个缺点便是没有明确的定义, 所以不同的实现 (比如 Struts 和 ASP.NET MVC) 细节上都是不一样的.
我们需要知道的是, MVC 并不是像上面所说的一些事情那样是一种 "必然的" 结果, 它是一系列必然结果问题中的一种解决方案, 而且是不完美的解决方案. 我们顺着推理去到一个地方很容易犯的一个错误就是认为路只有这一条而忽视其他可能性(估计这也是导致很多争斗的原因). 另外, 我们在讨论一件事物不完美的时候是有一个情境的, 所以请不要像 "我说它色彩单一, 然后你把它涂成彩色后证明我是错的".
MVC 的一般流程是这样的: View(界面)触发事件 --Controller(业务)处理了业务, 然后触发了数据更新 --不知道谁更新了 Model 的数据 --Model(带着数据)回到了 View--View 更新数据
这里也不多再陈述 MVC 的原理, 实践等等, 因为这就太长篇大论了.
Model-View-Presenter 和一些衍生
像我们之前推理的, 分化是一种需求的必然结果, 但却没有个一个确定的结果, 比如 Code Behind 和 Code Block 的问题等等.
MVC 顺着需求把 UI 相关的工作分化成了三份, 这点经过实践证明无可厚非. 但是它们的三角关系却被一些人认为带来了一些问题, 或者应该说他们有 "更好的" 解决方案.
在只有 Code Behind 和 Code Block 的那个时候维护是很直接的, 不是在同一段代码内解决就是在同一个关联的事件上解决. 三角关系的问题就是维护问题. 在 MVC, 当你有变化的时候你需要同时维护三个对象和三个交互, 这显然让事情复杂化了.
我们之前说到, 随着摩尔定律, 软件的需求不断地变化和变得庞大. 随着需求变得庞大的时候, 需求变化也变得频繁, 这是一个出现了无数次以后也将会出现无数的无数次的一个问题, 所以它需要一个解决方案, 哪怕它不一定能被解决.
为了解决需求变化, 从人月神话到敏捷到 DDD, 它不是我们已经解决了的问题, 而是我们正在解决的问题. 放在 UI 的模式和 MVC 上来讲, 就是优化或者替代 MVC 模式, 其中之一就是 Model-View-Presenter(MVP)模式.
我们先看看两个 MVP 模式的图:
(图一)
(图二)
两幅图是不同的, 但是对 MVC 的改进的思想却是一样的: 切断的 View 和 Model 的联系, 让 View 只和 Presenter(原 Controller)交互, 减少在需求变化中需要维护的对象的数量.
这种方式很符合我们的期待, 因为我们倾向于:
用更低的成本解决问题
用更容易理解的方式解决问题
许多时候并不是一种模式不好, 而是因为人没办法执行, 比如不容易理解, 我们就会选择容易理解的方式. 计算机依赖摩尔定律用数量的增长来解决问题, 而人是用方式的改变来解决问题的. 同样因为客观原因我们不善于维护多个对象和多个对象之间的关系, 所以我们改变了, 或者说简化了这种方式.
MVP 定义了 Presenter 和 View 之间的接口, 让一些可以根据已有的接口协议去各自分别独立开发, 以此去解决界面需求变化频繁的问题. 上面两图都有接口, 不过接口的实现和使用细节不一样, 不过思想上是一致的.
在这里要提到的是, 事实上, 需求变化最频繁的并不一定是最接近用户的界面, 但基本可以确定的是, 最接近用户的界面是因为需求变化而需要最频繁更改的. 当然, 如果 View 如果是 API 而不是 UI, 那就另说了.
还有一些用来 "解决"MVC 这项缺点的比如有: ASP.NET MVC 的 ViewBag,Cocoa 的 delegate. 它们都为了简化数据更新的问题而存在, 包括 MVVM.
Model-View-ViewModel
先直接看看 Model-View-ViewModel(MVVM)的图:
从图上看是比 MVP 简单了, 更不用说 MVC 了. 个人不认为 MVVM 是从 MVP 进化而来, 我只觉得这是在 MVP 之后出现的一种 "更好的"UI 模式解决方案, 但是用 MVP 来与之对比比较容易说明问题.
ViewModel 大致上就是 MVP 的 Presenter 和 MVC 的 Controller 了, 而 View 和 ViewModel 间没有了 MVP 的界面接口, 而是直接交互, 用数据 "绑定" 的形式让数据更新的事件不需要开发人员手动去编写特殊用例, 而是自动地双向同步. 数据绑定你可以认为是 Observer 模式或者是 Publish/Subscribe 模式, 原理都是为了 用一种统一的集中的方式实现频繁需要被实现的数据更新问题.
比起 MVP,MVVM 不仅简化了业务与界面的依赖关系, 还优化了数据频繁更新的解决方案, 甚至可以说提供了一种有效的解决模式.
至此, 我们能理解为什么许多人认为 MVVM 是最好的一种模式, 没有之一. 但事实上, MVVM 也是依赖于我们至今所讲的 "特有的情境".
当然, 最优雅的也是第一个能作代表的实践就是 Windows Presentation Foundation(WPF)了.
Web
之上, 我们在模式演变的推论基本上都还是基于桌面软件的, 但是过去十年却是互联网的时代. 实际上大部分让大家争议的并不是在桌面领域最合适的是那个, 而是在 Web 领域的模式问题, 也就是在 B/S 场景下的问题.
当软件离开单机, 去到网络的时候, 因为场景变了, 所以原有的解决方案也变了, 不过需求依然是不变的. 我们依然要解决的问题是用户交互与数据更新的问题, 还有维护等等的问题.
当场景变到 Web 的时候, 我们发现 MVVM 用来做服务端是极其不适用的, 至少现在是不适用的. 而 MVP 你提都不用提. 为什么呢? 因为:
网络资源成本过高
开发成本过高
问大家一个问题, 当一个网页的数据更新后, 你希望更新用户看到的数据, 你会怎么做?
一般情况下, 你会:
window.location.reload();
就算你不这么做, 用户也会:
F5
就像之前说的, 我们会选择更直接的方式解决问题. 直接刷新页面的原因是因为这样更直接, 更容易解决数据更新的问题.
很多时候你不会愿意为了一个数据更新写一个 AJAX, 更别说这个 AJAX 要带来 Loading, 事件顺序处理, 网络问题, 异常处理等等, 这就是开发成本过高.
另一个网络成本过高就更容易解释了, 虽然宽带是基本包月的, 但也不带这么用的, 何况还有移动用户. 更主要的原因是, 在本地软件, 更新数据是一个引用问题, 而在网络应用上, 这是一个传输问题. 传输成本远高于引用成本, 引用之上顶多是在本地内存中再进行一次内存拷贝.
这个时候, 我们会更倾向于用 MVC 模式, 因为在 Web 层面, 我们更倾向于一次性更新数据.
Web 的 MVVM
所有问题都不是问题, 就算有问题也要解决问题.
为什么这个标题下突然冒出这么一句话? 我想说的是, 需求依旧是不变的, 是推动进步的原动力.
还有我之前说过, 当我们讨论或者争论一个问题的时候, 问题的对象已经发生改变了, 而且这次是在我们讨论这个问题之前已经发生改变了.
网络资源成本不断下降, 相信已经不需要多提及. 摩尔定律和相近的一些原理正在发挥着它应用的作用, 网络带宽越来越高, 相应速度越来越快.
如果传输因为相对成本下降而导致数据传输的成本低于开发人员拒绝客户的成本, 那么它就会被实现而不是被拒绝.
另外还有一点就是因为技术的进步, 技术的资源不断被更大规模地压榨, 需求也不断地增长, 那么需求始终会增长超过相对不变的开发成本的. 比如 jQuery 的出现解决了很多问题, 我们现在更多地去使用 AJAX, 哪怕很大一部分依然是为了解决网络资源不足的问题; 我们会用更多的样式代码而用了相对更少的图片; 我们不再那么依赖 Flash 一类的矢量图解决方案而直接录制视频.
至少上一节我们说到的两个导致大家选用 MVC 的问题都正在被解决, 所以我们有理由相信未来 Web 不仅仅需要 MVC, 可能会需要 MVVM 或其他解决方案. 至少我们能理解容易理解为什么前端会出现一些 MVVM 的框架, 比如先驱 knockout.js 和 AngularJs. 这些框架本身的好坏就不作讨论了, 因为我们讨论的是模式.
在 Web 上, MVVM 的对比对象就不是 MVC, 而是 Code Block.
数据即时更新的需求在扩大, 但未必有达到一定要用 MVVM 这一等级的高大上的模式, 实际上如果你要更新一个数据, 你还是会采取:
$('.notice').html('发送成功!');
因为...... 我们依然会采取更直接的方式解决问题......
实际上, 现在 Web MVVM 主要并不是用在了 Web 或者 Wap 上, 而是移动 App 上. 按照前面的说法, 只可能是:
HTML+JS 比原生在一些场景上更适合 Native
在移动 App 上比 Web 上更适合使用 MVVM
哪怕是 Native 开发, 实际上 iOS 的开发上也是用类似的数据绑定的方式的. 这里也不深究了, 毕竟我也不算懂 iOS.
要说的是, 在 Web MVVM 或者 Web 的模式上, 也就是 Web 的富应用上, 现在还不过是个初期由膨胀的需求推动的阶段. 重要的不是技术会怎么走, 而是需求和客观条件会怎么走.
可能 Webform 会因为高速开发而焕发第二春, 它的 AJAX 的模式也十分满足于简单开发, 但似乎大家需要的不是 GUI 式的网页.
结尾语
我们不一定需要 MVVM, 但我们一定需要更强大的方式去解决不断膨胀的 Web 需求.
我们可以预见的是:
会有更强大的浏览器
会有更强大的 JavaScript 或者框架
会有更加适合的模式
除去客气话的部分, 我还是想说, 在不同的需求下其实有最适合的解决方案, 通常让我们纠结的不是因为哪个解决方案更好, 而是我们看到的条件不够多.
编译语言当然比解释语言效率高, 但考虑到开发和维护成本, JavaScript 等始终会大行其道, 比如 Node.JS,Python;.NET 和微软当然很强大, 移植. NET 到其他平台也很容易, 但微软是家有自己商业模式和要赚钱的公司; 当然有些实践和技术更好, 但其他开发人员会避开甚至否定自己不擅长的东西, 大家都喜欢确定的东西; 有些技术更强大, 但是只是基于特殊的客观条件和需求, 如果你想做大, 要么创造客观条件, 要么把它结合需求......
之前有要交流的园友会用园子的短消息, 好像个人资料里也没有把邮箱显示出来. 有兴趣交流的就直接 indreamluo@qq.com 吧.
来源: https://juejin.im/entry/5ad1cd9d518825556e5e85da