在数据格式验证中避免出现代码冗余方面提供一点帮助(助手类)
级别:中级
Brett McLaughlin(brett@oreilly.com)
作家兼编辑,O'Reilly and Associates
2003 年 4 月
设计良好的验证过程可以提高数据完整性、确保您的应用程序顺利运行并使未来的数据更改更易于处理。在这一期的 EJB 最佳实践中,Brett McLaughlin 扩充了上一篇技巧文章中讨论的验证技术,并改进了最初的概念。在上一篇专栏文章中,我们首先讨论了数据验证方面的问题,这是企业应用程序设计的基本组件之一。在快速检查了数据格式验证和特定于业务的验证这两种类型的数据验证之后,我们讨论了这两种验证逻辑在应用程序代码中最有利的布局。在结束时我们针对数据格式和特定于业务的验证,提出了很好且有效的解决方案,但我们没有真正解决您在这一编程领域可能会遇到的更复杂情况。
特别是对于数据格式验证代码,我们认为处理它的最佳方法是使它接近客户机,这样可以使处理开销保持最少。因为示例应用程序包含了一个业务委派类,所以我们在其中放置了验证逻辑。这个布局产生的问题是它会在您的代码中引入许多冗余。
变更管理和数据验证
在企业应用程序中更改数据格式是十分常见的情况。尽管 ISBN 和社会保障号的格式基本不变,但 IP 地址和 UPC 代码却不会一成不变。如果验证代码分散在整个应用程序中,那么您必须找到每个实例,并逐一更改它们,这个过程很繁琐,而且易于出错。另外,如果您漏改了一个或两个实例(这是人们常犯的错误),那么数据可能就最终被您自己的验证逻辑破坏!
如果您已经合并了验证逻辑,那么只要更改一处的格式,就可以使整个应用程序受益。
在本篇技巧文章中,我们将再次讨论数据格式验证,介绍数据验证助手类,它将让我们使验证过程接近客户机,同时不会引入任何不必要的代码。
合并验证逻辑
对跨多个业务委派中的多个业务方法使用相同的数据类型作为参数,这是十分常见的。例如,可以将一本书的 ISBN 传递给 Inventory 委派的搜索方法和 Payment 委派的采购方法。如果将验证逻辑与这两个业务委派(象上一篇技巧文章中的那样)联系在一起,那么最终这两个方法中都会有 ISBN 验证代码。
改进数据格式验证过程的第一步是将所有的验证逻辑都移至一个助手类中,其它方法可以按需从这个类上调用验证逻辑。这种合并不仅减少了代码冗余,而且当我们需要时,能够维护和更改应用程序的数据格式。
要实现这样的合并,我们将使用 Validator 类,它为我们需要验证的各种数据类型简单地提供了静态方法。清单 1 显示了这样一个类的框架:
注:可以从诸如业务委派等其它类上调用清单 1 中的简单方法,以按需执行验证逻辑。还要注意这些方法不返回布尔值。
清单 1. Validator 框架
package com.ibm.validation;
import java.util.Iterator;
import java.util.List;
public class Validator {
public static void validateISBN(String isbn)
throws InvalidDataException {
// Check the data type, and throw an error if
// needed
}
public static void validateIPAddress(String ipAddress)
throws InvalidDataException {
// Check data type
}
public static void validateUPC(String upc)
throws InvalidDataException {
// Check data type
}
public static void validateUPC(float upc)
throws InvalidDataException {
validateUPC(new String(upc));
}
public static void validateList(List list, Class class)
throws InvalidDataException {
for (Iterator i = list.iterator(); i.hasNext(); ) {
Object obj = i.next();
if !(obj instanceof class) {
throw new InvalidDataException("This list only " +
"accepts objects of type " +
class.getName());
}
}
}
}
清单 2 显示了一种有点杂乱的代码,这是使用布尔返回值产生的结果。
清单 2. 在 Validator 中使用布尔返回值
public boolean checkout(List books) throws ApplicationException {
if (Validator.validateList(books, Book.class)) {
try {
return library.checkout(books);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
}
public Book lookup(String isbn) throws ApplicationException {
if (Validator.validateISBN(isbn)) {
try {
return library.lookup(isbn);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
}
在更复杂的方法中,上述嵌套甚至会变得更为杂乱。通过允许抛出异常,我们已避免了这种杂乱所产生的额外复杂性,并使代码保持更“整洁”。另外,请注意 InvalidDataException 继承了 ApplicationException。这允许所有委派方法的特征符保持不变,因而也允许通过相同机制来抛出任何验证异常。我们不必向方法特征符添加其它 throws 子句,也不必向方法主体添加其它 try/catch 块。简而言之,它使代码保持整洁和简单,而不会到处充斥括号、应用程序异常、验证异常以及 if/then 和 try/catch 块。
使用 Validator
针对我们需要验证的特定数据类型,在将 Validator 置于适当位置并对它作设置之后,在应用程序中使用它就很容易,在我们的业务委派方法中尤其如此。清单 3 中重写了上一篇技巧文章中的委派,以使它能使用新的 Validator 类。
清单 3. 业务委派中的数据格式验证
package com.ibm.library;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.List;
import javax.ejb.CreateException;
import javax.naming.NamingException;
import com.ibm.validation.Validator;
import com.ibm.validation.InvalidDataException;
public class LibraryDelegate implements ILibrary {
private ILibrary library;
public LibraryDelegate() {
init();
}
public void init() {
// Look up and obtain our session bean
try {
LibraryHome libraryHome =
(LibraryHome)EJBHomeFactory.getInstance().lookup(
"java:comp/env/ejb/LibraryHome", LibraryHome.class);
library = libraryHome.create();
} catch (NamingException e) {
throw new RuntimeException(e);
} catch (CreateException e) {
throw new RuntimeException(e);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
// No validation required for accessor (getter) methods
public boolean checkout(Book book) throws ApplicationException {
// No validation required here; the object type
// takes care of it
try {
return library.checkout(book);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
public boolean checkout(List books) throws ApplicationException {
// Validate list
Validator.validateList(books, Book.class);
try {
return library.checkout(books);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
public Book lookup(String isbn) throws ApplicationException {
// Validate ISBN
Validator.validateISBN(isbn);
try {
return library.lookup(isbn);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
// And so on...
public void destroy() {
// In this case, do nothing
}
}
使用独立的 Validator 使代码更模块化和更易维护。另外,我们已将所有的验证逻辑移至一处,从而避免代码中出现冗余。其结果是一个更佳、更不易出错的应用程序。
在本系列的下一篇技巧文章中,我们将研究验证的另一方面:异常处理。那时网上见。