程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 智能數據使Swing保持簡單

智能數據使Swing保持簡單

編輯:關於JAVA

Swing 體系結構允許 Java 開發人員創建呈現大量數據的復雜顯示。遺憾的是,編寫代碼 以在大型 Swing 組件內維護那些數據簡直是一場噩夢。在本文中,Jonathan Simon 介紹了 一項稱為 iData 或稱為智能數據的技術。您可以使用 iData 體系結構來在您的應用程序內 創建數據的中央資源庫。這樣,可以更徹底地將數據和顯示相分離,並且產生數據的更清晰 更易於維護的代碼。甚至還有一個帶有樣本代碼的開放源碼工具箱可以幫助您入門。請繼續 閱讀以學習更多相關知識並查看 iData 技術的一個樣本實現。請在 論壇上與作者和其他讀 者分享您的想法。

高級 Swing 體系結構使得開發人員能夠設計比以前更復雜的顯示。這些顯示通常需要大 量極易出錯且難以維護的邏輯。對於高級 Swing 組件(例如,JTable 和 JTree),當程序 邏輯使用基於單元的數據存儲、編輯和渲染(常需要更多全局知識)時,常會碰到困難。可 以將智能數據,或帶有高級知識的數據作為單元數據持久存儲在組件模型內,此單元數據提 供了開發高級應用程序的必要知識。本文描述的 iData 技術建立了一個通用的體系結構,該 體系結構用於將智能數據與 Swing 組件集成,同時又保留了“模型-視圖-控制器(Model- View-Controller)”體系結構。通過一個緊密集成的間接方案實現了這一點,該方案將智能 數據用於數據存儲、數據檢索間接以及顯示設置間接。生成的間接對象創建了靈活且可擴展 的中央位置,用來實現帶有最小復雜性的復雜業務顯示邏輯和交互功能。

開發人員可以獲得一個開放源碼 iData 工具箱,以幫助他們將 iData 體系結構集成到他 們自己的項目中。該工具箱包含一個接口集合,這些接口定義了間接層、缺省實現、優化、 定制編輯器與渲染器以及許多示例。請閱讀 參考資料以獲取到該工具箱的鏈接。

iData 技術的三層

iData 技術包含三層。

數據存儲:iData 技術假定應用程序將數據存儲在 DataObject 中。人們將 DataObject 松散定義為符合 JavaBean 的對象,它含有一些字段,以及對應的 get[FieldName]() 和 set[FieldName]() 方法。

顯示組件的數據值間接:數據間接層由一個定義包含 DataObject 的對象組成。這稱為智 能數據或 iData 層。(注意,不要將 iData 層同 iData 技術相混淆,後者整體上是體系結 構的名稱。)iData 層接口定義了訪問與修改 DataObject 中字段的通用方法。針對具體的 需求,每個具體的 iData 層類都實現這些通用的取值(accessor)和賦值(mutator)方法 。通常,iData 層實現僅僅讀(get)和寫(set) DataObject 中的值。然而,正如您將在 示例中所看到的一樣,這一間接創建了一個實現復雜邏輯的集中位置,這些復雜邏輯包括編 輯驗證、虛擬數據和數據修飾。iData 層被進一步細分為不可修改(只讀)和可修改(讀/ 寫)數據的功能。進行這樣的區分是為了簡化那些帶有無須編輯邏輯的復雜的不可編輯數據 的接口。

根據數據定制編輯和渲染組件的顯示間接:智能顯示,或稱為 iDisplay 層,通過使用類 似於 iData 層的間接來完成智能顯示。iDisplay 層為編輯和渲染 iData 層對象的組件定義 了一個接口。這一 iDisplay 層定制的示例包括:通過更改單元背景顏色來顯示錯誤條件, 以及創建通用的編輯器,這些編輯器允許 iData 層實現確定最適合於編輯其數據的組件。同 iData 層一樣,iDisplay 層也被細分成可修改數據和不可修改數據的功能。

這三個層結合起來創建了一個緊密集成的間接對象集,這些對象被添加到了組件模型而不 是數據本身。該體系結構使得基於單元的知識成為可能,同時又可以保留 Swing 中的“模型 -視圖-控制器”體系結構。檢索、顯示和編輯數據的邏輯被封裝在每個單元內的智能數據對 象中。其結果是用於實現復雜用戶界面顯示和交互的功能上靈活和可擴展的技術。

