其实微软当年的MFC更接近于“代码描述界面”,Delphi等反而是通过专门的配置文件.DFM来描述窗体……
比如,这个来自CSDN的截图就是一个典型.DFM文件内容:
Delphi项目构成之窗体文件(Form Files).DFM_weixin_33904756的博客-CSDN博客
事实上,这并不应该叫“代码描述界面”,而是“窗体布局独立于程序设计”。
把窗体布局独立出来有极多好处。
1、集中管理,方便用程序自动维护,从而可以“所见即所得”。
相比之下,MFC经常一开始还是可以用窗体编辑器维护界面的;但做着做着……代码中到处改吧。
反之,如果窗体编辑器可以一直保持跟踪,而且允许你随时随地的想改代码改代码、想改布局改布局,会不会好很多?
但,如果代码和布局混合,实现起来就太麻烦了……
尤其是,当时的各种编程语言并不普遍支持现在烂大街的嵌套声明类/函数,也大多没有闭包、lambda等概念。这就使得代码结构天然和窗体结构不同;这种状况下,想要维护窗体之间的关系,实在太难。
2、窗体布局和程序设计脱耦。
于是,设计窗体的可以专心设计窗体、修改视觉表现;写程序的专注程序逻辑——将来只要按照约定,比如文本框输入框名字吻合,程序就能正常运行。
3、窗体布局文件独立。
更进一步的,还可以把窗体布局文件都独立出来、甚至在运行时加载——这就支持了“换肤”。
事实上,只要你懂开发、只要你的应用不是太小太垃圾,哪怕仅仅“脱耦”一个好处,就值得你投资这项技术了。
因此,并不仅仅是微软,业界绝大多数的GUI框架,从微软的WPF到Qt的QML、GTK的Glade XML,以及Android早期的界面描述文件,全都是独立的。而且大都采用了XML格式。
早期的确有一些采用了文本格式,如ini或其他格式。但做过界面的都知道,这玩意儿就是一层套一层来回套;可ini或者别的格式,包括json,想要清晰的表达这种嵌套关系,那就麻烦的很了。
而XML格式虽然繁琐,但它的格式足够规范,拿来表示这种嵌套关系,那是简单明了、一目了然。
那么,既然XML布局文件这么好,现在为何又回归了呢?
1、一直没有XML布局标准
这就使得,每一种编程语言、每一套GUI库,就有一套自己的XML格式约定。
它们自带的XML布局图形编辑器……只能说是差强人意。
简单说,你不可能不借助Photoshop/gimp之类东西,就搞定一个漂亮的界面;然后,哪怕已经有了photoshop做的漂亮界面图片,你要把它做成符合要求的、可缩放的、能够直接放程序中使用的XML布局描述……慢慢学习吧。
遗憾的是,绝大多数的界面设计师并不会编程。让他学会使用当前团队使用的某种语言的界面布局编辑器,这是个很难的任务……
换句话说,最终,界面布局还是得程序员做,很难做到“UI设计师和程序员分工合作,一个提供XML布局文件、一个专注程序设计”。
如果有XML界面布局标准规范,那么或许Photoshop或别的公司不介意做一个支持xml布局的、标准的图形工具;UI设计师只需学会使用这个工具,就可以和任何团队配合无间。
很遗憾。做不到。他必须为配合每个团队从头学习不同的界面设计工具,而这显然是不可能的——尤其是,他还得保持不同版本界面中输入框/文本框之类组件名称的对应关系,哪怕差一个字符都不行。
很遗憾,和被迫染上了“变量名/函数名必须规范”强迫症的程序员不同,UI设计师真没有这个习惯。
2、编译器设计水平提升
和早期C++标准难产多年不同,现在编译器设计已经走向成熟。
IDE中,你敲一个字符,它做一遍语法分析成为可能;同时,编程语言的发展,使得嵌套声明被普遍支持——于是,窗体布局和代码结构就很自然的对应起来了。
你看,不需要把布局文件独立出来,一样可以实现“所见即所得”。
如此一来,和xml布局强迫性的“文件和代码相分离”相比,反而更清晰、更方便也更灵活了。
事实上,无论是微软的WPF还是Qt、GTK或者其他,它们都允许程序员直接用程序生成窗体。
而且,这些GUI库本身其实也是按照XML里面的声明、自动调用API生成了窗体。
既然编译器已经可以支持,既然UI设计师和程序逻辑分头各自开发并不现实——那么,为何不合并起来呢?
程序员直接用代码生成窗体——或者,UI编辑器直接拖放产生代码(而不是先生成XML、后把XML转换成代码)——不再需要一会儿看看XML、一会儿看看代码逻辑、生怕一个名字敲错引用了错误控件……这会不会更方便一些?
3、做出一个好的抽象,极难
总之,一切的核心,就是方便做事。怎么方便怎么来。
而不同的阶段、不同的技术、不同的计算能力,决定了具体的实现思路可行与否、难度/复杂度如何。
这也正是人们发明了无数种编程语言、试图从根本上解决某些工程难题的原因——如果你真的深入了解过、写过相关领域的典型应用,就会知道,那些看起来不起眼的“语言基础设施”,的的确确会改变工程思路、影响开发难度。
当然,这种“每种语言都有一技之长”的状况,只在二十年前明显;之后,随着计算能力的提升、编译器设计水平提高,几乎所有语言都相互汲取了其他语言的长处。
其中的典型,就是C++这种“特性泛滥”的语言。
这使得之后很多年里,除了性能和开发难度,所有语言看起来就没什么差别了。
毕竟大家互相抄,你有我有全都有。
但这种泛滥的特性其实只是一种堆砌。堆砌的极致就是C++,以至于后来业界的共识就是禁止在一个不大的项目里同时使用所有C++特性,而是根据业务范围选择其中部分子集,以免复杂度失控。
xml布局其实也是一种“所有语言都在堆砌”的特性;它和语言的结合并不好,两张皮,这带来了额外的复杂性、且很难达到宣称的效果。
kotlin其实是一种尝试,尝试把“界面布局”直接整合于语言中——这个特性不再是和其他部分毫无瓜葛的、可以剥离的特性,而是和语言其他成分水乳交融的一部分。
说的更抽象点:过去,各种特性的堆砌其实是“非正交”的,不同特性要么格格不入、谁都不碍谁;要么一旦产生了交互作用,那么你就必须动用类似placement new这样的“非常手段”——也就是封装发生了泄露。
而更理想的设计是,语言中的每一个特性都是独立的、可以和其他特性自然互动的。不至于两张皮一样。
比如,过去,你在程序中create了一个view,但没人知道、其他组件也无法和它自然交互。除非你主动通过某种奇怪的机制把这个view呈现出来,否则在程序执行到这个位置之前,这个view就不可见。也就是这个view和程序其他组件之间,是缺乏联系的,非正交的。你必须通过某些“泄露封装”的东西,把它显式的添加到某个窗口,否则这个view甚至都无法正常交互;不仅如此,你还需要通过独立的布局文件把它单独列出来,否则就无法可视化的编辑它。
而真正正交的、相容的设计里,你create一个view,这个view马上就可以呈现在可视化编辑器里;而且,它也可以很自然的被其他窗口/view使用——不需要复杂的、啰嗦的、奇特的语法或者布局文件帮助,而是简单的把它赋给另一个窗口/view中的某个变量,马上就能看到最终的效果了。
当然,作为一个用过很多蹩脚库的人,我知道很多这种设计只是取巧——其中的重灾区就是微软。
这种取巧,使得你在做一些简单、常见的工作时方便快捷;却会给稍微复杂一些工程带来巨大的麻烦、表现出库设计者的思维缺陷——它只能解决表面问题,看起来“很好很强大”,但根本就不是深思熟虑的结果。
嗯,举一个容易理解的典型案例吧:win10早期的网络控制面板隐藏了ip、子网掩码、网关、DNS等等配置信息;你想解决网络问题,它会告诉你“正在联网寻求解决方案”——网络不通了,它去联网寻找解决方案!
这就是典型的“看起来很美”的封装——对小白来说,现在你不需要自己手动配置ip、网关、dns了,微软可以自动帮你搞定,爽吧?
但当实际环境出现意料之外的问题时(对我来说,是win10的驱动无法正确驱动我的笔记本网卡),对专业人员来说,看一眼ip、gateway、dns配置,故障在哪就了解的七七八八了。
可为了避免“吓到小白”,它把这些信息藏了。藏在九曲十八弯的很多个分支之后;而一旦你选错了分支,它就“联网寻求解决方案”——锁死窗口,关都关不掉,一定要耗上若干分钟!
这就为专业人员寻找问题根源、彻底解决问题人为制造了极大的麻烦。
这个状况的根本原因是:它根本就没有完成“网络配置”这个任务的全部封装工作、做不到“自动解决一切问题”同时又“不掩盖一切必要的信息”;却又装的好像自己把问题解决了一样、不留“窥探内部”的机会(或者把窥探内部做的复杂而曲折)——换句话说,就是相关业务的所有逻辑回路尚未闭合,因此,这种方案能解决80%的问题,但却使得另外20%的问题更加的难以解决、甚至彻底无法解决。
很显然,“完美的封装”要求对相关领域的“完美掌控”;要对这个领域的一切业务完美支持——无论是简单的、小白的业务,还是复杂的、专业的业务,都要用同一个思路一揽子解决,都要一视同仁的提供便利。
而这,并不是微软的长项。
就好像MFC时代,微软就率先给出了一个很烂但能用的、被人讥讽为“没有封装”的“封装”一样。
不过,同时代,能给出漂亮封装的并不多,也就Borland做的最成功。
在无数次失败之后,业界才找到了MVC这个近乎完美的架构。
所以,我一点都不奇怪微软不去引领架构设计/编程语言发展的方向。它并不擅长这个。
至于google……这个倒是符合它的一贯形象;但能否成功,还得看实践效果——看它能不能在开发任意复杂的程序时、仍然保持界面开发的简洁性和直观性。