创建静态横切
创建静态横切的语法和动态横切有很大的不同,即没有切入点和通知。给定一个对象(比如下面定义的 Foo
),静态横切使得创建新方法、添加附加的构造函数,甚至改变继承层次都变得十分简单。我们将用一个例子来更好地展示静态横切是怎样在一个现有的类中实现的。清单 1 显示了一个简单的没有方面的 Foo
。
清单 1. 没有方面的 Foo
public class Foo { public Foo() { super(); } } |
如清单 2 所示,在一个对象中添加一个新的方法和在一个方面中定义一个方法是同样简单的。
清单 2. 向 Foo 添加一个新方法
public aspect FooBar { void Foo.bar() { System.out.println("in Foo.bar()"); } } |
构造函数略有区别的地方在于 new
关键字是必需的,如清单 3 所示。
清单 3. 向 Foo 添加一个新的构造函数
public aspect FooNew { public Foo.new(String parm1){ super(); System.out.println("in Foo(string parm1)"); } } |
改变对象的继承层次需要一个 declare parents
标签。比如,为了变成多线程的, Foo
将需要实现 Runnable
,或者扩展 Thread
。清单 4 显示了用 declare parents
标签来改变 Foo
的继承层次。
清单 4. 改变 Foo 的继承层次
public aspect FooRunnable { declare parents: Foo implements Runnable; public void Foo.run() { System.out.println("in Foo.run()"); } } |
现在,您可能开始独自设想静态横切的含意了,特别是在与创建松散耦合、高度可扩展的系统有关时。在下面的几小节中,我将带您看一个一个真实的设计和实现场景,以展示使用静态横切来扩展您的企业应用的灵活性是多么容易。
实现场景
企业系统经常被设计来利用第三方的产品和库。为了不把整个结构和所需产品耦合在一起,通常在设计来与外部厂商代码交互的应用中包括进一个抽象层。在插入其他厂商的实现乃至自主开发的代码时,这个抽象层在对系统的一致性产生最小破坏的情况下,为该体系结构提供了高度的灵活性。
在这个实现场景中,设想系统在某一操作发生后,通过各不相同的通讯渠道通知客户。这个例子系统使用了一个 Email
对象来代表直接电子邮件通讯的一个实例。如清单 5 所示, Email
对象包含了诸如发件人地址、收件人地址、主题栏和消息正文等属性。
清单 5. 例子 Email 对象
public class Email implements Sendable { private String body; private String toAddress; private String fromAddress; private String subject; public String getBody() { return body; } public String getFromAddress() { return fromAddress; } public String getSubject() { return subject; } public String getToAddress() { return toAddress; } public void setBody(String string) { body = string; } public void setFromAddress(String string) { fromAddress = string; } public void setSubject(String string) { subject = string; } public void setToAddress(String string) { toAddress = string; } } |
整合第三方代码
除了建立一个发送电子邮件、传真、短消息等的自定义通讯系统之外,体系结构团队决定整合进一个供应商的产品,该产品能遵循特定的规则,发送基于任意对象的消息。该产品非常灵活,并且通过 XML 提供了一个映射机制,允许将自定义客户端对象映射到与厂商的特定渠道实现。该厂商的系统严重依赖于这一映射文件和 Java 平台的反射能力来与普通 Java 对象协同工作。为了体现灵活性,体系结构团队建立了一个 Sendable
接口模型,如清单 6 所示。
清单 6. 例子 Sendable 接口
public interface Sendable { String getBody(); String getToAddress(); } |
图 1 显示了 Email
对象和 Sendable
接口的类图。
图 1. Email 和 Sendable 的类图
设计挑战
除了通过不同渠道发送各种格式的消息的能力之外,供应商通过一个已给的接口,提供一个钩子(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; } } } 本文来自爪式千斤顶:http://www.qizejx.com |