用 JavaServer Faces 2 实现可扩展 UI (2)
 
- UID
- 1066743
|

用 JavaServer Faces 2 实现可扩展 UI (2)
技巧 1:遵守 DRY 原则在我作为软件开发人员从事的第一项工作中,我的任务是为基于 UNIX® 的计算机辅助设计和计算机辅助制造(CAD/CAM)系统实现一个 GUI。
最初,一切进行顺利,但是一段时间后,我的代码开始问题不断。待到代码发布的时候,系统已经相当脆弱,我甚至都害怕修复 bug,而这次的代码发布自然也伴随着一连串的 bug 报告。
如果我在这个项目中遵循了 DRY 原则 — 不重复自己(Don't Repeat Yourself),我本可以让自己不至于这么悲惨。DRY 原则最初由 Dave Thomas 和 Andy Huntprinciple 提出(参见 ),它要求:
每条知识都必须在系统内具有一个单一、清晰和权威的表示。 我的 CAD/CAM 应用程序并不符合 DRY 原则 — 它具有太多关注点之间的交叉 — 因此在一个地方所做的更改常常会在其他地方引起意想不到的更改。
JSF 1 在几个方面违背了 DRY 原则,比如,它强迫您提供托管 beans 的两种表示 — 一个使用 XML,一个使用 Java 代码。对多重表示的需求让创建和更改托管 bean 更加困难。正如我在本系列 中介绍的,JSF 2 让您能够使用注释取代 XML 来配置托管 bean,这样一来,托管 bean 就具有了一个单一、权威的表示。
除托管 beans 之外,就连一些看似有益的实践 — 比如在所有视图中包括相同的样式表 — 也违背了 DRY 原则,并会导致混乱。比如,如果要更改样式表的名字,就必须更改多个视图。如果可能,最好是封装此样式表包含。
DRY 原则同样适用于代码设计。如果多个方法均包含遍历树的代码,一种好的做法是(比如在一个子类中)封装遍历树的算法。
在实现 UI 时,因大多数更改均在开发过程中发生,所以遵守 DRY 原则尤其重要。
JSF 2 模板JSF 2 在很多方面都支持 DRY 原则,其中之一就是通过模板。模板能够封装在应用程序视图中十分常见的功能,因此该功能只需被指定一次。在 JSF 2 应用程序中,一个模板可供多个组装(compositions)用于创建视图。
我在 中所介绍的 places 应用程序具有三个视图,如图 1 所示:
图 1. places 应用程序的视图:Login、source viewer 和 places与很多 Web 应用程序一样,这个 places 应用程序包含多个具有相同布局的视图。JSF 模板功能让您能够在一个模板内封装该布局 — 及其他共享工件,比如 JavaScript 和 Cascading Style Sheets(CSS)。清单 1 是 中所示的这三个视图的模板:
清单 1. places 模板:/templates/masterLayout.xhtml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>
<ui:insert name="windowTitle">
#{msgs.placesWindowTitle}
</ui:insert>
</title>
</h:head>
<h:body>
<h utputScript library="javascript" name="util.js" target="head"/>
<h utputStylesheet library="css" name="styles.css" target="body"/>
<div class="pageHeading">
<ui:insert name="heading">
#{msgs.placesHeading}
</ui:insert>
</div>
<div class="menuAndContent">
<div class="menuLeft">
<ui:insert name="menuLeft"/>
</div>
<div class="content" style="display: #{places.showContent}">
<ui:insert name="content"/>
</div>
<div class="menuRight">
<ui:insert name="menuRight">
<ui:include src="/sections/shared/sourceViewer.xhtml"/>
</ui:insert>
</div>
</div>
</h:body>
</html>
|
中的模板为此应用程序的所有视图提供了如下的基础设施:
- HTML <head>、<body> 和 <title>
- 一个默认标题(可由使用此模板的那些组装覆盖)
- 一个 CSS 样式表
- 某些实用 JavaScript
- 一个布局,格式为 <div>,以及对应的 CSS 类
- 头的默认内容(可被覆盖)
- 右菜单的默认内容(可被覆盖)
正如 所示,模板通过 <ui:insert> 标记将内容插入到布局中。
如为 <ui:insert> 标记指定了主体,正如我在 中为窗口标题、头和右菜单所做的,JSF 会将此标记的主体作为默认内容。借助 <ui:define> 标记,使用此模板的那些封装可以定义内容或者覆盖默认内容,如清单 2 所示,它给出了 login 视图的标记:
清单 2. login 视图1
2
3
4
5
6
7
8
9
10
11
12
13
| <ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="menuLeft">
<ui:include src="/sections/login/menuLeft.xhtml"/>
</ui:define>
<ui:define name="content">
<ui:include src="/sections/login/content.xhtml"/>
</ui:define>
</ui:composition>
|
这个 login 视图为窗口的标题、头和右菜单使用了模板的默认内容。它只定义了特定于此 login 视图的功能:内容部分和左菜单。
通过为窗口标题、头或右菜单提供 <ui:define> 标记,我也可以覆盖此模板的默认内容。比如,清单 3 显示了这个 source-viewer 视图( 中间的图片):
清单 3. source-viewer 视图1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="content">
<ui:include src="/sections/showSource/content.xhtml"/>
</ui:define>
<ui:define name="menuLeft">
<ui:include src="/sections/showSource/menuLeft.xhtml"/>
</ui:define>
<ui:define name="menuRight">
<ui:include src="/sections/showSource/menuRight.xhtml"/>
</ui:define>
</ui:composition>
|
source-viewer 视图定义了内容部分以及右菜单的内容。它还覆盖了由 中的模板定义的针对左菜单的默认内容。
清单 4 显示了 places 视图( 底部的图片):
清单 4. places 视图1
2
3
4
5
6
7
8
9
10
11
12
13
| <ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="menuLeft">
<ui:include src="/sections/places/menuLeft.xhtml"/>
</ui:define>
<ui:define name="content">
<ui:include src="/sections/places/content.xhtml"/>
</ui:define>
</ui:composition>
|
JSF 2 模板功能模板功能背后的概念十分简单。定义一个模板来封装在多个视图中常见的功能。每个视图由一个组装和一个模板组成。
当 JSF 创建视图时,它加载组装的模板,然后将由组装所定义的内容插入模板。
请注意清单 、和 之间的相似性。所有这三个视图均指定模板并定义内容。另外,也请注意创建新视图十分容易,因为大多数视图的基础设施都封装在模板及所包含的文件内。
使用 JSF 模板功能的另一个有趣之处是类似清单 、 和 中的这些视图并不会随时间有太多变化,所以大部分视图代码基本不需要维护。
与使用模板的视图类似,模板本身也更改甚少。由于大量常见功能都封装在几乎不用维护的代码中,这样一来,您就可以将精力集中于视图的实际内容 — 比如,login 页面的左菜单应该有些什么内容。专心于视图的实际内容就是下一个技巧的主旨所在。 |
|
|
|
|
|