作为程序开发人员,我们经常需要对一个实际上程序性的系统应用面向对象的方法。商业分析家和管理人员描述这样的系统时通常不使用类层次和序列图,而是使用流程图和工作流图表。但是不论如何,使用面向对象的方法解决这些问题时会带来更多的灵活性。面向对象的设计模式提供了有用的结构和行为来描述这种顺序的处理,比如模版方法(Template Method)[GoF]和责任链(Chain of Responsibility)[GoF]。
由于模版方法(Template Method)依赖继承——子类必须继承定义了算法的父类——因此使用这个模式的软件表现出紧耦合而且缺少灵活性。又由于实现类添加自己的行为前必须扩展父类,沟每⑷嗽北幌拗朴诶嗖愦沃校佣拗屏顺绦蛏杓频牧榛钚浴ommons Chain使用配置文件定义算法,在程序运行时解析配置文件,从而很好的解决了这个问题。
现在来看一下Commons Chain是怎样工作的,我们从一个人造的例子开始:二手车销售员的商业流程。下面是销售流程的步骤:
- 得到用户信息
- 试车
- 谈判销售
- 安排财务
- 结束销售
现在假设使用模版方法(Template Method)造型这个流程。首先建立一个定义了算法的抽象类:
public abstract class SellVehicleTemplate { public void sellVehicle() { getCustomerInfo(); testDriveVehicle(); negotiateSale(); arrangeFinancing(); closeSale(); } public abstract void getCustomerInfo(); public abstract void testDriveVehicle(); public abstract void negotiateSale(); public abstract void arrangeFinancing(); public abstract void closeSale(); }
现在来看一下怎样用Commons Chain实现这个流程。首先,下载Commons Chain。你可以直接下载最新的zip或tar文件,也可以从CVS或者SubVersion源码库检出Commons Chain模块得到最新的代码。解压缩打包文件,将commons-chain.jar放入你的classpath中。
使用Commons Chain实现这个商业流程,必须将流程中的每一步写成一个类,这个类需要有一个public的方法execute()。这和传统的命令模式(Command pattern)实现相同。下面简单实现了“得到用户信息”:
package com.jadecove.chain.sample; import org.apache.commons.chain.Command; import org.apache.commons.chain.Context; public class GetCustomerInfo implements Command { public boolean execute(Context ctx) throws Exception System.out.println("Get customer info"); ctx.put("customerName","George Burdell"); return false; } }
由于只是演示,这个类并没有做很多工作。这里将用户名放入了Context对象ctx中。这个Context对象连接了各个命令。暂时先将这个对象想象成根据关键字存取值的哈希表。所有后来的命令可以通过它访问刚才放入的用户名。TestDriveVehicle,NegotiateSale和 ArrangeFinancing命令的实现只是简单的打印了将执行什么操作。
CloseSale从Context对象中取出GetCustomerInfo放入的用户名,并将其打印。
package com.jadecove.chain.sample; import org.apache.commons.chain.Command; import org.apache.commons.chain.Context; public class CloseSale implements Command { public boolean execute(Context ctx) throws Exception { System.out.println("Congratulations " + ctx.get("customerName") +", you bought a new car!"); return false; } }
现在你可以将这个流程定义成一个序列(或者说“命令链”)。
package com.jadecove.chain.sample; import org.apache.commons.chain.impl.ChainBase; import org.apache.commons.chain.Command; import org.apache.commons.chain.Context; import org.apache.commons.chain.impl.ContextBase; public class SellVehicleChain extends ChainBase { public SellVehicleChain() { super(); addCommand(new GetCustomerInfo()); addCommand(new TestDriveVehicle()); addCommand(new NegotiateSale()); addCommand(new ArrangeFinancing()); addCommand(new CloseSale()); } public static void main(String[] args) throws Exception { Command process = new SellVehicleChain(); Context ctx = new ContextBase(); process.execute(ctx); } }
运行这个类将会输出以下结果:
public boolean execute(Context ctx) throws Exception { SellVehicleContext myCtx = (SellVehicleContext) ctx; System.out.println("Congratulations " + myCtx.getCustomerName() + ", you bought a new car!"); return false; }
<catalog> <chain name="sell-vehicle"> <command id="GetCustomerInfo" className="com.jadecove.chain.sample.GetCustomerInfo"/> <command id="TestDriveVehicle" className="com.jadecove.chain.sample.TestDriveVehicle"/> <command id="NegotiateSale" className="com.jadecove.chain.sample.NegotiateSale"/> <command id="ArrangeFinancing" className="com.jadecove.chain.sample.ArrangeFinancing"/> <command id="CloseSale" className="com.jadecove.chain.sample.CloseSale"/> </chain> </catalog>
Chain的配置文件可以包含多个链定义,这些链定义可以集合进不同的编目中。在这个例子中,链定义在一个默认的编目中定义。事实上,你可以在这个文件中定义多个名字的编目,每个编目可拥有自己的链组。
package com.jadecove.chain.sample; import org.apache.commons.chain.Catalog; import org.apache.commons.chain.Command; import org.apache.commons.chain.Context; import org.apache.commons.chain.config.ConfigParser; import org.apache.commons.chain.impl.CatalogFactoryBase; public class CatalogLoader { private static final String CONFIG_FILE = "/com/jadecove/chain/sample/chain-config.xml"; private ConfigParser parser; private Catalog catalog; public CatalogLoader() { parser = new ConfigParser(); } public Catalog getCatalog() throws Exception { if (catalog == null) { parser.parse(this.getClass().getResource(CONFIG_FILE)); } catalog = CatalogFactoryBase.getInstance().getCatalog(); return catalog; } public static void main(String[] args) throws Exception { CatalogLoader loader = new CatalogLoader(); Catalog sampleCatalog = loader.getCatalog(); Command command = sampleCatalog.getCommand("sell-vehicle"); Context ctx = new SellVehicleContext(); command.execute(ctx); } }
<catalog name="auto-sales"> <chain name="sell-vehicle"> <command id="GetCustomerInfo" className="com.jadecove.chain.sample.GetCustomerInfo"/> <command id="TestDriveVehicle" className="com.jadecove.chain.sample.TestDriveVehicle"/> <command id="NegotiateSale" className="com.jadecove.chain.sample.NegotiateSale"/> <command className="org.apache.commons.chain.generic.LookupCommand" catalogName="auto-sales" name="arrange-financing" optional="true"/> <command id="CloseSale" className="com.jadecove.chain.sample.CloseSale"/> </chain> <chain name="arrange-financing"> <command id="ArrangeFinancing" className="com.jadecove.chain.sample.ArrangeFinancing"/> </chain> </catalog>
- 命令的execute方法返回true
- 运行到了链的尽头
- 命令抛出异常
只要Filter的execute方法被调用,不论链的执行过程中是否抛出错误,Commons Chain都将保证Filter的postprocess方法被调用。和servlet的过滤器(filter)相同,Commons Chain的Filter按它们在链中的顺序依次执行。同样,Filter的postprocess方法按倒序执行。你可以使用这个特性实现自己的错误处理。下面是一个用于处理我们例子中的错误的Filter:
package com.jadecove.chain.sample; import org.apache.commons.chain.Context; import org.apache.commons.chain.Filter; public class SellVehicleExceptionHandler implements Filter { public boolean execute(Context context) throws Exception { System.out.println("Filter.execute() called."); return false; } public boolean postprocess(Context context, Exception exception) { if (exception == null) return false; System.out.println("Exception " + exception.getMessage() + " occurred."); return true; } }
结合了过滤器(filter)和子链技术后,你就可以造型很复杂的工作流程。