Java程式的髒資料問題 (轉)

amyz發表於2007-11-16
Java程式的髒資料問題 (轉)[@more@]

的髒資料問題

  髒資料(Out-of-date data),指過時的資料。
  如果在您的Java程式中存在髒資料,將或多或少地給帶來一些問題,如:無法實時地應用已經發生改變的,軟體系統出現一些莫名其妙的、難以重現的、後果嚴重的錯誤等等。儘量避免髒資料的存在是非常有價值的。本文希望能在這方面給同行們一點幫助。

Fragment 1. 快取技術的髒資料問題
  /**
  * A report printer is used to print a report.
  *
  * @version 1.0 9/9/
  * @author Bill
  */
  public class ReportPrinter {
  /**
  * Constructs a ReportPrinter instance.
  */
  public ReportPrinter() {
  // do something...
  }

  /**
  * Prints a printable.
  *
  * @param printable the specified printable
  */
  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);

  if(reportFont == null) {
  reportFont = loadFont(javaFont);
  fontMap.put(javaFont, reportFont);
  }

  return reportFont;
  }

  /**
  * 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 StorageUsageHandler instance.
  *
  * @return the single StorageUsageHandler instance
  */
  public static StorageUsageHandler getStorageUsageHandler() {
  if(handler == null) {
  handler = new StorageUsageHandler();
  }

  return handler;
  }

  /**
  * Constructs a StorageUsageHandler instance.
  */
  private StorageUsageHandler() {
  users = Context.getAllUsers();
  }

  /**
  * 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 StorageUsageHandler singleton.
  */
  private static StorageUsageHandler handler;

  /**
  * The users.
  */
  private List users;
  }

  您看出了問題所在嗎?
  Fragment 2中,由於沒有必要次次例項化StorageUsageHandler而帶來不必要的開銷,採用了Singleton模式以保證StorageUsageHandler只被例項化一次。
  在例項化SotrageUsageHandler時,StorageUsageHandler的類成員users將被賦值。由於不存在任何對users重新賦值的方法,一直駐留在軟體系統中的users將不會發生任何變化。在軟體系統啟動之後,增加、刪除或修改使用者的操作經常會發生,而一旦發生這類操作,users就成為了髒資料,Fragment 2將無法正常工作。
  如何避免使用Singleton模式所帶來的髒資料問題呢?
  對於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 StorageUsageHandler 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 request
  * @param res the http servlet response
  *
  * @throws IOException
  * @throws ServletException
  */
  public void process(HttpServletRequest req, HttpServletResponse res)
  throws IOException, ServletException {

  res.setContentType("text/html");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Pragma","no-cache");
  res.setDateHeader("Expires", 0);

  PrintWriter writer = new PrintWriter(res.getOutputStream());
  long sizes[] = getsizes();
  writer.println("

Storage Usage");
  writer.println("");

  for(int i = 0; i < sizes.length; i++) {
  writer.print("

");
  }

  writer.println("");
  writer.flush();
  writer.close();
  }

  /**
  * The users.
  */
  private List users;
  }

  /**
  * 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 something...

  handlerMap.put("__storage_Usage__", new StorageUsageHandler());
  }

  /**
  * Processes the http servlet request and response.
  *
  * @throws IOException
  * @throws ServletException
  */
  public void service(HttpServletRequest req, HttpServletResponse res)
  throws IOException, ServletException {

  AdminHandler handler = handlerMap.get(req.getParameter("handler"));

  if(handler == null) {
  // do something...

  return;
  }

  handler.process(req, res);
  }

  /**
  * The admin handler map(handler name->handler).
  */
  private HashMap handlerMap = new HashMap();
  }

  您一定看出了問題所在吧!
  Fragment 3中,由於StorageUsageHandler並不遵循Singleton模式,儘管StorageUsageHandler的類成員users只能在例項化StorageUsageHandler時被賦值,但是在單執行緒模式下,只要保證每次所使用的StorageUsageHandler例項是新例項化的,基本上還是沒有問題的。
  問題在於,在初始化AdminServlet的過程中,StorageUsageHandler被例項化並起來。此後,除非servlet container重新裝載AdminServlet,否則將無法重新例項化StorageUsageHandler,也將無法更新StorageUsageHandler的類成員users。這樣,在發生了增加、刪除或修改使用者的操作之後,users將成為髒資料。
  如何避免類使用所帶來的髒資料問題呢?
  i. 對於與類外部無依賴關係的類成員,不存在這種問題;
  ii. 對於依賴於類外部的類成員,且該類成員不存在更新機制。最好是將其去掉,需要時從類外部直接獲取;如果這種辦法不可行,應提供機制以確保在使用該類成員之前,該類成員已經被更新過;如果這種辦法還不可行,請清晰地說明類的使用方式,以防止不當的類使用發生。

小節
  以上用三個例子列舉了三類常見的髒資料問題。事實上,Java程式中的髒資料問題存在形式非常多樣,因而,在設計、實現、測試和重構過程中,緊記(Keep in mind)避免髒資料的存在是非常重要的,我們可以從系統、子系統、類和類成員等各個層次來檢查Java程式。
  “只做好一件事”是對簡單性的最佳詮釋,這句話同樣最好地詮釋了軟體系統在功能方面的正交性。然而,在面向的過程中,僅僅在功能方面確保正交性是不夠的,還應該在資料儲存方面來儘量保證正交性。當然,考慮到效能等因素,在資料儲存方面確保正交性比較困難,對於破壞此規則的資料儲存,應提供機制以確保所使用資料的實時性。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982692/,如需轉載,請註明出處,否則將追究法律責任。

Java程式的髒資料問題 (轉)
請登入後發表評論 登入
全部評論
");
  writer.print(users.get(i) + ": " + sizes[i]);
  writer.println("

相關文章