Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » 技术文章库  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 诊断 Java 代码:消除包间的耦合关联
palatum



CJSDN高级会员


发贴: 451
积分: 80
于 2003-03-07 14:20 user profilesend a private message to usersend email to palatumsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
诊断 Java 代码:消除包间的耦合关联

基于组件编程给连接可测试代码带来更大的灵活性
级别:入门


Eric E. Allen (eallen@cs.rice.edu)
博士研究生,Java 编程语言团队,Rice 大学
2003 年 3 月

在测试程序时,如何应付模拟外部资源和库连接这一艰巨任务?基于组件的编程和去耦包相关性可以解决这个问题。基于组件的软件开发作为促进代码的一种手段而经常受到人们赞誉,另外它还有助于生成可测试代码。Eric Allen 用一些 Jiazzi(一种使用 Java 语言进行基于组件编程的功能强大的自由工具)示例展示了这种编程概念。可以通过论坛提出您对本文的想法,以飨笔者和其他读者。(您也可以单击本文顶部或底部的“讨论”参加论坛。)
测试优先编程(test-first programming)中反复遇到的一个问题是,似乎不可能对程序的许多部分进行自动测试。尤其当程序在很大程度上要利用外部资源和库时,似乎很难对它进行测试,因为没有很好的方法来模拟程序与这些外部资源的连接。

然而,虽然只使用 Java 代码很难测试这样的程序,但有一种类型的编程(带有开发工具)可以解决这个问题 — 基于组件的编程。

基于组件的编程和 Java 语言
我所说的基于组件的编程是指什么?我只是指,编程时程序的各个单元处于分布状态,而不是象 JavaBeans 或类似技术这样的运行时“组件”。

从概念上讲,这些分布的各个单元大致类似于 Java 包。然而,Java 语言中的包非常受限,因为它们相互之间是耦合的。每个包中的类与它们导入的包之间是硬连接的(因为这些类必须显式地引用所导入的包)。

由于这些包之间是相互耦合的,因此很难统一用提供同一功能的其它包的引用来替代程序中这些包的引用。

同样,独立开发的各个团队可能偶尔会用到重复的包名,这些团队试图使用对方的包时,就会引起问题。为确保包名的唯一性,Sun 强烈主张每个开发团队使用这样的约定:用倒序排列的因特网地址作为团队开发的所有包的前缀。开发人员通常都遵守这个约定,但未必总是如此。

然而即便严格遵守了这个包命名约定,仍然有其它一些原因使程序员想解除组件之间的耦合。其中一个原因是,这样可以更有效地测试这些组件 — 在谈到基于组件的编程工具(Jiazzi 组件系统)时,会解释这一点。

Jiazzi:针对 Java 语言的组件系统
Jiazzi 是一个富有前途的、用 Java 语言进行基于组件编程的系统,它与 JVM 完全兼容,并且完全解除了各组件间的耦合,它是犹他大学计算机科学系所开发的一个项目。这个系统使程序员可以叠加组件,并在现有 Java 代码之上将这些组件连接起来。而不需修改 Java 语言或 JVM。

开发人员的描述
Jiazzi 开发人员是这样描述的:

……是这样一个系统,支持用 Java 编写的大规模二进制组件的构造 [添加了对用 Java 编写的大规模二进制组件的支持]。可以将 Jiazzi 组件看成是对 Java 包的泛化,同时向这些 Java 包添加了外部链接和独立编译的支持。Jiazzi 组件很实用,因为它们是从标准 Java 源代码构造出来的。Jiazzi 既不需要对 Java 语言扩展,也不需要对编写 Java 源码进行特殊的约定,这些扩展和约定将写在组件内部。我们的组件是富有表现力的,因为 Jiazzi 支持循环组件链接和 mixin,在开放的类模式中一起使用了循环组件链接和 mixin,这种模式支持将具有新特性的模块添加到现有的类。
当前的 Jiazzi 实现用链接程序(linker,用于操作组件)和存根生成器(使 Jiazzi 可以与常规的 Java 源码编译器共同使用)集成进了 Java 平台。Jiazzi 中的组件可以包含、导入和导出 Java 类,可以跨组件边界使用 Java 平台的用于继承的语言内支持。除了富有表现力之外,这些组件还很健壮 — 可以分别对组件的实现和链接进行类型检查。