圖 1. iData 技術的完整體系結構類圖

接下來,我們將討論 iData 技術體系結構的每一層。同時,我們將構建假想的“自行車 商店(Bike Shop)”應用程序的一些代碼片段以演示該技術。

DataObject

如上面所提到的,人們將 DataObject 定義為符合 JavaBean 的對象,該對象含有一些字 段和對應的 get[FieldName]() 和 set[FieldName]() 方法。通常,將數據字段按業務區域 組合在 DataObject 中。我們的示例“自行車商店”應用程序含有一個稱為 Bicycle 的 DataObject 對象,該對象有大量字段( modelName 、 manufacturer 、 price 和 cost 等 等)以及相應的讀(get)和寫(set)方法。“自行車商店”中其它可能的 DataObject 對 象有 BicycleComponent 對象(帶有類似於 Bicycle 的字段)和 Purchase DataObject 對 象(帶有如 purchasorName 、 price 、 dateOfPurchase 等字段)。下面是“自行車商店 ”應用程序中 Bicycle DataObject 對象的部分代碼的示例。

清單 1. 樣本 DataObject

public class Bicycle
{
   //fields
   double price = ...
   String manufacturer = ...
   ...
   //default constructor
   public Bicycle(){}
   //accessors
   public Double getPrice()
   {
      //sometimes its necessary to wrap primitives in related
      //Object types...
      return new Double(this.price);
   }
   public String getManufacturer()
   {
      return this.manufacturer;
   }
   ...
   //mutators
   public void setPrice(Double price)
   {
      this.price = price.doubleValue();
   }
   public void setManufacturer(String manufacturer)
   {
      this.manufacturer = manufacturer;
   }
   ...
}

間接:iData 層

如上面所提到的,iData 層被細分為不可修改數據和可修改數據的功能。由於 MutableIData 接口繼承了 ImmutableIData 接口,我們將從研究不可修改數據的功能開始。

只讀智能數據的數據間接層(ImmutableIData)

ImmutableIData 接口是 iData 層的一部分;它表示不可修改 iData 間接。它由兩個方 法和一個推薦的方法覆蓋組成:

getData() 從 DataObject 返回一個具有類型的數據值。

getSource() 返回 DataObject 本身。

覆蓋 toString() 方法返回 getData() 結果的 string 表示。

作為示例,讓我們看一下 Manufacturer 字段的 ImmutableIData 實現。

清單 2. “自行車制造商”的 ImmutableIData 實現

public class BicycleManufacturerIData implements ImmutableIData
{
  //the DataObject
  Bicycle bicycle = null;
  public BicycleManufacturerIData(Bicycle bicycle)
  {
   this.bicycle = bicycle; //cache the DataObject
  }
  public Object getSource()
  {
   return this.bicycle; //this simply returns the DataObject
  }
  public Object getData()
  {
   //returns the manufacturer field from the DataObject.
   //This is the main logical method of the indirection layer.
   return bicycle.getManufacturer();
  }
  public String toString()
  {
   //create a safe to String method to avoid null pointer exceptions
   //while painting...
   Object data = this.getData();
   if (data != null)  
    return data.toString();
   else
    return "";
  }
}

iData 工具箱提供了一個實現 ImmutableIData 的稱作 DefaultImmutableIData 的抽象 類。它覆蓋 Object 中的 toString() 方法以安全地返回 getData().toString() 。示例的 剩余部分將擴展 iData 層接口的缺省實現。這些缺省實現也包含在該工具箱中。

與 JTable 集成

讓我們繼續“自行車商店”示例,並將 iData 技術集成到 JTable 中。表有 manufacturer 、 modelName 、 modelID 、 price 、 cost 和 inventory 列。假定 ImmutableIData 實現的剩余部分緊跟 manufacturer iData 語句行編寫。

實際添加到 JTable DataModel 的數據是 ImmutableIData 實現,每個實現都含有一個 DataObject 。這種添加含有 DataObject 的 iData 層實現的思想就是前面所指的間接層的 實現。

圖 2. 帶有含有 DataObject 的 ImmutableIData 實現的 JTable 單元

