首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

智能數據讓 Swing 保持簡單:iData 技術深度解析

     編輯:Java 相關知識

Swing 架構允許 Java 開發人員創建呈現大量數據的複雜顯示。遺憾的是,編寫代碼在大型 Swing 組件內維護那些數據簡直是噩夢。本文介紹稱為 iData 或智能數據的技術。可用 iData 架構在應用內創建數據中央資源庫,更徹底地將數據和顯示分離,產生更清晰易維護代碼。甚至有帶樣本代碼的開源工具箱幫助入門。

高級 Swing 架構使開發人員能設計比以前更複雜顯示。這些顯示通常需大量極易出錯且難維護邏輯。對高級 Swing 組件(如 JTable、JTree),當程序邏輯使用基於單元的數據存儲、編輯和渲染(常需更多全局知識)時,常遇困難。可將智能數據(帶高級知識數據)作為單元數據持久存儲在組件模型內,提供開發高級應用必要知識。iData 技術建立通用架構,將智能數據與 Swing 組件集成,同時保留 MVC 架構。通過緊密集成間接方案實現,將智能數據用於數據存儲、數據檢索間接以及顯示設置間接。生成間接對象創建靈活可擴展中央位置,實現帶最小複雜性的複雜業務顯示邏輯和交互功能。

開發人員可獲得開源 iData 工具箱,幫助將 iData 架構集成到項目。工具箱包含定義間接層接口集合、預設實現、優化、定制編輯器與渲染器及許多示例。

iData 技術三層

  1. 數據存儲:假定應用將數據存儲在 DataObject 中。DataObject 定義為符合 JavaBean 對象,含字段及對應 get[FieldName]() 和 set[FieldName]() 方法。
  2. 顯示組件數據值間接:數據間接層由定義包含 DataObject 對象組成,稱智能數據或 iData 層。iData 層接口定義訪問修改 DataObject 中字段通用方法。具體 iData 層類實現通用取值和賦值方法。通常僅讀寫 DataObject 值,但此間接創建實現複雜邏輯集中位置,如編譯驗證、虛擬數據、數據修飾。iData 層細分為不可修改(只讀)和可修改(讀/寫)功能,以簡化帶無需編輯邏輯複雜不可編輯數據接口。
  3. 根據數據定制編輯和渲染組件顯示間接:智能顯示或 iDisplay 層,通過類似 iData 層間接完成智能顯示。iDisplay 層為編輯和渲染 iData 層對象組件定義接口。定制示例:通過改變單元背景顏色顯示錯誤條件,創建通用編輯器允許 iData 層實現確定最適合編輯其數據組件。同樣細分可修改和不可修改功能。

三層結合創建緊密集成間接對象集,添加到組件模型而非數據本身。架構使基於單元知識成為可能,同時保留 Swing MVC。檢索、顯示、編輯數據邏輯封裝在每個單元內智能數據對象中。結果是實現複雜用戶界面顯示和交互功能上靈活可擴展技術。

圖1 iData 技術完整架構類圖

DataObject

DataObject 定義為符合 JavaBean 對象,含字段及對應 get/set 方法。通常按業務區域組合數據字段。示例「自行車商店」應用含 Bicycle DataObject,有大量字段(modelName、manufacturer、price、cost 等)及相應讀寫方法。其他可能 DataObject:BicycleComponent、Purchase。下為 Bicycle 部分代碼:

