Session Facade 的规则和模式(2) Session Facade 的重要规则
那么我们该如何应用这些关于针对会话的 Facade 的规则呢?这对我们的 EJB 设计又意味着什么呢?我在设计Session Facade 时遵循三条基本原则:
它们自己不做实际工作;它们委派其它对象做实际工作。这意味着Session facade 中的每个方法都应该很小(异常处理逻辑不计算在内,代码应为五行或更少)。 它们提供简单的接口。这意味着 facade 方法的数量应相对较少(每个Session bean 中仅有约 24 个)。 它们是底层系统的客户端接口。它们应该把特定于子系统的信息封装起来,并且不应该在不必要的情况下公开它。 那么它的工作机制呢?您还能代理别的哪些类型的对象呢?这又会给您的设计带来什么好处呢?在我的一篇早期论文和 [Brown 2001] 这本书中,我已论述了其中一些问题,在那里可以找到一些详细信息。但,总的来说,在我的多数 EJB 设计中我通常会找到以下四类对象:
值对象是包含了客户机所请求的数据的、可序列化的 Java bean。它包含Entity bean 和其他数据源所包含的数据的一个子集。它是Session EJB 方法的返回类型。[EJB 2.0] 和 [Sun 2001] 都描述了值对象和值对象的用途。请注意 [Fowler 2001] 称其为“数据传输对象”( Data Transfer Objects ),[Brown 1999] 也使用这个名称。我个人觉得数据传输对象是描述性更好的术语,但不幸的是,Sun 的术语似乎更通用。 对象制造厂 (Factory) [Brown 1999] [Brown 2000] 负责构建值对象。它能完成辨别不同的数据源、创建值对象的实例、填充值对象的实例等等工作。每个 factory 类 都可以从多个数据源中检索数据或更新其中的数据。在您的对象模型中,每个“根”对象都应该有一个 factory 类。(根对象是那些“包含”其它对象的对象。)从某种意义上说,对象 Factory 类在 JDBC 或持久的 Entity bean 子系统上担当 Facade,实现 [Gamma] 中提到的分层原则。 Entity EJB 应该是标准的、企业全局范围内可用的“数据源”。Entity bean 不应包含特定于应用程序的域逻辑,也不应限制为只能在单一应用程序内工作。请注意Entity bean 是可选的,它不是这种体系结构中必需的部分;Factory 可能像 JMS 队列或 JDBC 连接那样简单地直接从数据源获取数据。 Action 对象是Session bean 可能调用的唯一对商业业务进行处理的对象。Action 对象只处理与简单的创建、读取、更新或删除数据无关的商业流程。和对象 Factory 一样,Action 对象也充当内层 Facade。 一个 EJB 对象示例
描述类似这样的模式遇到的一个问题是,能够使用这种模式的示例都太大,以至于无法包含在模式自身的描述中。尽管如此,我还是要尝试举出如下示例(它显然很简单)来说明一下这些对象看起来是什么样子。
假设我们正在为银行构建一个 ATM 系统。这是最老掉牙的 OO 设计问题之一,当然其它很多书籍和论文已经讨论过它,但它确实有足够符合我们要求的有趣特点。通过分析,我们发现了两种 EJB。
从 ATM 到银行的连接表示为Session bean。该 bean 上有一些方法负责处理您通过 ATM 可以完成的交易 ? 存款、取款以及帐户间的资金转移。 帐户表示为Entity bean(我们的示例采用 CMP,但它在我们的示例中实际上并没什么影响)表示。它有返回帐户余额、对帐户进行借贷处理的方法。 ATM Session bean 的远程接口如下:
package com.ibm.bankexample.ejbs;
import com.ibm.bankexample.domain.*;
/** * This is the Enterprise Java Bean Remote Interface * for the ATM example. */ public interface ATM extends javax.ejb.EJBObject {
void deposit(java.lang.String accountNumber, double amount) throws java.rmi.RemoteException, com.ibm.bankexample.domain.FactoryException;
java.util.Vector getAccounts(java.lang.String userid) throws java.rmi.RemoteException, com.ibm.bankexample.domain.FactoryException;
void transfer(java.lang.String fromAccount, java.lang.String toAccount, double amount) throws java.rmi.RemoteException, com.ibm.bankexample.domain.InsufficientFundsException, com.ibm.bankexample.domain.FactoryException;
void withdraw(java.lang.String accountNumber, double amount) throws java.rmi.RemoteException, com.ibm.bankexample.domain.InsufficientFundsException, com.ibm.bankexample.domain.FactoryException;
} 同样地,帐户 EJB 的远程接口如下:
package com.ibm.bankexample.ejbs;
/** * This is the Enterprise Java Bean Remote Interface * for the Account Entity EJB. */ public interface Account extends javax.ejb.EJBObject {
void deposit(double amount) throws java.rmi.RemoteException;
java.lang.String getAccountNumber() throws java.rmi.RemoteException;
double getBalance() throws java.rmi.RemoteException;
java.lang.String getUserid() throws java.rmi.RemoteException;
void setBalance(double newValue) throws java.rmi.RemoteException;
void setUserid(java.lang.String newUserid) throws java.rmi.RemoteException;
void withdraw(double amount) throws java.rmi.RemoteException;
} 现在,我们还发现有另外两种对象类型对我们的系统是有用的。第一种是描述显示在 ATM 机上的帐户信息的值对象。这个类看起来如下所示:
public class AccountValue implements java.io.Serializable { private java.lang.String accountNumber; private double balance; } 当然,AccountValue 类也有作为属性的 getter 和 setter 的方法,但我们暂时不考虑它们。
现在,我们基本上有了足够的信息来理解 ATM EJB 的 getAccounts() 方法的实现。这个方法的实现如下:
public java.util.Vector getAccounts(String userid) throws FactoryException { AccountFactory fact = new AccountFactory(); Vector result = fact.getAccounts(userid); return result; } 这个方法展示了Session Facade EJB 的方法的标准模式。它找到合适的帮助对象(Action 或 Factory,在本例中是 Factory),调用帮助对象上的业务方法,然后返回结果。
如这个方法所指出的,我们需要一个 AccountFactory 类来从 Accounts 构建 AccountValues。这个类的类定义如下:
public class AccountFactory { private static AccountHome accountHome = null; } AccountFactory 的 getAccounts(userid) 方法的实现如下:
public java.util.Vector getAccounts(String userid) throws FactoryException { try { Vector vect = new Vector(); AccountHome home = getAccountHome(); Enumeration accountRefs = home.findByUserid(userid); while (accountRefs.hasMoreElements()) { Account acc = (Account) accountRefs.nextElement(); AccountValue valueObject = new AccountValue(); valueObject.setAccountNumber( acc.getAccountNumber()); valueObject.setBalance(acc.getBalance()); vect.addElement(valueObject); } return vect; } catch (Exception e) { throw new FactoryException( "Cannot generate accounts due to wrapped exception " + e); } } 这个方法使用一个高速缓存的 AccountHome 实例,它是从以下方法中获取的:
private AccountHome getAccountHome() { if (accountHome == null) { try { java.lang.Object homeObject = getInitialContext().lookup( "com/ibm/bankexample/ejbs/Account"); accountHome = (AccountHome) javax.rmi.PortableRemoteObject.narrow( (org.omg.CORBA.Object) homeObject, AccountHome.class); } catch (Exception e) { // Error getting the home interface System.out.println( "Exception " + e + " in createTimeSheetHome()"); } } return accountHome;
} 正如 [Brown 2001] 和 [Gunther 2000] 所描述的那样,在 WebSphere® 中,高速缓存 EJB home 是一个极好的习惯,因为获取 JNDI InitialContext 和从 InitialContext 获取 EJB Home 需要一段时间。
既然您已经看到了Session、Entity和 Factory 如何组合在一起,那我们就来看一个 Action 类的示例。在本例中,我们有一个处理从一个帐户到另一个帐户的资金转移的 Transfer 对象。Transfer 由 ATM EJB 中的 transfer() 方法的实现中创建,该方法的实现如下:
public void transfer(String fromAccount, String toAccount, double amount) throws InsufficientFundsException, FactoryException { Transfer trans = new Transfer(); trans.transfer(fromAccount, toAccount, amount); } 请再次注意同样的流程。不过,这个方法不必从 Action 对象返回值。Transfer 类的定义如下:
public class Transfer { private static AccountHome accountHome; } transfer() 方法的实现如下:
public void transfer(String fromAccount, String toAccount, double amount) throws InsufficientFundsException, FactoryException { try { Account from = getAccountHome().findByPrimaryKey( new AccountKey(fromAccount)); Account to = getAccountHome().findByPrimaryKey( new AccountKey(toAccount)); if (from.getBalance() < amount) throw new InsufficientFundsException(); to.deposit(amount); from.withdraw(amount); } catch (Exception e) { throw new FactoryException( "cannot perform transfer. Nested exception is " + e); } } 您已经看到,Transfer 对象中的 transfer() 方法处理以下细节:定位两个 Account 实体,确保“From”帐户有足够的余额,把转移金额存入“To”帐户,从“From”帐户中提出转移金额。同样地,您可以看到 Action 对象的其它方法可以实现您系统中的其它业务规则。 (未完待续)
|