我發現使用一個助手(helper)方法(我將其稱為 createRow() )來立即創建一整行是 有用的。當使用 AbstractTableModel 的子類時,可以將這一行整個添加到模型中。 createRow() 方法將 DataObject 作為參數,並為特定的表實例化適當的 ImmutableIData 實現。

清單 3. createRow() 方法

protected Vector createRow(Bicycle bicycle)
  {
   Vector vec = new Vector();
    vec.add(new BicycleModelNameImmutableIData(bicycle));
    vec.add(new BicycleModelIDImmutableIData(bicycle));
    vec.add(new BicycleManufacturerImmutableIData(bicycle));
    vec.add(new BicyclePriceAndCostImmutableIData(bicycle));
    vec.add(new BicycleProfitImmutableIData(bicycle));
    vec.add(new BicycleInventoryImmutableIData(bicycle));
   return vec;
  }

此外, createRow() 方法是確定模型中應放置何種 ImmutableIData 實現的邏輯的集中 位置。從類管理的角度,使用匿名內部類也是有用的,對於簡單的 ImmutableIData 實現, 可以直接在 createRow() 方法中聲明這些類。

渲染順序

缺省渲染器通過調用對象的 toString() 方法來創建他們要顯示對象的字符串表示。這就 是 ImmutableIData 實現要有一個有用的 toString() 方法是很重要的原因之一。在渲染期 間,渲染器從 JTable 接收到 ImmutableIData 實現。要渲染 iData,調用了 toString() 方法。以下表示了整個渲染順序:

iData 實現上的 toString()

iData 實現上的 getData()

DataObject 上的 get[FieldName]()

圖 3. 渲染順序

圖 4. 只讀表

動態持久性

使用 DataObject 作為 iData 的數據不僅為 iData 間接提供了靈活性,而且添加了一個 提供動態持久性的有用的數據間接層。請考慮一個已顯示的表的示例,正在從外部更新該表 的值。通常,客戶機需要實現復雜的邏輯來推斷模型中持久存儲更新值的地方。當使用 iData 和 DataObject 間接時,由於會自動持久存儲新值,因此這一邏輯完全沒有必要。這 就是用包含相同 DataObject 實例的多個 iData 對象填充模型的結果。當更改 DataObject 的內部值時,由於所有 iData 對象指向同一個實例,因此 DataObject 本身不會改變。通過 使用 DataObject 的讀(get)和寫(set)方法對其進行再查詢,無須任何手工持久性的工 作,就總能返回最新的結果。客戶機對更新要執行的唯一操作是重畫(repaint),這一操作 強制渲染器重新渲染更新的單元,即依次檢索並顯示新數據值。

這種間接的一個結果是有統一的客戶機數據高速緩存的能力。假定組件使用來自中央客戶 機高速緩存的 iData 間接和 DataObjects ,在整個應用程序中,所有數據編輯將動態持久 存儲。這極大地簡化了負責顯示動態數據的交易系統和其它客戶機。

示例:虛擬列

虛擬列體現了 iData 技術的靈活性。虛擬列是一個含有數據的列(這些數據沒有顯式地 包含在模型中,而是由多個字段組合而成)。設想稱為 profit 的列,它將顯示 price 和 cost 之間的差額。要創建這個列,需要創建一個 ImmutableIData 實現,其中 getData() 返回 price 和 cost 之間的差額。

清單 4. 利潤虛擬列

public class BicycleProfitImmutableIData extends DefaultImmutableIData
{
  ...

  public Object getData()
  {
   //return the difference of the price and cost field from the DataObject
   return new Double(bicycle.getPrice() - bicycle.getCost());
  }
}

使用標准模型創建這種類型的虛擬列將需要大量邏輯。首先,要使用正確的值填充該模型 。當編輯 price 或 cost 時,可能會出現一些問題:在整個應用程序中需要復雜和經常容易 出錯的邏輯來更新 profit 值。有了 iData 技術,編輯 price 或 cost 時,無須任何更新 操作。動態地存在持久性。

使用 iData 對象作為構造模塊

