UML软件工程组织

AOP 解决紧密耦合的难题(二)
作者:Andrew Glover 来 源: IBM DW
设计挑战

除了通过不同渠道发送各种格式的消息的能力之外,供应商通过一个已给的接口,提供一个钩子(hook)来允许进行收件人地址验证。供应商的文档表明,实现这个接口的任何对象都将遵循一个预定义的生命周期,它 validateAddress() 方法将被调用,并正确地处理相应的结果行为。如果 validateAddress() 返回 false,供应商的通讯系统将不再试图进行相应的通讯。清单 7 显示了供应商的 validateAddress() 接口。

清单 7. Sendable 接口的地址验证

package com.acme.validate;

public interface Validatable {
   
   boolean validateAddress();

}


运用基本的面向对象设计原理,体系结构团队决定修改 Sendable 接口来扩展供应商的 Validatable 接口。但是,这一决定将导致对供应商代码的直接依赖和耦合。如果开发团队接下来决定用另一个供应商的工具,就不得不重构代码库,以删除 Sendable 接口中的 extends 语句和对象层次中已实现的行为。

一个更优雅且根本上更灵活的解决方案就是使用静态横切来对预期对象添加行为。

静态横切带来援助

运用面向方面的原理,该团队可以创建一个方面来声明 Email 对象实现供应商的 Validatable 接口;此外,在方面中,体系结构团队将预期的行为编码在 ValidataAddress() 方法中。因为 Email 对象中既不包含任何供应商包的导入,也没有定义 validateAddress() 方法,这样代码就更好地消除了耦合。Email 对象根本没意识到它是 validatabl e 类型的对象!清单 8 显示了结果方面,其中 Email 静态地得到增强以实现供应商的 Validatable 接口。

清单 8. Email 可验证方面

import com.acme.validate.Validatable;

public aspect EmailValidateAspect {

   declare parents: Email implements Validatable;

   public boolean Email.validateAddress(){
     if(this.getToAddress() != null){
	   return true;
     }else{
	   return false;
     }
   }
}


测试一下!

您可以利用 JUnit 来证明 EmailValidateAspect 确实改变了 Email 对象。在一个 JUnit 测试套件中, Email 对象可以用缺省值创建,然后一系列测试案例可以检验 Email 的确是 Validatable 的一个实例;此外,可以通过一个测试案例来断言,如果 toAddress 为 null,对 validateAddress() 的调用将返回 false。另外,还可以用另一个测试案例来检验:一个非 null 的 toAddress 将导致 validateAddress() 返回 true。

1-2-3,用 JUnit 进行测试

您可以首先创建这样一个结构,它构造带有简单值的 Email 对象实例。注意在清单 9 中,该实例确实有一个有效(意为非 nul)的toAddress 值。

清单 9. JUnit setUp()

import com.acme.validate.Validatable;

public class EmailTest extends TestCase {
 private Email email;

 protected void setUp() throws Exception {
   //set up an email instance
   this.email = new Email();
   this.email.setBody("body");
   this.email.setFromAddress("dev@dev.com");
   this.email.setSubject("validate me");
   this.email.setToAddress("ag@ag.com");
 }

 protected void tearDown() throws Exception {
   this.email = null;
 }

//EmailTest continued...
}


对于一个有效的 Email 对象,estEmailValidateInstanceof() 确保实例是 Validatable 类型的,如清单 10 所示。

清单 10. JUnit 校验实例

public void testEmailValidateInstanceof() throws Exception{
  
  TestCase.assertEquals("Email object should be of type Validatable", 
    true, this.email instanceof Validatable);		 
}


如清单 11 所示,下一个测试案例故意把 toAddress 字段设置为 null ,然后检验 validateAddress() 将返回 false。

清单 11. JUnit null toAddress 检查

public void testEmailAddressValidateNull() throws Exception{
   
   //force a false
   this.email.setToAddress(null);

   Validatable validtr = (Validatable)this.email;	
   
   TestCase.assertEquals("validateAddress should return false", 
      false, validtr.validateAddress());		
}


最后一步是出于稳健的考虑:testEmailAddressValidateTrue() 测试案例用 Email 实例的初始值调用 validateAddress(),即 toAddress 域的值为 ag@ag.com。

清单 12. JUnit 非 null toAddress 检查

public void testEmailAddressValidateTrue() throws Exception{		
   
   Validatable validtr = (Validatable)this.email;	

   TestCase.assertEquals("validateAddress should return true", 
      true, validtr.validateAddress());		
}


重构该例子

体系结构团队想方设法使用 Sendable 接口来抽象通讯实现;然而,他们的第一次尝试就好像忽略了这个接口。从静态横切民Email 对象中吸取教训之后,他们通过把契约行为在对象层次中提升到 Sendable 基接口,从而进一步精炼了其策略。

新的方面创建了一个用供应商的 Validatable 接口来对 Sendable 接口进行的扩展。此外,他们在方面中创建了已实现的行为。这次,validateAddress() 方法是为另一个通信对象定义的:Fax,如清单 13 所示。

清单 13. 一个更好的方面

import com.acme.validate.Validatable;

public aspect SendableValidateAspect {
	
   declare parents: Sendable extends Validatable;

   public boolean Email.validateAddress(){
     
     if(this.getToAddress() != null){
       return true;
     }else{
       return false;
     }
   }

   public boolean Fax.validateAddress(){
 
     if(this.getToAddress() != null 
	    && this.getToAddress().length() >= 11){

        return true;
     }else{
        return false;
     }
  }
}


永远不要停止重构

您可能注意到清单 13 中的方面稍有不足,因为所有 Sendable 的实现者各自都有在同一个方面中定义的 validateAddress() 方法。这很容易导致代码膨胀。另外,如果不谨慎处理,改变一个接口的静态结构将出现很多不希望的副作用:必须找到目标接口的所有实现者。因此,这里的教训很简单:永远不要停止重构。

结束语

虽然这里的 API 例子是人为的,但它有望证明在企业体系结构中应用静态横切是多么简单。静态横切应用于本文描述的这一类场景(它可以在其中用于非强制性地改变对象的行为甚至定义)尤为有效,不过它还有其他很多用处。譬如,您可以在开发时用静态横切来“EJB 化”POJO(传统的普通 Java 对象);或者您可以在业务对象中用它来利用诸如 Hibernate 的持久框架的生命周期接口。

静态横切为很多影响企业代码有效性的轻微缺陷提供了优雅的解决方案。通过本文,您已经学习了该技术的基础知识和它最基本的应用之一。

关于作者

Andrew Glover 是 Vanward Technologies 的首席技术官(CTO),该公司是一家位于华盛顿特区市中心的公司,专业从事自动测试框架的构建,以减少软件中的 bug 数量,降低集成和测试次数,并提高整体代码稳定性。

版权所有:UML软件工程组织