观察解除组件的耦合
让我们研究一个 Java 包 view 的简短示例,来看一下 Jiazzi 如何解除组件间的耦合,这个示例用到了 GUI 库包。我们将调用 toolkit 包。为了引用该包中的所有类,在我们的包中源文件的开头,放置了一条 import 语句:

package view;

import toolkit.*;

...



通常,这会将 view 包与 toolkit 包联系起来。但可以设想一下,我们希望编译这个源文件,却不能确定我们实际上正在导入哪个包。我们没有将 toolkit 与 view 包硬连接起来,而是设想在这两个包之上定义一个函数,它可以接受 toolkit 包,并返回与 toolkit 包相关的 view 包。在 Jiazzi 中,这些函数称为单元(unit)。

单元类似于 LEGO 积木;可以将它们拼装在一起创建一个程序。如果将单元视为函数,则可以说,Jiazzi 提供了函数复合(functional composition)。每个单元接受一个或多个带有指定“包签名”的包,同样,可以用指定的签名导出一个或多个包。

包签名类似于类型;它们限制了包的形状。包签名会定义包中所期望的类以及这些类的方法签名等。导出包的签名可以取决于导入包的签名。

有两类单元:

原子(atom),是包之间的简单映射
复合(Compound),是其它单元的组合
原子描述了直接导入和导出的包。复合从所组合的单元继承了导入和导出的包。如果将单元视为 LEGO 积木,那么原子就是单个的 LEGO 积木,复合就是从多个 LEGO 积木构建而来的结构。

在单独的文件中,用特定的规范语言描述单元。该语言给单元输入和输出的包分配名称。例如,这里有一个简单的原子单元,它接受 toolkit 包,并输出 view 包:

atom app_view {
  import toolkit: toolkit_s;
  export view: view_s;
}



这个单元称为 app_view。它将“toolkit”名称分配给它所导入的包。声明这个包以与 toolkit_s 这个特定的包签名相匹配。该单元导出的包称为 view,并声名这个包以与 view_s 签名匹配。