間接層作為一組 iData 對象來實現,這樣會帶來實質性的好處。例如,附加兩個數據值 的 PriceAndCost 顯示也可以使用組合來實現。不是直接從新的 CompositePriceAndCost 顯 示中的 DataObject 檢索這兩個值,而是可以使用以前編寫的 BicyclePriceImmutableIData 和 BicycleCostImmutableIData 對象。通過從兩個 iData 層實現(由一個分隔符來分隔, 在本例中,分隔符為斜槓)檢索值,然後附加這兩個值,這樣 getData() 就創建了返回字符 串。最終的 getData() 方法類似於這樣:

清單 5. PriceAndCostImmutableIData 的組合實現

public Object getData()
{
   // append the price, a slash, and the cost using pre-built iData
   // implementations  
   return new String( (String)priceIData.getData() + " / " +
     (String)costIData.getData() );
}

這種組合不同 iData 實現的能力提高了代碼重用和靈活性。可以通過對已有 iData 實現 進行不同的組合來開發新的 iData 實現。由於這種組合有利於運行時 iData 實現的動態組 合,這意味著需要更少的具體類和更大的靈活性。工具箱含有一些實現簡單的基於組合的 iData 對象的助手類,包括前綴和後綴字符串修飾符 iData 實現,這些實現使用任意一個帶 有後綴和/或前綴的 iData 對象來修飾 iData 的字符串表示。

基於反射(reflection)的 ImmutableIData 實現(UniversalImmutableIData)

iData 方法的主要缺陷之一是類的數目過多。在大型應用程序中,iData 類的數目可能會 迅速變得難以控制。大多數 iData 層實現重復相同的順序,在這一順序裡, getData() 請 求被重定向到 DataObject 中的 get[FieldName]() 方法。通常,可以使用反射來實現這一 點。工具箱含有一個基於反射的 ImmutableIData 實現的缺省實現,名為 UniversalImmutableIData 。 UniversalImmutableIData 使用一個 DataObject 和一個字段 名作為初始化參數。在內部,它獲取字段名,然後檢索 get[FieldName]() 方法,當調用 getData() 或 toString() 方法時會調用 get[FieldName]() 方法。這種方法簡化了開發, 同時減少了類的數目,所付出的代價只是由於使用反射而帶來的性能上的輕微降低。大多數 應用程序不會受這一性能下降影響,而大型或實時應用程序的開發人員應該牢記這一點。

清單 6. 使用 UniversalImmutableIData 的 createRow() 方法

protected Vector createRow(Bicycle bicycle)
{
  Vector vec = new Vector();
   vec.add(new UniversalImmutableIData(bicycle, "modelName"));
   vec.add(new UniversalImmutableIData(bicycle, "modelID"));
   vec.add(new UniversalImmutableIData(bicycle, "manufacturer"));
   vec.add(new UniversalImmutableIData(bicycle, "priceAndCost"));
   vec.add(new UniversalImmutableIData(bicycle, "inventory"));
  return vec;
}

清單 7. 來自基於反射的 UniversalImmutableIData 的樣本

protected String field = ... //the field name
  protected Method accessorMethod = ... //the accessor method
  protected Object source = ... //the DataObject
  ...
  protected void setMethods()
  {
   if (field == null || field.equals(""))
    return;
   //capitalize the first letter of the field, so you get getName,
   //not getname...
   String firstChar = field.substring(0,1).toUpperCase();
   //remove first letter
   String restOfField = field.substring(1);
   //add together the string "get" + the capitalized first letter,
   //plus the remaining
   String fieldAccessor = "get" + firstChar + restOfField;
   //cache the method object for future use
   this.setAccessorMethod(fieldAccessor);
  }
  ...
  protected void setAccessorMethod(String methodName)
  {
   try
   {
    accessorMethod = source.getClass().getMethod(methodName, null);
   }
   catch ( ... )
   {
    ...
   }
  }
  ...
  public Object getData()
  {
   try
   {
    return accessorMethod.invoke(source, null);
   }
   catch ( ... )
   {
    ...
   }
  }

可編輯智能數據的數據間接層(MutableIData)

通過增加一個 setData() 方法, MutableIData 繼承了 ImmutableIData ,使之可修改 。 setData() 方法采用新數據值作為一個參數,並返回一個表示編輯是否成功的布爾值。通 常,有必要對 setData() 方法中的新數據值進行強制類型轉換,以與 DataObject 中那個字 段高速緩存的數據類型相匹配。標准實現安全地測試對象類型,如果類類型不匹配,則返回 值為 false。