public class Bicycle {
    // fields
    double price = ...
    String manufacturer = ...
    // default constructor
    public Bicycle() {}
    // accessors
    public Double getPrice() { 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 接口含兩方法和一推薦覆蓋:getData() 從 DataObject 返回類型數據值;getSource() 返回 DataObject 本身;覆蓋 toString() 返回 getData() 結果字符串表示。

示例:Manufacturer 字段 ImmutableIData 實現:

public class BicycleManufacturerIData implements ImmutableIData {
    Bicycle bicycle = null;
    public BicycleManufacturerIData(Bicycle bicycle) { this.bicycle = bicycle; }
    public Object getSource() { return this.bicycle; }
    public Object getData() { return bicycle.getManufacturer(); }
    public String toString() {
        Object data = this.getData();
        if (data != null) return data.toString(); else return "";
    }
}

iData 工具箱提供實現 ImmutableIData 的 DefaultImmutableIData 抽象類,覆蓋 toString() 安全返回 getData().toString()。示例其餘擴展 iData 層接口預設實現。

與 JTable 集成

繼續「自行車商店」示例,將 iData 技術集成到 JTable。表有 manufacturer、modelName、modelID、price、cost、inventory 列。假定 ImmutableIData 實現緊跟 manufacturer iData 語句行。實際添加到 JTable DataModel 數據是 ImmutableIData 實現,每個含一 DataObject。這就是間接層實現思想。

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

發現用助手方法 createRow() 立即創建整行有用。用 AbstractTableModel 子類時,可將整行添加到模型。createRow() 以 DataObject 為參數,為特定表實例化適當 ImmutableIData 實現:

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 實現邏輯集中位置。從類管理角度,用匿名內部類也有用,對簡單實現可直接在 createRow() 聲明。

渲染順序

預設渲染器調用對象 toString() 創建字符串表示。這就是 ImmutableIData 實現需有用 toString() 重要原因。渲染期間,渲染器從 JTable 接收 ImmutableIData 實現。渲染 iData 調用 toString()。整個渲染順序:

  1. iData 實現 toString()
  2. iData 實現 getData()
  3. DataObject get[FieldName]()

圖3 渲染順序

圖4 只讀表

動態持久性

用 DataObject 作為 iData 數據不僅為間接提供靈活性,還添加提供動態持久性有用數據間接層。考慮已顯示表示例,從外部更新值。通常客戶端需複雜邏輯推斷模型中持久存儲更新值位置。用 iData 和 DataObject 間接,因自動持久存儲新值,此邏輯完全無需。這是用包含相同 DataObject 實例多個 iData 對象填充模型結果。更改 DataObject 內部值時,因所有 iData 對象指向同一實例,DataObject 本身不變。通過使用 DataObject 讀寫方法再查詢,無需手工持久性工作,總能返回最新結果。客戶端對更新唯一操作是重繪(repaint),強制渲染器重新渲染更新單元,即依次檢索顯示新數據值。

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

示例:虛擬列

虛擬列體現 iData 技術靈活性。虛擬列含數據(非顯式包含模型中,而是多字段組合)。設想 profit 列,顯示 price 和 cost 差額。創建此列,需創建 ImmutableIData 實現,getData() 返回差額:

public class BicycleProfitImmutableIData extends DefaultImmutableIData {
    public Object getData() {
        return new Double(bicycle.getPrice() - bicycle.getCost());
    }
}

用標準模型創建此類虛擬列需大量邏輯。先用正確值填充模型。編輯 price 或 cost 時可能出現問題:需複雜易出錯邏輯更新 profit 值。有 iData 技術,編輯時無需更新操作,動態持久性。

使用 iData 對象作構造塊

間接層作為一組 iData 對象實現,帶來實質好處。如附加兩數據值 PriceAndCost 顯示可用組合實現。非直接從新 CompositePriceAndCost 顯示 DataObject 檢索兩值,可用以前編寫 BicyclePriceImmutableIData 和 BicycleCostImmutableIData 對象。通過從兩 iData 實現(分隔符分隔,本例斜槓)檢索值並附加,getData() 創建返回字符串:

public Object getData() {
    return new String((String)priceIData.getData() + " / " + (String)costIData.getData());
}

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

基於反射 ImmutableIData 實現(UniversalImmutableIData)

iData 方法主要缺陷:類數目過多。大型應用中 iData 類數目可能迅速難控制。大多數 iData 實現重複相同順序:getData() 請求重定向到 DataObject get[FieldName]()。通常可用反射實現。工具箱含基於反射 ImmutableIData 實現預設實現 UniversalImmutableIData。UniversalImmutableIData 用 DataObject 和字段名作初始化參數。內部獲取字段名,檢索 get[FieldName]() 方法,調用 getData() 或 toString() 時調用該方法。簡化開發,減少類數目,代價僅反射帶來性能輕微降低。大多數應用不受影響,大型或實時應用開發者應記住。createRow() 使用 UniversalImmutableIData:

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;
}

UniversalImmutableIData 核心反射代碼片段:

protected String field = ...
protected Method accessorMethod = ...
protected Object source = ...
protected void setMethods() {
    if (field == null || field.equals("")) return;
    String firstChar = field.substring(0,1).toUpperCase();
    String restOfField = field.substring(1);
    String fieldAccessor = "get" + firstChar + restOfField;
    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() 採用新數據值參數,返回布林值表示編輯是否成功。通常需對新值強制類型轉換匹配 DataObject 字段緩存數據類型。標準實現安全測試對象類型,類型不匹配返回 false。Manufacturer 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;否則生成 MutableIData 且僅在只讀表用它。

定制編輯器

能修改數據後,大變化:編輯需定制編輯器。用預設編輯器,JTable 檢索編輯器查找 Object 類型數據,實際是 String 編輯器。停止編輯後,編輯器返回 String 值,持久存儲在模型中,用 String 替換 iData 實現。下圖描繪保持 iData 間接完整性必須遵循編輯順序:

圖5 編輯順序

雖可擴展現有編輯器遵循順序,但不切實際;導致過多類和不必要複雜性。定制編輯器某些獨特情況可行,但大多數編輯器遵循相同順序,可封裝在單獨類中。iData 工具箱含此類實現 UniversalTableCellEditor。UniversalTableCellEditor 用 TableCellEditor 內涵而非擴展。編輯時,從 iData 實現抽取數據值,用該值初始化所含 TableCellEditor。停止編輯時,從 TableCellEditor 檢索值並相應在 iData 實現設置值。若開始未指定編輯器,檢索 JTable 預設編輯器查找 iData 實現數據類型。整個編輯程序完全封裝在 UniversalTableCellEditor 中。意味著可用任何編輯器,甚至第三方,無需實現 iData 邏輯擴展。

建議通過將每個 TableColumn 預設編輯器設置成 UniversalTableCellEditor 來設置 JTable 編輯器。工具箱含帶幾靜態助手方法實用類。configureTable() 遍歷 TableColumns,將每個當前編輯器設置成包含該列以前單元編輯器 UniversalTableCellEditor 新實例。工具箱含 UniversalTableCellRenderer、JTree、JComboBox/JList 通用編輯器渲染器組合。

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

編輯標準困難:單元內驗證,即單元編輯停止前數據驗證。setData() 創建單元內驗證集中位置。考慮編輯 price 或 cost 後,若 price 低於 cost,用戶應被通知。提供選項:不處理;提高 price 等於 cost;修改未編輯值使差值與最初價差相等。在 setData() 實現相對容易:向用戶提供 JOptionPane 標識首選選項。選定後執行計算設置適當值。知道實現業務邏輯所有數據值及集中位置是 iData 技術靈活性關鍵。

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) {
            if (result.equals(priceEqualsCost))
                this.bicycle.setPrice(bicycle.getCost());
            else if (result.equals(keepProfitDifference))
                this.bicycle.setPrice(newCost + (oldPrice - oldCost));
            else if (result.equals(keepProfitPercentage))
                this.bicycle.setPrice(newCost * (oldPrice / oldCost));
        }
    }
    return true;
}

