/** * A report PRinter is used to print a report. * * @version 1.0 9/9/2003 * @author Bill */ public class ReportPrinter { /** * ConstrUCts a <code>ReportPrinter</code> instance. */ public ReportPrinter() { // do something... }
/** * Prints a printable. * * @param printable the specified printable object */ public void print(Printable printable) { Graphics g = getGraphics(); g.setFont(getReportFont(printable.getFont());
printable.print(g); }
/** * Returns the corresponding report font of a java font. * * @param javaFont the specified java font * @return the corresponding report font */ private Font getReportFont(font javaFont) { Font reportFont = fontMap.get(javaFont);
/** * Loads the corresponding report font of a java font. * * @param javaFont the specified java font * @param the corresponding report font */ protected static Font loadFont(Font javaFont) { Font reportFont = null;
// do something...
return reportFont; }
/** * The font map(java font->report font). */ private static HashMap fontMap = new HashMap(); }
Fragment 1中,由于装载一个java font所对应的report font开销较大,使用了缓存技术来避免这种开销。这是一种常见的提高性能的方式,而且在一般情况下运行良好。但是Fragment 1的设计与实现可能是不完备的,因为极有可能一个java font所对应的report font在系统启动之后发生变化,在这种变化发生之后,只有重启软件系统才能装载之,这经常是最终用户的抱怨之一。更可怕的是,类似的这种脏数据的存在还可能带来其它严重的、无法想象的后果。
如何避免使用缓存技术所带来的脏数据问题呢?
在设计、实现和测试时,应该清楚定义缓存数据的更新: i. 不考虑缓存数据的更新,重启软件系统是一种必要的方式; ii. 不考虑缓存数据的更新,缓存数据不可能成为脏数据(但在软件系统中,往往“不可能”会在一次又一次的重构之后变为“可能”); iii. 考虑缓存数据的更新,当源数据变化时,实时更新缓存数据。
Fragment 2. Singleton模式的脏数据问题
/** * A storage usage handler is used to query the storage usage of users. * * @version 1.0 9/9/2003 * @author Bill */ public class StorageUsageHandler { /** * Returns a <code>StorageUsageHandler</code> instance. * * @return the single <code>StorageUsageHandler</code> instance */ public static StorageUsageHandler getStorageUsageHandler() { if(handler == null) { handler = new StorageUsageHandler(); }
/** * Returns the storage sizes of all the users. * * @return the storage sizes */ public long[] getSizes() { long sizes[] = new long[users.size()];
for(int i = 0; i < users.size(); i++) { sizes[i] = getOneSize(users.get(i)); } }
/** * Returns the storage size of a user. * * @param user the specified user * @return the storage size */ protected long getSize(User user) { // do something...
return 0; }
/** * The <code>StorageUsageHandler</code> singleton. */ private static StorageUsageHandler handler;
/** * The users. */ private List users; }
您看出了问题所在吗?
Fragment 2中,由于没有必要次次实例化StorageUsageHandler而带来不必要的开销,采用了Singleton模式以保证StorageUsageHandler只被实例化一次。
对于Singleton类的类成员: i. 对于与Singleton类外部无依靠关系的类成员,不存在这种问题; ii. 对于依靠于Singleton类外部的类成员,且该类成员不存在更新机制,最好是将其去掉,需要时从Singleton类外部直接获取;假如这种办法不可行,应提供机制以确保在使用该类成员之前,该类成员已经被更新过。
Fragment 3. 类使用的脏数据问题
/** * A storage usage handler is used to query the storage usage of users. * * @version 1.0 9/9/2003 * @author Bill */ public class StorageUsageHandler implements AdminHandler { /** * Constructs a <code>StorageUsageHandler</code> instance. */ private StorageUsageHandler() { users = Context.getAllUsers(); }
/** * Returns the storage sizes of all users. * * @return the storage sizes */ public long[] getSizes() { long sizes[] = new long[users.size()];
for(int i = 0; i < users.size(); i++) { sizes[i] = getOneSize(users.get(i)); } }
/** * Returns the storage size of a user. * * @param user the specified user * @return the storage size */ protected long getSize(User user) { // do something...
return 0; }
/** * Displays the storage usage of users. * * @param req the http servlet request * @param res the http servlet response * * @throws IOException * @throws ServletException */ public void process(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
/** * An admin servlet as a http servlet to process the admin http servlet * request and response. * * @version 1.0 9/9/2003 * @author Bill */ public class AdminServlet extends HttpServlet { /** * Initiates the configuration. * * @param config the servlet config * * @throws ServletException */ private void initConfig(ServletConfig config) throws ServletException { // do somet