清單 8. “自行車制造商”的 MutableIData

public boolean setData(Object data)
  {
   if (!data instanceof String)
     return false;
   ((Bicycle)this.getSource()).setManufacturer((String)data);
     return true;
  }

一旦編寫完所有的新 MutableIData 對象,則更新 getRow() 方法來實例化 MutableIData 實例而不是與其對應的 Immutable 實例。我發現,當字段明確是不可修改時 ,只生成 ImmutableIData 對象。否則,知道不會調用 setData() 方法,生成了 MutableIData 並且僅在只讀表中使用它。

定制編輯器

既然已經能夠修改數據了,就有了一個大的改變:編輯數據需要定制編輯器。如果使用缺 省編輯器,那麼 JTable 將會檢索編輯器以查找 Object 類型的數據,這個編輯器實際上是 一個 String 編輯器。一旦停止編輯,該編輯器就返回一個 String 值,這個 String 值持 久存儲在模型中,其中使用 String 值替換 iData 層實現。下圖描繪了要保持 iData 間接 的完整性必須遵循的編輯順序。

圖 5. 編輯順序

雖然可以擴展已有的編輯器來遵循該順序,但這種方法是不切實際的;它會導致過多的類 和造成不必要的復雜性。定制編輯器在某些獨特情況下是可行的,但是大多數編輯器將遵循 相同的順序,可以將這一順序封裝在一個單獨的類中。iData 工具箱包含這個類的一個實現 ,稱為 UniversalTableCellEditor 。

UniversalTableCellEditor 使用 TableCellEditor 的內涵而不是其擴展。在編輯時, UniversalTableCellEditor 從 iData 層實現抽取出數據值,並使用該值初始化所含的 TableCellEditor 。當停止編輯時, UniversalTableCellEditor 從 TableCellEditor 中檢 索該值並相應地在 iData 實現中設置該值。如果開始沒有指定編輯器,則 UniversalTableCellEditor 檢索 JTable 中的缺省編輯器以查找 iData 實現的數據類型。

在以上所述的整個編輯程序完全封裝在 UniversalTableCellEditor 中。這意味著,可以 使用任何編輯器,甚至是第三方編輯器,而不需要實現 iData 邏輯的擴展。

我建議通過將每個 TableColumn 的缺省編輯器設置成 UniversalTableCellEditor 來設 置 JTable 中的編輯器。iData 工具箱含有一個帶有幾個靜態助手方法的實用類。實用類中 的 configureTable() 方法對 TableColumns 進行遍歷,將每個當前編輯器設置成包含那一 列以前的單元編輯器的 UniversalTableCellEditor 的新實例。

當工具箱與渲染器相關時,它有具有類似功能的 UniversalTableCellRenderer ,它具有 與渲染器類似的功能。工具箱中還包括 JTree 和 JComboBox/JList 的通用編輯器和渲染器 組合。

示例:單元內驗證保證了價格高於成本

編輯的標准困難是 單元內驗證,即在單元編輯停止之前進行數據驗證。 setData() 方法 創建了一個用於單元內驗證的集中位置。請考慮這樣一個示例,在對 price 或 cost 進行編 輯之後,如果 price 值低於 cost ,則用戶應該接到通知。這時,我們希望向用戶顯示下列 選項:

對兩個值不做任何處理。

提高 price 使之等於 cost 。

修改未編輯的那個值,使這個值與剛編輯過的值之間的差值與最初的價差相等。

在 setData() 方法中,實現它們相對比較容易。它向用戶提供了一個 JOptionPane 以標 識首選的選項。一旦選定了某個選項,則會執行計算以設置適當的值。知道實現這一業務邏 輯的所有數據值以及集中位置是 iData 技術靈活性的關鍵。

清單 9. 單元內驗證

