智能数据使Swing保持简单

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 ldCost = 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 ldColor = 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 功能 — 有的只是仔细放置的间接。结果是以直接、灵活并且可扩 展的方式简化了复杂显示功能和定制实现的系统。

从起点,到尽头,也许快乐,或有时孤独,

智能数据使Swing保持简单

相关文章:

你感兴趣的文章:

标签云: