<!-- /* Font Definitions */ @font-face {font-family:Wingdings; panose-1:5 0 0 0 0 0 0 0 0 0; mso-font-charset:2; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:0 268435456 0 0 -2147483648 0;} @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:867331554; mso-list-template-ids:2136221472;} @list l0:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:36.0pt; mso-level-number-position:left; text-indent:-18.0pt; mso-ansi-font-size:10.0pt; font-family:Symbol;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
原文地址:
http://www.never-online.net/blog/article.asp?id=258
今天想把以前想写的文章写的前端UI 控件的问题写完,也许还有点语句不太顺畅,能看懂就行了,呵呵,毕竟是半夜写的东西,难免有点糊涂。
在应用当中如何构建一个比较灵活的UI 控件是一个比较让人头痛的问题。
本文将自己的一些经验和思路拿出来和大家讨论一下,在这里并不想用设计模式的理论上去套,如果硬套上去感觉有点象是为了理论而理论了。但基本出发点是一样的。
为了少写和易于维护代码,所以我们尽量在前期做好设计。
首先,先概括一下我们做程序时应该遵循的大概原则。
- 无强依赖
- 事件模型
- 易扩展性
- 接口统一
- 无硬偏码
- 单一职责
- 易用性
- 简单灵活
无依赖性
比 如你有一个很好的库,例如YUI 之类的基础框架。如果你把UI 控件很多的代码都依赖于这个框架,假设这个框架升级了大版本,你的代码将很有可能重构一次。 或者再设想一个情况,如果你的控件依赖了好几个文件。我们在一个页面只需要用到一个UI 控件,那么我们也不得不把所依赖的文件都链进来。这无疑增加了 http 请求。这时你可能会说,可以合并几个所依赖的文件。但是你是不是违被了你的初衷呢?—— 管理起来又复杂了。
在这里我不是说毫不依赖于框架。而是有所为有所不为。比如,如果你只在框架里用到判断是否浏览器是否为IE ,何苦依赖于一个大的框架呢?一句!!window.ActiveXObject 不就行了吗?
无依赖这点如何做是一个项目里评估并决策的过程,因此是否依赖根据实据情况而定。如果在这一点上我能为各位提一个醒,对一些朋友有所帮助就足矣。
事件模型
假设我们把一个框架/UI 控件看成一个金字塔型,或者看作一个树结构。
如 果你是喜欢一句代码就把一个UI 控件生成,且控件帮你做所有的事的话,那么只能说这个UI 控件是整个框架或者继承树的叶子(最末端)。通常这样做的后果是 欠缺灵活,哪天需求变了,你的UI 控件就必须得重写。我们现在的编程都是事件驱动的,因此,在很多情况下,灵活的事件机制可以达到象人的关节一样,练散打 也可以,打太极也行。如何做就看你怎么用了。
关于这一点,我们可以向操作系统来学习。比如:DragDrop 的设计,可以看做是实现一个事件模型;它完全可以说是一套事件驱动编程的典范。关于这一点以前也写过一篇文章。
详细的设计及JS 伪代码可以参看一下我的这篇文章——设计简单可重用的拖动(DragDrop) 库. 。
易扩展性
易扩展是指至少在UI 控件分层的时候应该有一个程序员可操作的层,这也是YUI 的示例里还会有这么多的代码的原因。
接口统一
事实上我想我们应该把它和数据抽象联系起来。先言归正传吧,为什么我们要有一个统一的接口供使用者调用呢?
假设一个Tip 组件,一个Dialog 组件,你会怎么定义什么名字给使用该控件的人调用?
是这样?
Copy Code(拷贝代码) -Run HTML(运行代码) -Save Code(另存代码)
var tip = new Tip(...);
var dlg = new Dialog(...);
tip.showTip();
dlg.showDialog();
这个思路是没有错的。而且使用该控件的人应该也没有什么怨言的了。但仔细考虑一下我们的设计:
首先我们都可以把他们归属于container 类,(都可以说他们都是一个容器的扩展,且不说是is a container 还是has a container 的关系)
因 此,我们可以把一个container 定义一组统一的接口。例如显示,隐藏,设置坐标等接口,也可以包括设置left,top,width,height 等等属性,他们应该是一样的公用接口。以上的代码因为没有在设计上把公用的接口抽象出来,虽然我们不完全面向对象,但至少接口也可算作是公用的约定。因此 上面的代码个人觉得可以适当修改为:
Copy Code(拷贝代码) -Run HTML(运行代码) -Save Code(另存代码)
var tip = new Tip(...);
var dlg = new Dialog(...);
tip.show();
dlg.show();
无硬编码
硬编码是一个让人头疼的问题。举个例子:
Copy Code(拷贝代码) -Run HTML(运行代码) -Save Code(另存代码)
var img = document.getElementsByClassName('img-k');
for (var i=0, l=img.length; i<l; i++)
...
上面代码里这个'img-k' 是什么类名对于维护这份代码的人完全不可知,他为了知道这代码的作用,得切换到HTML ,找到HTML 里img-k 的结构,再分析JS 获取该className 元素集合的作用是什么。
假如改成以下这样:
Copy Code(拷贝代码) -Run HTML(运行代码) -Save Code(另存代码)
var dragDropClassName = 'img-k';
var img = document.getElementsByClassName(dragDropClassName);
for (var i=0, l=img.length; i<l; i++)
...
是不是会清晰多了。
单一职责
比如一个控件Dialog 里面,你就不应该把控制坐标的代码在控件里面写,应当委托给一个坐标控制器。
一个基础的tabview 里面就不应该写死结构。
如果坐标由Dialog 来管,那么你UI 控件里会有越来越多这样的代码。
如果tabview 这样的标准结构:
Copy Code(拷贝代码) -Run HTML(运行代码) -Save Code(另存代码)
<div id="tabview">
<ul>
<li>tab1</li>
<li>tab2</li>
<ul>
<div>
<div>content1</div>
<div>content2</div>
</div>
</div>
代码结构很明显,是一个tab 节点对应一个内容节点。在你的代码里是可以应付的。
假设一个tabview 是纵向的tab 排列。亦或者结构有变,我要改变HTML 结构。你就比较麻烦了。这里又出现另一个问题,如何应付HTML 结构与脚本代码的高耦合呢?
请参看这篇文章——降低HTML 结构与脚本之间的强耦合 ,这里就暂不过多描述了。
把话说回来。tabview 在我看来只是一个管理器,控制tabview 的tab 节点的事件触发我们定义好的事件模型。他不负责组织tabview 的结构。
UI 控件应该分层,但不要超过三层。每层都有他们的职责。让他们该干嘛干嘛,不要一个控件把所有的好事做完。
易用性,简单灵活:
这似乎和单一职责相矛盾。如果说要简单灵活,又要易用性好。就有必要在UI 框架的叶端为其做一个能够足够满足大多数人的一个应用。
一个框架拆分得多了,学习成本似乎要提高。因为使用者必须了解更多的类型用来拼装成想要的一个应用。积木要搭好,还要看你这个积木的结构是怎么样的。
可以说,学习一个框架与学习一门新语言一样让人心烦。它使我们在学习成本,易用性,单一职责上面要做一个权衡。
需求永远都是我们的指路明灯。我个人觉得在这点上做得好不好,可以从代码架构上看出一个人的整体水平。
简单灵活的使用可以体现在我们的代码的传参,如果是一个UI 控件,我会建议如果有标准循标准,否则建议传json ,而不是传统的参数类型。一是为了以后你能很好的扩展你控件的参数个数。二是给使用者不必死记硬背参数的顺序。
YUI 之所以受程序员的青睐,是因为他可以让我们不考虑兼容,提供了基础框架,
JQuery 之所以精彩是因为他为我们的开发带来了具大的便利,也就是我们所说的易用性。
任何一个框架都是一个迭代和完善的一个过程,重构不可避免,没有完全无bug 的程序,设计本就是一个自由体。
本文纯属自己的漫谈,如有不对,请各位及时指出。