String doNotEdit = "Do Not Edit";
String priceEqualsCost = "Price = Cost";
String keepProfitDifference = "Keep Profit Difference";
String keepProfitPercentage = "Keep Profit Percentage";
...
public boolean setData(Object data)
{
   double newCost = new Double(data.toString()).doubleValue();
   double oldCost = this.bicycle.getCost();
   double price = bicycle.getPrice();
   ((Bicycle)this.getSource()).setCost(newCost);
   if (price > newCost)
   {
    Object result = JOptionPane.showInputDialog
    (
     null,
     "Cost you have entered is more than the set price for this bicycle"
     + "\nPlease select from the following options",
     "",
     JOptionPane.QUESTION_MESSAGE,
     null,
     new Object[]{doNotEdit, priceEqualsCost, keepProfitDifference,
       keepProfitPercentage},
     priceEqualsCost
    );
    if (result != null)
    {
     //persist the data
     if (result.equals(priceEqualsCost))
      this.bicycle.setPrice(bicycle.getCost());
     //keep the delta between price and cost
     else if (result.equals(keepProfitDifference))
      this.bicycle.setPrice( newCost + (oldPrice - oldCost) );
     //keep the same profit percentage
     else if (result.equals(keepProfitPercentage))
      this.bicycle.setPrice( newCost * (oldPrice / oldCost) );
    }
   }
   return true;
  }

使用非 JTable 組件

雖然,出於一致性考慮,在我們的示例中一直使用 JTable,但是使用另一個 Swing 組件 來研究一個簡單的示例也是值得的。讓我們創建一個含有自行車名稱的 JList。僅對 Bicycle 對象的集合進行遍歷,將它們封裝在 BicycleModelNameImmutableIData 對象中, 然後將這些對象添加到 JList 中。請注意,在 JList 中使用了與 JTable 中相同的 iData 實例。在任何其它組件中也可以以相同方式使用這些實例。

清單 10. JList 初始化

protected void initList()
{
   ...
   while ( ... )
   {
    //wrap the bicycle in an iData object and add it to the list model
    Bicycle bike = ...
    model.addElement(new BicycleModelNameMutableIData(bicycle));
   }
   //wrap the lists renderer in the iData toolkit universal renderer
   //for JLists
   list.setCellRenderer(new
    UniversalListCellRenderer(list.getCellRenderer()));
}

圖 6. JList 示例

智能顯示間接(iDisplay)

iDisplay 結構是根據 iData 來創建定制顯示的層。例如,可以考慮這樣一個接口:其中 ,首選的 Manufacturer 應該用紅色文本顯示以提示用戶其首選狀態。通常,這需要復雜的 邏輯來檢索不是由渲染器傳入的數據值,這會導致復雜的代碼,使得以數據為中心的定制顯 示的實現變得不切實際。iDisplay 同 iData 的緊密集成使得這一方案非常簡單,這樣做也 為擴展創建了一個集中的位置。

只讀智能數據的顯示間接層(ImmutableIDisplay)

ImmutableIDisplay 封裝了特定於顯示的邏輯。可以通過 ImmutableIDisplay 讓對於三 種主要渲染器類型每一個有 get[Component]CellRendererComponent() 方法來實現這一點, 這三種類型是: TableCellRenderer 、 TreeCellRenderer 和 ListCellRenderer 。通過包 含 ImmutableIDisplay , ImmutableIDisplayIData 將 ImmutableIDisplay 與 ImmutableIData 集成在一起。

當 JTable 調用 UniversalTableCellRenderer 中的 getCellRendererComponent() 並傳 入一個 ImmutableIDisplayIData 類型的對象時, UniversalTableCellRenderer 則轉發 getCellRendererComponent 請求給相應的 get[Component]CellRendererComponent , get [Component]CellRendererComponent 位於 ImmutableIDisplayIData 所包含的 ImmutableIDisplay 中。

讓我們看一看“自行車商店”演示的另外一個示例,其中用戶輸入一個低於 cost 的 price ,隨後 price 和 cost 單元的背景顏色變紅。 ImmutableIDisplay 的 getTableCellRenderer() 方法檢索 DataObject ,並檢查 price 是否低於 cost 。如果是 這樣,就將背景設置為紅色;否則就將背景設置為白色。當沒有出現特例時,記住將背景顯 式地設置成缺省顏色,這一點很重要。Swing 使用最輕量級的模式用於渲染,重復地繪制同 一個組件。如果更改了特例的標准設置而又沒有為標准情況復位,那麼就會產生難以預料的 結果。

清單 11. Bicycle 成本的 getTableCellRenderer()方法根據數據對單元著色