正如前面所提到的,包签名类似于类型;它们限制了可能传递给某个单元(或从某个单元返回)的参数的类型。例如,toolkit_s 签名可以象这样指定一组类(这些类非常类似于 javax.swing 包中的类):
signature toolkit_s {
  class Frame {
    public Container getContentPane();
    public Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



我们还可以象这样指定 view_s 签名:

signature view_s {
  class EditorPane {...}
  class InteractionsPane {...}
  ...
}



当然,在签名中所引用的某些类本身可能在单独的包中。为了解除特定包中包签名间的耦合,Jiazzi 允许对包签名使用参数。在签名名称后的尖括号内是签名的包参数。例如,我们可能希望 toolkit_s 签名使用 awt 包参数,如下所示:

signature toolkit_s<awt> {
  class Frame {
    public awt.Container getContentPane();
    public awt.Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



然后,我们必须设计新的签名(称为 awt_s),并修改 app_view toolkit 来接受 awt 包,并用这个包实例化 toolkit_s 签名:

atom app_view {
  import
    my_toolkit: toolkit_s%lt;awt_s>;
    my_awt: awt_s;
  export my_view: view_s;
}



接着,Java 源文件可以引用 my_toolkit 和 my_view 这两个已经分配的名称,就好象它们是真正的包名一样。事实上,Jiazzi 可以让我们在根本不做任何修改的情况下重新编译上面的源文件,以引用这些已经分配的名称!(请继续阅读下面的内容,其中解释了如何这样做。)

象 LEGO 积木一样,可以使用复合单元拼装单元。复合单元以特殊的“link”子句将其它单元组合起来,其中“link”子句标识出由一些单元导出的类,而这些类是由其它单元导入的。例如,可以将 app_view atom 与 default_toolkit 和 default_awt 单元按如下方式组合起来:

atom default_toolkit {
  import my_awt: awt_s;
  export my_toolkit: toolkit_s<my_awt>;
}

atom default_awt {
  export my_awt: awt_s;
}

compound app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: default_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view to my_view;
}



注意单元 app 中的 local 子句。这条子句定义了表示“单元实例”的局部变量。这些单元实例中的类是实际链接的元素。通过链接单元实例(而不是直接链接单元),Jiazzi 可以清楚地表明复合单元中的链接不影响各组成单元的定义。

通过为程序指定这些签名、原子和复合,我们描述了如何将程序中的各个包链接在一起。Java 源文件是指单元所输入和输出的包,但在另一方面它们看上去象普通的 Java 文件。下一节将详细讲述如何使用 Jiazzi 来以单元所指定的方式将 Java 类真正链接在一起。

Jiazzi 的工作原理
Jiazzi 分三个阶段编译代码:

首先,将一组签名和单元定义传送给 Jiazzi 存根生成器,然后,Jiazzi 存根生成器为传递给该生成器任何单元的签名中所导入的所有类生成存根类文件。

然后,常规的 Java 编译器使用这些类文件来编译源文件,这些源文件对应于提供给存根生成器的单元所导出的类。

编译完源文件后,Jiazzi 单元链接程序检查结果类文件是否与原来单元中所声明的类签名匹配。必需要有这一步,因为:

Jiazzi 可以与任何第三方的编译器共同使用
Jiazzi 从不检查 Java 源文件
(顺便说一句,请注意,Jiazzi 这种事实上从不检查 Java 源代码的方法有其优越之处。它使 Jiazzi 可以与用于 JVM 的非 Java 语言的编译器一起使用,譬如 Jython、JSR-14 和 NextGen 编译器。事实上,Jiazzi 本身就是用 JSR-14 编写的。)

检查完之后,组件链接程序为提供给它的每个单元生成一个 JAR 文件。这个 JAR 文件包含已编译的源文件和存根以及作为元数据的签名信息。接着,通过将这些 JAR 文件传递给 Jiazzi 及相应的复合单元,从而将这些 JAR 文件链接起来。

Jiazzi 单元链接程序是脱机工作的,并且单独地工作在类文件常量池之上,而且会重命名隐藏的方法,从而避免了偶尔会发生的方法名称冲突。

通过特定的类装入器,还可以联机链接单元。然而,由于不能使用编译单元所依据的存根类,因此必须在类装入器中进行类型检查,作为“递增性整体程序分析”。事实上,Jiazzi 程序员目前必须将脱机链接和联机链接结合起来使用,因为在标准 Java 库中,有许多类只能通过类装入器来链接。

当前系统的另一个限制是,重命名会影响到 JNI 和反射库。尤其是不能重命名本机方法,因为它们是用 C 语言编写的。其结果是,许多类库(那些过度依赖 JNI 和反射的库)不能作为 Jiazzi 组件进行重新打包。

如上所说,复合单元描述其它单元间的连接。这些链接与该复合的导入和导出单元的绑定名称相关;已链接的单元完全不会意识到已将它们链接起来了(链接程序最终会将这些复合单元宏展开成原子)。

通过这种方式,用简单的重新编译换入和换出新包,从而使我们可以创建和分发程序的 JAR 文件。而不需要涉及一行 Java 源代码。

此外,其他 Jiazzi 用户能够在可以用 JAR 文件之前,根据程序所提供的类进行开发;他们所需要的只是相应导出的包签名。他们对包所做的扩展也只与那个包签名的任何其它实现相链接。

单元测试和 Jiazzi
基于组件的编程提供了许多优点。最常受到人们赞誉的优点是组件更大程度地方便了代码重用。可以单独分发组件成品,并按照自己的意愿将其插进新的应用程序。而且这种类型的编程还使单元测试效率更高。

在测试时,可以使用特殊的“仿制品”组件将程序的各个组件链接起来,这些“仿制品”组件的类只是记录测试组件的行为。实质上,这些仿制品组件充当了与记录器(请参阅参考资料中的“进行记录器测试以正确调用方法”)类似的角色,但它是在组件这一层次上的。

被测试组件类似于笛卡尔的钵中之脑(brains in a vat);它们不能区分是与仿制品组件相连还是与真正的组件相连。例如,在先前的样本应用程序中,我们可以编写一个特殊的 test_app 复合,它将 app_view 单元与 test_toolkit 单元联系起来,如下所示:

compound test_app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: test_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view ot my_view;
}



test_toolkit 包会导出与 toolkit 相同签名的包,但这个包只包含一些仿制的对象,使 app_view 认为它是与真正的 toolkit 相连。这些仿制对象可以记录 app_view 的行为,因为可以在单元测试时检查这些记录。

将这个系统与 Java 包系统相比较会发现,在这个系统中,每个源文件必须显式地声明要导入的包。在 Java 包系统中,欺骗整个包链接到测试包的唯一方法是编辑所有的源文件,然后重新编译这些文件。这一过程不能真正实现自动化测试。

遗憾的是,因为 Jiazzi 不能处理反射或 JNI(这一点是有情可原的),而且还因为许多内置的 Java API 广泛地使用这些设施,所以不可能将现有的 API 包转换成 Jiazzi 组件。但是,如果可以转换成 Jiazzi,那么我们能够对程序执行更强大的测试。例如,在测试期间,可以将使用 Swing API 的程序连接到仿制的 Swing 组件,从而在没有真正尝试画图形对象的情况下,可以确保调用所有相应的 API。用类似的手段,我们可以测试与 java.io 包、Java3D、JDBC、RMI 以及任何其它 API 的交互,在这些 API 中,性能或功能的本质造成了客户机难以进行测试。

好了,这是一个美好的梦想,是吗?不过,即使反射和 JNI 不能让我们使现有的 API 成为一流的 Jiazzi 单元,但仍可能有折衷的办法。

虽然不能去耦现有 API 之间的连接,但可以去耦新单元与这些 API 的连接。实质上,可以将这些 API 与只导出类的特殊单元捆绑在一起;导入仍然与现有的 Java 包系统硬性地连接在一起。如果这些 API 是真正的单元,则这样做可以实现 99% 的功能;只有那些想对现有 API 构建第三方选择的程序员会对此不满意。

可喜的是,Jiazzi 将朝着这个方向或通过类似的途径变化着,这使我们可以和现有的 API 一起使用它。同时,它还为测试不使用反射或 JNI 的包提供了功能强大的机制。

去耦历史
最初开发 Jiazzi 的动机是为特性的可扩展性创建一种机制 — 将一个设计拆分成几个特性(每个单独的组件包含其中一个特性)。基于组件的编程主张,在以后的编程中,开发人员可以从组件供应商提供的成品组件上开发程序。具有通用签名的组件就可以象汽车零部件一样进行互换。

无论这一天是否会到来,即使没有繁荣的组件软件市场,基于组件编程仍有许多好处。尤其是,这种类型的编程使单元测试效率更高。

正如我们所展示的,用 Jiazzi 进行基于组件的编程提供了一种功能强大的测试程序组件的手段,并且它可以使用现有的 Java(或 Jython,或 JSR-14)源文件。我们可以从这一强大的工具中获益。这一工具及其它类似的工具是面向测试编程的必要工具。

下次,我将讨论 Jam(一种 Java 语言的扩展),它允许基于 mixin 的编程。就象 Jiazzi 提供了去耦包相关性的方法一样,mixin 提供了去耦类相关性的方法。正如您可能猜到的,minxin 给我们提供了测试程序的另一种强大的机制。




话题树型展开
人气 标题 作者 字数 发贴时间
9012 诊断 Java 代码:消除包间的耦合关联 palatum 7673 2003-03-07 14:20
6924 Re:诊断 Java 代码:消除包间的耦合关联 floater 179 2003-03-08 00:09
7005 OK,Here is the English Version palatum 18245 2003-03-09 18:23
8083 Re:诊断 Java 代码:消除包间的耦合关联 floater 59 2003-03-14 04:31

flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923