用AspectJ实现外观模式
【IT168 技术文档】本文介绍使用AspectJ实现设计模式之装饰模式,文章利用一个打印发票的例子说明如何使用AspectJ来实现装饰模式。
示例说明
装饰模式是大家都很熟悉的一个模式,最典型的例子就是Java的I/O库,它的设计完全按照装饰模式。我们通常在客户端使用new操作符来对一个类进行包装,例如下述代码
DataOutputStream out=new DataOutputStream(new FileOutputStream(“1.txt”));
它使用DataOutputStream来包装FileOutputStream,所以可以使用前者的方法来对文件流进行写入。而采用AspectJ实现的装饰模式中,在客户端我们将不在看见这样的new操作,而是直接将装饰行为织入方法执行内部,在方法内部增加额外的操作。现将<<Java与模式>>一书中有打印发票的例子改用AspectJ实现,我们看看到底AspectJ是如何将额外操作织入方法体中的。系统UML图如下所式
源代码
抽象方面OrderDecorator.java
abstract aspect OrderDecorator ...{
![]()
declare parents : SalesOrder extends Order;
![]()
public void SalesOrder.print()...{
![]()
super.print();
![]()
}
![]()
protected pointcut print(Order order) : target(order) && call(public void print());
![]()
}
![]()
抽象方面利用类型间声明为SalesOrder类声明了父类,并实现了方法print()。同时还定义了一个切点print(Order order),它将捕捉Order类print方法调用。
具体方面HeaderDecorator.java
public aspect HeaderDecorator extends OrderDecorator...{
![]()
void around(Order order) : print(order)...{
![]()
printHeader(order);
![]()
proceed(order);
![]()
}
![]()
private void printHeader(Order order)...{
![]()
System.out.println("\t***\tINVOICE\t***");
![]()
System.out.println("XYZ Incorporated\nDate of Sale:");
![]()
System.out.println(order.getSalesDate());
![]()
System.out.println("======================================");
![]()
System.out.println("Item\t\tUnits\tUnit Price\tSubtotal");
![]()
}
![]()
}
![]()
HeaderDecorator方面实现发票头打印功能,它定义了通知around截获切点print所捕捉的方法调用,然后先打印发票头再利用proceed()方法执行所捕捉到的方法的原有逻辑。
具体方面FooterDecorator.java
public aspect FooterDecorator extends OrderDecorator{
declare precedence : FooterDecorator,HeaderDecorator; //声明优先顺序
void around(Order order) : print(order){
proceed(order);
printFooter(order);
}
private void printFooter(Order order){
System.out.println("=====================================");
System.out.println("Total\t\t\t\t"+order.formatCurrency(order.getGrandTotal()));
}
}
FooterDecorator方面实现发票注脚打印功能,它定义了通知around截获切点print所捕捉的方法调用,然后先利用proceed()方法执行所捕捉到的方法的原有逻辑再打印注脚信息。还有最重要的一点是它的precedence声明,这个声明决定了FooterDecorator方面和HeaderDecorator方面捕捉同一个方法调用时同一通知的执行顺序。方面中的声明说明HeaderDecorator比FooterDecorator的优先级别高,其通知较先执行。
抽象定单类Order.java
import java.util.*;
![]()
import java.text.NumberFormat;
![]()
abstract public class Order ...{
![]()
private OrderLine InkOrderLine;
![]()
protected Collection items=new ArrayList();
![]()
protected String customerName;
![]()
protected Date salesDate;
![]()
![]()
![]()
public void print()...{
![]()
Iterator it=items.iterator();
![]()
while(it.hasNext())...{
![]()
OrderLine item=(OrderLine)it.next();
![]()
item.printLine();
![]()
}
![]()
}
![]()
public String getCustomerName()...{
![]()
return customerName;
![]()
}
![]()
public void setCustomerName(String customerName)...{
![]()
this.customerName=customerName;
![]()
}
![]()
public Date getSalesDate()...{
![]()
return salesDate;
![]()
}
![]()
public void setSalesDate(Date salesDate)...{
![]()
this.salesDate=salesDate;
![]()
}
![]()
public void addItem(OrderLine item)...{
![]()
items.add(item);
![]()
}
![]()
public void removeItem(OrderLine item)...{
![]()
items.remove(item);
![]()
}
![]()
public double getGrandTotal()...{
![]()
double amount=0.0D;
![]()
Iterator it=items.iterator();
![]()
while(it.hasNext())...{
![]()
OrderLine item=(OrderLine)it.next();
![]()
amount+=item.getSubTotal();
![]()
}
![]()
return amount;
![]()
}
![]()
public String formatCurrency(double amount)...{
![]()
return NumberFormat.getCurrencyInstance().format(amount);
![]()
}
![]()
}
![]()
![]()
![]()
定单条目类OrderLine.java
![]()
import java.text.NumberFormat;
![]()
public class OrderLine ...{
![]()
private String itemName;
![]()
private int units;
![]()
private double unitPrice;
![]()
![]()
![]()
public String getItemName()...{
![]()
return itemName;
![]()
}
![]()
public void setItemName(String itemName)...{
![]()
this.itemName=itemName;
![]()
}
![]()
public int getUnits()...{
![]()
return units;
![]()
}
![]()
public void setUnits(int units)...{
![]()
this.units=units;
![]()
}
![]()
public double getUnitPrice()...{
![]()
return unitPrice;
![]()
}
![]()
public void setUnitPrice(double unitPrice)...{
![]()
this.unitPrice=unitPrice;
![]()
}
![]()
public void printLine()...{
![]()
System.out.println(itemName+"\t"+units+"\t"
![]()
+formatCurrency(unitPrice)+"\t"
![]()
+formatCurrency(getSubTotal()));
![]()
}
![]()
public double getSubTotal()...{
![]()
return units*unitPrice;
![]()
}
![]()
private String formatCurrency(double amount)...{
![]()
return NumberFormat.getCurrencyInstance().format(amount);
![]()
}
![]()
}
![]()
具体销售定单类SalesOrder.java
![]()
public class SalesOrder ...{}
![]()
![]()
![]()
客户端演示Demo.java
![]()
public class Demo ...{
![]()
private static Order order;
![]()
public static void main(String [] args)...{
![]()
order=new SalesOrder();
![]()
order.setSalesDate(new java.util.Date());
![]()
order.setCustomerName("starchu1981");
![]()
OrderLine item1=new OrderLine();
![]()
item1.setItemName("Fire Wheel Tire");
![]()
item1.setUnitPrice(154.23);
![]()
item1.setUnits(4);
![]()
order.addItem(item1);
![]()
OrderLine item2=new OrderLine();
![]()
item2.setItemName("Front Fender");
![]()
item2.setUnitPrice(300.45);
![]()
item2.setUnits(1);
![]()
order.addItem(item2);
![]()
order.print(); //这里与Java版本的客户端代码不同,稍后解释。
![]()
}
![]()
}
![]()
结果分析
Demo输出的结果如下
*** INVOICE ***
XYZ Incorporated
Date of Sale:
Thu Jul 24 11:45:10 CST 2003
=================================
Item Units Unit Price Subtotal
Fire Wheel Tire 4 ¥154.23 ¥616.92
Front Fender 1 ¥300.45 ¥300.45
=================================
Total ¥917.37
例子输出与我们期望的结果是一致的。特别值得注意的是代码的最后一行,通常在Java中可能会有下面的代码
Order order=new HeaderDecorator(new FooterDecorator());
order.print();
而使用AspectJ构造的装饰模式在客户端完全将装饰现象消除,可以直接使用方法,而不需要向在Java中那样提供具体装饰类的构造行为,这使得客户端代码更加透明,客户可以完全不知道装饰类的存在。当然这样做也有一个缺点,就是客户端无法自己控制所有的装饰行为,例如如果客户无法撤消打印注脚行的行为。