public TableCellRenderer getTableCellRenderer(JTable table, Object value,
  boolean isSelected, boolean hasFocus, int row, int column)
{
    //cache old background for change comparisons
    Color oldColor = renderer.getBackground();
    //cache old background for change comparisons
    Color newColor = null;
    //check to see if Object is a MutableIData
    if (value instanceof MutableIData)
    {
     MutableIData arg = (MutableIData)value; //cast it.
     Bicycle bike = (Bicycle)arg.getSource();
     if (arg.getData() instanceof Number) //check the data type
     {
     // retrieve price and cost from the DataObject
      double cost = ((Number)arg.getData()).doubleValue();
      double price = bike.getPrice();
      //make comparisons
      if (price > cost)
       newColor = Color.cyan;
      else
       newColor = Color.red;
     }
    }
    // check and see if color changed
    if (!newColor.equals(oldColor))
      this.setBackground(newColor);
}

圖 7. 帶有 price 和 cost 背景顏色驗證的表

可編輯智能數據的顯示間接層(MutableIDisplay)

對於 iDisplay 實現,也存在不可修改/可修改的差異。 MutableIDisplay 負責編輯器 ,而 ImmutableIDisplay 負責渲染器。就象 ImmutableIDisplayIData 一樣,有一個繼承 MutableIData 且含有一個 MutableIDisplay 的 MutableIDisplayIData 。其用法同 ImmutableIDisplay 的用法相同,不同之處只是它實現的是 get[Component]CellEditor() 方法而不是 get[Component]CellRenderer() 方法。工具箱包含 JTable、JTree 和 JComboBox 的定制編輯器。

將 get[Component]CellRenderer() 和 get[Component]CellEditor() 方法轉發到 iDisplay 創建了一個有用的間接層。主要結果是產生了一個定制顯示設置和功能的集中的、 已封裝的位置。iData 使用 iDisplay 的內涵而不是擴展,這樣,除了限制了類的數目之外 還增加了靈活性和可擴展性。最為重要的是,幾乎不需要定制編輯器和渲染器,它們通常包 含非常復雜的顯示邏輯。雖然需要完整的定制編輯器和渲染器,但是可以使用由 iDisplay 提供的間接層來實現大多數顯示。

缺陷

在實現 iData 技術時,需要記住有幾個缺陷:

性能:對於大多數應用程序來說,iData 技術並沒有帶來顯著的性能開銷。該技術規定了 大量的間接而不是邏輯或處理。然而,如果 getData() / setData() 方法或 get [Component]CellRenderer() / Editor () 方法有太多邏輯,那麼就會產生問題。每次繪制 組件時,就會為組件中的每一個單元調用這些方法中的任何邏輯。因此,請盡可能地使這些 方法保持簡潔。

添加到代碼庫中的類:毫無疑問,使用 iData 技術需要相當數量的類。任何面向對象的 技術都會如此,而且這有一定好處。事實上,在這些額外的類中駐留著大量特定於應用程序 的業務邏輯,這樣會強制產生某個級別的封裝,而這種封裝在一般情況下是不會出現的。如 果要使類的數量保持絕對最低,那麼這可能不是最佳選擇。對於這些對規模要求至關重要的 應用程序已經做了很多優化,然而通常有相關的性能代價。因此,當決定代碼復雜性、類的 數目以及性能代價時,應該考慮應用程序需求。

學習曲線:這是最突出的缺陷。設計 iData 技術時,考慮的是靈活性和可擴展性。這就 要求某些數量的抽象,這些抽象首先就容易令人迷惑,即便沒有讓人望而卻步。我相信經過 一些探索後,這一體系結構是可達到的,但是這確實需要堅持不懈。

結束語

使用結合 iData 間接層的智能數據來填充組件模型有助於創建用於實現高級用戶界面功 能的靈活和可擴展的集中位置。此外,可以使用完全封裝的類中相對簡單的邏輯來實現這一 功能,這可以增加靈活性和重用。附加的開放源碼工具箱使向集成 iData 技術的轉換變得方 便,這是因為已經編寫並測試了轉換所需的大多數代碼。每個應用程序只需 iData 間接層的 自己實現就可以成功地使用上面所討論的技術。沒有定制主要的 Swing 組件,沒有定制模型 ,也沒有更改標准 Swing 功能 ― 有的只是仔細放置的間接。結果是以直接、靈活並且可擴 展的方式簡化了復雜顯示功能和定制實現的系統。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved