转自:http://benchwang.spaces.live.com/blog/cns!1621B5CAD6EB680B!149.entry
Adam McCrea 写了篇使用 JavaScript 进行元编程的文章: Metaprogamming JavaScript。
该文用一个例子来说明元编程。例子很简单,一个 Form 包中两个下拉列表 country 和 state,业务需求是 country 下拉列表中选中“United States”则要显示 state 列表,否则隐藏该下拉列表。处理该逻辑的代码如下:
Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
if ($F(“country”) == “United States”)
$(“state-field”).show();
else
$(“state-field”).hide();
});
});
这里用到的对象和方法是 Prototype 对 JavaScript 的扩展。Prototype 是旨在简化动态 Web 程序开发的 JavaScript Framework. 详见 http://www.prototypejs.org/。在上面代码中:Event.observe 在IE下相当于 attachEvent,用于注册事件处理函数。这是注册了 window.onload 和 country.onchange 事件处理程序。$() 是 document.getElementById() 的别名。$F() 会取出 form 中元素当前值。show() 和 hide() 是在 html 元素上增加的方法。通过 prototype 在 html 元素基础上扩展了很多方法。如果想运行上面代码,需要下载 prototype.js 并放置在 html 页面相同目录。完整代码如下:
<html>
<head>
<title>Metaprogramming JavaScript – Example 1</title>
<script src=”prototype.js”></script>
<script type=”text/javascript” charset=”utf-8″>
Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
if ($F(“country”) == “United States”)
$(“state-field”).show();
else
$(“state-field”).hide();
});});
</script>
</head><body>
<form>
<p id=”country-field”>
<label for=”country”>Country</label>
<select id=”country”>
<option>United States</option>
<option>Canada</option>
<option>Somewhere Else</option>
</select>
</p><p id=”state-field”>
<label for=”state”>State</label>
<select id=”state”>
<option>Ohio</option>
<option>Michigan</option>
<option>Kentucky</option>
</select>
</p>
</form>
</body>
</html>
这些代码满足这样简单的需要没有什么问题。然而随着需求变化,代码会变得繁琐。假如增加如下需求:
增加 province 字段
当 country 为 “Canada” ,显示 province
当 country 为 “United States”,显示 state
当 state 是 “Ohio” 或 “Michigen”,显示 Brutus(很可爱的logo)
根据以上需求,代码修改为:
Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
var country = $F(“country”);
if (country == “United States”) {
$(“us-state-field”).show();
$(“province-field”).hide();
} else if (country == “Canada”) {
$(“province-field”).show();
$(“us-state-field”).hide();
} else {
$(“us-state-field”).hide();
$(“province-field”).hide();
}
});Event.observe($(“us-state”), “change”, function() {
var state = $F(“us-state”);
if (state == “Ohio” || state == “Michigan”)
$(“brutus”).show();
else
$(“brutus”).hide();
});});
新程序使用先前的事件处理和字段检查方式,尝试扩展程序以满足新的需求。然而这样有几个问题,第一个问题这个代码有两个 bug:1)页面初始载入时所有字段都显示出来了,2)隐藏 state 字段不会,不会自动隐藏 Brutus。后者可称为多米诺(domino)bug,因为如果有其他元素依赖于动态显示和隐藏的字段会导致级联影响。
第二个问题是代码的可读性。不容易立即看出这段代码想达到的目的,因而需要很仔细的检查是否遗漏或者不严格符合需求。当需求发生变化,很希望准确地知道代码什么地方需要修改,当作了修改后,需要有信心确信不会影响其它功能。元编程概念就是用于解决这个问题。
Pragmatic Programmer(实用主义程序员?) 把元编程描述为江代码中的细节抽出来放到元数据中。元数据通常为配置文件或者其他的数据源,当然元数据也可以是可执行代码,只要把“what”(是什么)和“how”(如何做)清晰分离开。该方法的思路是使用易于描述问题论域的词汇来编写代码。问题论域即要描述的“what”。这种类型语言称为领域专用语言(domain specific language),缩写为 DSL。在上面例子中,“what”是显示和隐藏 form 中字段的规则。目前的实现是把规则和规则的实现耦合在一起。随着需求变化和特性增加,代码会变得越来越不清晰。如果能够将这些规则抽出来,规则修改不再依赖于实现,会减少应对需求变化的苦恼。如果使用一种比 JavaScript 更接近问题论域的语言来描述规则,会将问题大幅简化。
编写 DSL 可以从考虑描述业务领域使用的词汇入手。这些词汇有时会因为描述人角色不同而不同。例如程序员、用户、业务分析员就会各不相同。在作出上述假设前,可以考虑一个问题:为什么这些不同角色要使用不同词汇呢?不同词汇在沟通中导致信息失真。通过找到在各组人之间通用的词汇,可以消除信息传递,也就可避免信息失真。这种公共的词汇通常可以在需求说明书中找到:
show us-state when country is “United States”
show province when country is “Canada”
show Brutus when state is “Ohio” or “Michian”
DSL 应该尽可能与上述描述一致。
有可能直接使用上述描述。上述描述并不是可执行的 JavaScript 代码,但可以放在文本文件中,使用 JavaScript 读出来进行解析。这种方法虽然可行,但可能过于复杂。如果使用纯文本作为 DSL,它需要和英语同样的灵活性。但我们希望把它作为数据,它需要遵循一些严格的规则。如果 DSL 本身就是可执行代码,可以将问题简化。不足的一面是非程序开发人员可能不会使用这种 DSL。实际上让非开发人员写DSL也是不现实的目标。如果程序元写好了,业务人员能够看懂或者能够修改,就很不错了。怎样处理例子中的需求,把它转成可执行代码?这可能是最难的一步了,具有很强的主管性。在 JavaScript 中,有两个建议:把方法串起来组成类似于句子的结构,灵活的使用方法名。沿着这个思路,可将上面的业务规则描述如下:
show(“us-state-field”).when(“country”).is(“United States”);
/>show(“province-field”).when(“country”).is(“Canada”);
show(“brutus”).when(“us-state”).is(“Ohio,Michigan”);
方法名“when”和“is”本身表意不完整,但在 DSL 上下文中,这些方法串起来,能够表意很好。可以看出 show() 返回一个对象,该对象具有方法 when();when() 返回一个对象,该对象具有方法 is()。下面给出能够描述第一条规则的简单实现:
function show(fieldToDisplay){
return {
when : function(field){
return {
is: function(value){
if($F(field) == value){
$(fieldToDisplay).show();
}
else{
$(fieldToDisplay).hide();
}
}
}
}
};
};
完整代码如下:
<html>
<head>
<title>Metaprogramming JavaScript – Example 1</title>
<script src=”prototype.js”></script>
<script type=”text/javascript” charset=”utf-8″>
function show(fieldToDisplay){
return {
when : function(field){
return {
is: function(value){
if($F(field) == value){
$(fieldToDisplay).show();
}
else{
$(fieldToDisplay).hide();
}
}
}
}
};
};Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
show(“us-state-field”).when(“country”).is(“United States”);
});});
</script>
</head><body>
<form>
<p id=”country-field”>
<label for=”country”>Country</label>
<select id=”country”>
<option>United States</option>
<option>Canada</option>
<option>Somewhere Else</option>
</select>
</p><p id=”us-state-field”>
<label for=”state”>State</label>
<select id=”us-state”>
<option>Ohio</option>
<option>Michigan</option>
<option>Kentucky</option>
</select>
</p>
</form>
</body>
</html>
以上只是一个简单示意,在后续文章中会给出完整的实现。