第20章 最佳实践 (二)
来源:互联网 发布:视频交友软件聊天 编辑:程序博客网 时间:2024/05/06 13:56
20.2 松散耦合
只要应用的某个部分过分依赖于另一部分,代码就是耦合过紧,难于维护。典型的问题如: 对象直接引用另一个对象,并且当修改其中一个的同时需要修改另外一个。紧密耦合的软件难于维护并且需要经常重写。
因为 Web 应用所涉及的技术,有多种情况会使它变得耦合过紧。必须小心这些情况,并尽可能维护弱耦合的代码。
1.解耦 HTML/JavaScript
一种最常见的耦合类型是 HTML/JavaScript 耦合。在 Web 上,HTML 和 JavaScript 各自代表了解决方案中的不同层次: HTML 是数据,JavaScript 是行为。因为它们天生就需要交互,所以有多种不同的方式将这两个技术关联起来。但是,有一些方法会将 HTML 和 JavaScript 过于紧密地耦合在一起。
直接写在 HTML 中的 JavaScript ,使用包含内联代码的 <script> 元素或者是使用 HTML 属性来分配事件处理程序,都是过于紧密的耦合。请看一下代码:
<!-- 使用了 <script> 的紧密耦合的 HTML/JavaScript -->
<script type="text/javascript">
document.write("Hello world!");
</script>
<!-- 使用事件处理程序属性值的紧密耦合的 HTML/JavaScript -->
<input type="button" value="Click Me" onclick="doSomething()" />
虽然这些从技术上来说都是正确的,但是实践中,它们将表示数据的 HTML 和定义行为的 JavaScript 紧密耦合在了一起。理想情况是,HTML 和 JavaScript 应该完全分离,并通过外部文件和使用 DOM 附加行为来包含 JavaScript 。
当 HTML 和 JavaScript 过于紧密的耦合在一起时,出现 JavaScript 错误时就要先判断错误是出现在 HTML 部分还是在 JavaScript 文件中。它还会引入和代码是否可用的相关新问题。在这个例子中,可能在 "doSomething()" 函数可用之前,就已经按下了按钮,引发一个 JavaScript 错误。因为任何对按钮行为的更改要同时触及 HTML 和 JavaScript ,因此影响了可维护性。而这些更改本应该只在 JavaScript 中进行。
HTML 和 JavaScript 的紧密耦合也可以在相反的关系上成立: JavaScript 包含了 HTML 。这通常会出现在使用 innerHTML 来插入一段 HTML 文本到页面上这种情况中,如下面的例子:
// 将 HTML 紧密耦合到 JavaScript
function insertMessage(msg){
var container = document.getElementById("container");
container.innerHTML = "<div class=\"msg\"><p class=\"post\">" + msg + "</p>" + "<p><em>Latest message above.</em></p></div>";
}
一般来说,你应该避免在 JavaScript 中创建大量 HTML 。再一次重申要保存层次的分离,这样可以很容易的确定错误来源。当使用上面这个例子的时候,有一个页面布局的问题,可能和动态创建的 HTML 没有被正确格式化有关。不过,要定位这个错误可能非常困难,因为你可能一般先看页面的源代码来查找那段烦人的 HTML ,但是却没能找到,因为它是动态生成的。对数据或者布局的更改也会要求更改 JavaScript,这也表明了这两个层次过于紧密地耦合了。
HTML 呈现应该尽可能与 JavaScript 保持分离。当 JavaScript 用于插入数据时,尽量不要直接插入标记。一般可以在页面中直接包含并隐藏标记,然后等到整个页面渲染好之后,就可以用 JavaScript 显示该标记,而非生成它。另一种方法是进行 Ajax 请求并获取更多要显示的 HTML ,这个方法可以让同样的渲染层 (PHP、JSP、Ruby 等等) 来输出标记,而不是直接嵌在 JavaScript 中。
将 HTML 和 JavaScript 解耦可以在调用过程中节省时间,更加容易确定错误的来源,也减轻维护的难度:更改行为只需要在 JavaScript 文件中进行,而更改标记则只要在渲染文件中。
2.解耦 CSS/JavaScript
另一个 web 层则是 CSS,它主要负责页面的显示。JavaScript 和 CSS 也是非常紧密相关的:他们都是 HTML 之上的层次,因此常常一起使用。但是,和 HTML 与 JavaScript 的情况一样,CSS 和 JavaScript 也可能会过于紧密地耦合在一起。最常见的紧密耦合的例子是使用 JavaScript 来更改某些样式,如下所示:
// CSS 对 JavaScript 的紧密耦合
element.style.color = "red";
element.style.backgroundColor = "blue";
由于 CSS 负责页面的显示,当显示出现任何问题时都应该只是查看 CSS 文件来解决。然而,当使用了 JavaScript 来更改某些样式的时候,比如颜色,就出现了第二个可能已更改和必须检查的地方。结果是 JavaScript 也在某种程度上负责了页面的显示,并与 CSS 紧密耦合了。如果未来需要更改样式表,CSS 和 JavaScript 文件可能都需要修改。这就给开发人员造成了维护上的噩梦。所以在这两个层次之间必须有清晰的划分。
现代 web 应用常常要使用 JavaScript 来更改样式,所以虽然不可能完全将 CSS 和 JavaScript 解耦,但是还是能让耦合更松散的。这是通过动态更改样式类而非特定样式来实现的,如下面的例子:
// CSS 对 JavaScript 的松散耦合
element.className = "edit";
通过只修改某个元素的 CSS 类,就可以让大部分样式信息严格保留在 CSS 中。JavaScript 可以更改样式类,但并不会直接影响到元素的样式。只要应用了正确的类,那么任何显示问题都可以直接追溯到 CSS 而非 JavaScript 。
第二类紧密耦合耦合仅会在 Internet Explorer 中出现 (但运行于标准模式下的 IE8 不会出现),它可以在 CSS 中通过表达式嵌入 JavaScript ,如下例:
div {
width: expression(document.body.offsetWidth - 10 + "px");
}
通常要避免使用表达式,因为它们不能跨浏览器兼容,还因为它们所引入的 JavaScript 和 CSS 之间的紧密耦合。如果使用了表达式,那么可能会在 CSS 中出现 JavaScript 错误。由于 CSS 表达式而追踪过 JavaScript 错误的开发人员,会告诉你在他们决定看一下 CSS 之前花了多长时间来查找错误。
再次提醒,好的层次划分是非常重要的。显示问题的唯一来源应该是 CSS,行为问题的唯一来源应该是 JavaScript 。在这些层次之间保持松散耦合可以让你的整个应用更加易于维护。
3.解耦应用逻辑/事件处理程序
每个 Web 应用一般都有相当多的事件处理程序,监听着无数不同的事件。然而,很少有能仔细得将应用逻辑从事件处理程序中分离的。请看以下例子:
function handleKeyPress(event){
if (event.keyCode == 13) {
var target = EventUtil.getTarget(event);
var value = 5 * parseInt(target.value);
if (value > 10) {
document.getElementById("error-msg").style.display = "block";
}
}
}
这个事件处理程序除了包含了应用逻辑,还进行了事件的处理。这种方式的问题有其双重性。首先,除了通过事件之外就没有方法执行应用逻辑,这让调试变得困难。如果没有发生预想的结果怎么办?是不是表示事件处理程序没有被调用还是指应用逻辑失败?其次,如果一个后续的事件引发同样的应用逻辑,那就必须复制功能代码或者将代码抽取到一个单独的函数中。无论何种方式,都要作比实际所需更多的改动。
较好的方法是将应用逻辑和事件处理程序相分离,这样两者分别处理各自的东西。一个事件处理程序应该从事件对象中提取相关信息,并将这些信息传送到处理应用逻辑的某个方法中。例如,前面的代码可以被重写为:
function validateValue(value) {
value = 5 * parseInt(value);
if (value > 10) {
document.getElementById("error-msg").style.display = "block";
}
}
function handleKeyPress(event) {
if (event.keyCode == 13) {
var target = EventUtil.getTarget(event);
validateValue(target.value);
}
}
改动过的代码合理将应用逻辑从事件处理程序中分离了出来。handleKeyPress() 函数确认是按下了 Enter 键 (event.keyCode 为 13) ,然后取得了事件的目标并将 value 属性传递给 validateValue() 函数,这个函数包含了应用逻辑。注意 validateValue() 中没有任何东西会依赖于任何事件处理程序逻辑,它只是接收一个值,并根据该值进行其他处理。
从事件处理程序中分离应用逻辑有几个好处。首先,可以让你更容易更改触发特定过程的事件。如果最开始由鼠标点击事件触发过程,但现在按键也要进行同样处理,这种更改就很容易。其次,可以在不附加到事件的情况下测试代码,使其更易创建单元测试或者自动化应用流程。
以下是要牢记的应用和业务逻辑之间松散耦合的几条原则:
- 勿将 event 对象传给其他方法;只传来自 event 对象中所需的数据;
- 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;
- 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。
- 不要为实例或原型添加属性;
- 不要为实例或原型添加方法;
- 不要重定义已存在的方法。
- 创建包含所需功能的新对象,并用它与相关对象进行交互;
- 创建自定义类型,继承需要进行修改的类型。然后可以为自定义类型添加额外功能。
这段重写的代码引入了一个单一的全局对象 MyApplication ,name 和 sayName() 都附加到其上。这样做消除了一些存在于前一段代码中的一些问题。首先,变量 name 覆盖了 window.name 属性,可能会与其他功能产生冲突;其次,它有助消除功能作用域之间的混淆。调用 MyApplication.sayName() 在逻辑上暗示了代码的任何问题都可以通过检查定义 MyApplication 的代码来确定。
单一的全局量的延伸便是命名空间的概念,由 YUI(Yahoo! User Interface) 库普及。命名空间包括创建一个用于放置功能的对象。在 YUI的2.x版本中,有若干用于追加功能的命名空间。比如:
- YAHOO.util.Dom -- 处理 DOM 的方法;
- YAHOO.util.Event -- 与事件交互的方法;
- YAHOO.lang -- 用于底层语音特性的方法。
- 如果值应为一个引用类型,使用 instanceof 操作符检查其构造函数;
- 如果值应为一个基本类型,使用 typeof 检查其类型;
- 如果是希望对象包含某个特定的方法名,则使用 typeof 操作符确保指定名字的方法存在于对象上。
在这段重写过的代码中,消息和 URL 都被定义于 Constants 对象中,然后函数引用这些值。这些设置运行数据在无须接触使用它的函数的情况下进行变更。Constants 对象甚至可以完全在单独的文件中进行定义,同时该文件可以由包含正确值的其他过程根据国际化设置来生成。
关键在于数据和使用它的逻辑进行分离。要注意的值的类型如下所示。
- 重复值 -- 任何在多处用到的值都应抽取为一个变量。这就限制了当一个值变了而另一个没变的时候会造成的错误。这也包含了 CSS 类名。
- 用户界面字符串 -- 任何用于显示给用户的字符串,都应该被抽取出来以方便国际化。
- URLs -- 在 Web 应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的 URL。
- 任意可能会更改的值 -- 每当你在用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化。如果答案是 "是" ,那么这个值就应该被提取出来作为一个常量。
- 第20章 最佳实践 (二)
- 第20章 最佳实践 (一)
- 第20章 最佳实践 (三)
- 第20章 最佳实践 (四)
- Dockerfile最佳实践(二)
- CUDA最佳实践(二)
- 最佳网络编程实践二
- 第6章 需求分析与建模最佳实践
- JavaScript高级程序设计第24章(最佳实践)
- 第2.1章 WEB系统最佳实践Spring文件配置
- 第2.2章 WEB系统最佳实践Web.xml配置
- 第2.3章 WEB系统最佳实践属性配置
- nodejs 实践:express 最佳实践(二) 中间件
- Unity3D游戏开发最佳实践20技巧(二)
- 《Visual C# 最佳实践》第三章 数组结构 (二):排序
- 《Visual C# 最佳实践》第四章 函数 (二):函数分类
- [ ANT ] ---------- ANT 十五大最佳实践 (第一页)
- 活动的最佳实践--第一行代码
- 匿名内部类 是否可以继承其它类,是否可以实接口
- chapter 7之进程管理
- sms开发
- 第20章 最佳实践 (一)
- java应用运维
- 第20章 最佳实践 (二)
- hbase运维
- 从今天起,为了生活要狠努力
- getch()与system("pause")的区别
- JDK+Tomcat+Servlet连接Mysql数据库
- 第20章 最佳实践 (三)
- extern “C” 阅读笔记
- 左旋转字符串总结(v_JULY_v的编程艺术第一章笔记)
- MyEclipse6.5下载地址(含注册码)