使用非 JTable 組件

為一致性示例一直用 JTable,但用另一 Swing 組件研究簡單示例也值得。創建含自行車名稱 JList。遍歷 Bicycle 集合,封裝在 BicycleModelNameImmutableIData 對象中,添加到 JList。注意 JList 用與 JTable 相同 iData 實例。任何其他組件同樣方式使用這些實例。JList 初始化:

protected void initList() {
    while (...) {
        Bicycle bike = ...
        model.addElement(new BicycleModelNameMutableIData(bicycle));
    }
    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 轉發請求給相應 get[Component]CellRendererComponent,位於 ImmutableIDisplayIData 所含 ImmutableIDisplay 中。

示例:用戶輸入低於 cost 的 price,隨後 price 和 cost 單元背景變紅。ImmutableIDisplay getTableCellRenderer() 檢索 DataObject,檢查 price 是否低於 cost。若是,背景設紅;否則設白。無特例時記得顯式設置背景為預設顏色,因 Swing 用輕量級模式渲染,重複繪制同一組件。若更改特例標準設置未為標準情況復位,會產生難預料結果。Bicycle 成本 getTableCellRenderer() 根據數據著色:

public TableCellRenderer getTableCellRenderer(JTable table, Object value,
    boolean isSelected, boolean hasFocus, int row, int column) {
    Color oldColor = renderer.getBackground();
    Color newColor = null;
    if (value instanceof MutableIData) {
        MutableIData arg = (MutableIData)value;
        Bicycle bike = (Bicycle)arg.getSource();
        if (arg.getData() instanceof Number) {
            double cost = ((Number)arg.getData()).doubleValue();
            double price = bike.getPrice();
            if (price > cost) newColor = Color.cyan; else newColor = Color.red;
        }
    }
    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 技術無顯著性能開銷。規定大量間接而非邏輯處理。但若 getData()/setData() 或 get[Component]CellRenderer()/Editor() 有太多邏輯會產生問題。每次繪制組件時,為組件中每單元調用這些方法邏輯。請盡可能保持簡潔。
  • 類數量:無疑 iData 技術需相當數量類。任何面向對象技術皆如此,且有好處。事實上,這些額外類駐留大量特定業務邏輯,強制產生某級別封裝,一般情況不會出現。若要使類數量保持最低,可能非最佳選擇。已做許多優化,但通常有相關性能代價。決定代碼複雜性、類數目、性能代價時應考慮應用需求。
  • 學習曲線:最突出缺陷。設計考慮靈活性擴展性,要求某些抽象量,易令人迷惑。相信經過探索架構可達到,但確實需堅持。

結束語

用結合 iData 間接層智能數據填充組件模型,有助於創建實現高級用戶界面功能靈活可擴展集中位置。可用完全封裝類中相對簡單邏輯實現,增加靈活性重用。附加開源工具箱使向集成 iData 技術轉換方便,因已編寫測試轉換所需大多數代碼。每應用只需 iData 間接層自己實現即可成功使用上述技術。無定制主要 Swing 組件、無定制模型、無更改標準 Swing 功能 — 有只是仔細放置間接。結果以直接、靈活可擴展方式簡化複雜顯示功能和定制實現系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved