到 Google 资讯主页   
EasyJF首页   资料   源码   软件    论坛   网站    
   使用帮助    
    该信息为本站MyRSS系统缓存内容,部分图片及附件有可能无法正常使用.easyjf.comwww.javaresearch.org无关,不对该信息负责.通过http://www.javaresearch.org/article//showarticle.jsp?column=31&thread=917访问该信息的原始内容.
页面功能  【加入收藏】 【推荐给朋友】 【字体:  】 【关闭】   
Decorate your Java code (2) -- A look at the Decorator design pattern
作者:bruce 来源:www.javaresearch.org  发布时间:2006-02-28 12:36:57.603


Sort and filter decorators for Swing tables

The Decorator pattern excels at attaching functionality, such as sorting and filtering, to Swing tables. In fact, the Decorator pattern is flexible enough that we can attach functionality to any Swing table at runtime. Before we can discuss how decorators enhance Swing tables in detail, we must have a basic understanding of Swing tables; so, let's take a short detour. 

Swing tables

Swing components are implemented with the Model-View-Controller (MVC) design pattern. Each component consists of a model that maintains data, views that display the data, and controllers that react to events. For example, Swing tables, instances of JTable, are commonly created with an explicit model. Example 3 lists an application that does just that. (Try to picture the table in Example 3 before viewing it in Figure 5.) 

Example 3. Create a Swing table

 
  1. import javax.swing.*;
  2. import javax.swing.table.*;
  3. public class Test extends JFrame {
  4.    public static void main(String args[]) {
  5.       Test frame = new Test();
  6.       frame.setTitle("Tables and Models");
  7.       frame.setBounds(300, 300, 450, 300);
  8.       frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
  9.       frame.show();
  10.    }
  11.    public Test() {
  12.       TableModel model = new TestModel();   
  13.       getContentPane().add(new JScrollPane(new JTable(model)));
  14.    }
  15.    private static class TestModel extends AbstractTableModel {
  16.       final int rows = 100, cols = 10;
  17.       public int    getRowCount()    { return rows; }
  18.       public int    getColumnCount() { return cols; }
  19.       public Object getValueAt(int row, int col) {
  20.          return "(" + row + "," + col + ")";
  21.       }
  22.    }
  23. }

The application creates a table with a model. That table is placed in a scrollpane and added to the frame's content pane. Figure 5 shows the application listed in Example 3. 


[i]Figure 5. A simple Swing table[/i] 

The application shown above creates a table model with 100 rows and 10 columns. When asked for a cell value, the model creates a string that represents the cell's row and column. The application uses that model to construct a Swing table (an instance of JTable). Table models produce a table's data, in addition to metadata such as the number of rows and columns. 

It's important to understand that Swing tables possess models that maintain a table's data. Table models, as evidenced in Example 3, do not have to actually store any data -- they must produce a value for a given row and column. 

A sort decorator

With a basic understanding of the Decorator pattern and Swing tables, we can now implement a table sort decorator. First, we'll see an application that uses the decorator, followed by a discussion of the decorator's implementation. 

The application shown in Figure 6 contains a table with two columns: one for items and another for price. You can sort columns by clicking on column headers. When the application starts, nothing is sorted, as evidenced by the status bar in the bottom of the left picture in Figure 6. I took the middle picture in Figure 6 after I clicked the Item column header. I took the the right picture after I clicked the Price/Lb. column header.
 

[i]Figure 6. A sorting decorator. Click on thumbnail to view full-size image[/i] 

The application shown in Figure 6 replaces the table's model with a sort decorator. That application is partially listed in Example 4. (You can download the full source code from the Resources section below.) 

Example 4. Sorting a Swing table 


  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.util.Locale;
  4. import java.util.ResourceBundle;
  5. import javax.swing.*;
  6. import javax.swing.event.*;
  7. import javax.swing.table.*;
  8. public class Test extends JFrame {
  9.    public static void main(String args[]) {
  10.       SwingApp.launch(new Test(), "A Sort Decorator"
  11.                                    300, 300, 200, 250);
  12.    }
  13.    public Test() {
  14.       // Create the decorator that will decorate the table's 
  15.       // original model. The reference must be final because it's
  16.       // accessed by an inner class below. 
  17.       final TableSortDecorator decorator = 
  18. new TableSortDecorator(table.getModel());
  19.       // Set the table's model to the decorator. Because the
  20.       // decorator implements TableModel, the table will never
  21.       // know the difference between the decorator and it's
  22.       // original model.
  23.       table.setModel(decorator);
  24.       ...
  25.       // Obtain a reference to the table's header
  26.       JTableHeader header = (JTableHeader)table.getTableHeader();
  27.       // React to mouse clicks in the table header by calling 
  28.       // the decorator's sort method.
  29.       header.addMouseListener(new MouseAdapter() {
  30.          public void mouseClicked(MouseEvent e) {
  31.             TableColumnModel tcm = table.getColumnModel();
  32.             int vc = tcm.getColumnIndexAtX(e.getX());
  33.             int mc = table.convertColumnIndexToModel(vc);
  34.             // Perform the sort
  35.             decorator.sort(mc);
  36.             // Update the status area
  37.             SwingApp.showStatus(headers[mc] + " sorted");
  38.          }
  39.       });
  40.    }
  41.    ...
  42. }


In the preceding code, a sort decorator decorates the table's original model. When you click on a column header, the application calls the decorator's sort() method. The interesting code in this example is not the application, but the sort decorator. Before looking at the source to TableSortDecorator, let's look at its class diagram, shown in Figure 7. 


[i]Figure 7. Sorting decorator class diagram[/i] 

Since instances of TableSortDecorator decorate table models, TableSortDecorator implements the TableModel interface. TableSortDecorator also maintains a reference to the real model that it decorates. That reference can point to any table model. Example 5 lists the TableSortDecorator class: 

Example 5. A table sort decorator 

  1. class TableSortDecorator implements TableModel,TableModelListener{
  2.    public TableSortDecorator(TableModel model) {
  3.       this.realModel = model;   
  4.       realModel.addTableModelListener(this);
  5.       allocate();
  6.    }
  7.    // The following nine methods are defined by the TableModel
  8.    // interface; all of those methods forward to the real model.
  9.    public void addTableModelListener(TableModelListener l) {
  10.       realModel.addTableModelListener(l);
  11.    }
  12.    public Class getColumnClass(int columnIndex) {
  13.       return realModel.getColumnClass(columnIndex);
  14.    }
  15.    public int getColumnCount() {
  16.       return realModel.getColumnCount();   
  17.    }
  18.    public String getColumnName(int columnIndex) {
  19.       return realModel.getColumnName(columnIndex);
  20.    }
  21.    public int getRowCount() {
  22.       return realModel.getRowCount();   
  23.    }
  24.    public boolean isCellEditable(int rowIndex, int columnIndex) {
  25.       return realModel.isCellEditable(rowIndex, columnIndex);
  26.    }
  27.    public void removeTableModelListener(TableModelListener l) {
  28.       realModel.removeTableModelListener(l);
  29.    }
  30.    public Object getValueAt(int row, int column) {
  31.       return getRealModel().getValueAt(indexes[row], column);
  32.    }
  33.    public void setValueAt(Object aValue, int row, int column) {
  34.       getRealModel().setValueAt(aValue, indexes[row], column);
  35.    }
  36.    // The getRealModel method is used by subclasses to
  37.    // access the real model.
  38.    protected TableModel getRealModel() {
  39.       return realModel;
  40.    }
  41.    // tableChanged is defined in TableModelListener, which
  42.    // is implemented by TableSortDecorator.
  43.    public void tableChanged(TableModelEvent e) {
  44.       allocate();   
  45.    }
  46.    // The following methods perform the bubble sort ...
  47.    public void sort(int column) {
  48.       int rowCount = getRowCount();
  49.       for(int i=0; i < rowCount; i++) {
  50.          for(int j = i+1; j < rowCount; j++) {
  51.             if(compare(indexes[i], indexes[j], column) < 0) {
  52.                swap(i,j);
  53.             }
  54.          }
  55.       }
  56.    }
  57.    private void swap(int i, int j) {
  58.       int tmp = indexes[i];
  59.       indexes[i] = indexes[j];
  60.       indexes[j] = tmp;
  61.    }
  62.    private int compare(int i, int j, int column) {
  63.       TableModel realModel = getRealModel();
  64.       Object io = realModel.getValueAt(i,column);
  65.       Object jo = realModel.getValueAt(j,column);
  66.       int c = jo.toString().compareTo(io.toString());
  67.       return (c < 0) ? -1 : ((c > 0) ? 1 : 0);
  68.    }
  69.    private void allocate() {
  70.       indexes = new int[getRowCount()];
  71.       for(int i=0; i < indexes.length; ++i) {
  72.          indexes[i] = i;         
  73.       }
  74.    }
  75.    private TableModel realModel; // We're decorating this model
  76.    private int indexes[];
  77. }


Table sort decorators do not change their real model when they sort; instead, decorators maintain an array of integers representing sorted row positions. When a sort decorator, masquerading as a table model, is asked for a value at a specific row and column, it uses the row value as an index into its array, and returns the corresponding value from the array. In that way, sort decorators overlay sorting onto Swing table models without modifying the original model in any way. 

TableSortDecorator also implements the TableModelListener interface and registers itself as a listener with the real model. When the real model fires a table changed event, the decorator reallocates its array of sorted index references. That allocation happens in TableSortDecorator.tableChanged. 

TableSortDecorator has 11 public methods. Nine of those methods forward to the real model. Of those nine methods, only two methods -- getValueAt() and setValueAt() -- provide any additional functionality. Such ratios are not unusual among decorators; most decorators add a small amount of behavior to a decorated object that already performs several functions. 

Refactoring the sort decorator

The sort decorator discussed above works as advertised by decorating arbitrary table models with sorting functionality at runtime. This gives you flexibility because the decorated models do not need to be modified, which means that any table model can be decorated with sorting capabilities. 

But the TableSortDecorator as implemented above is not designed for reuse because it implements two responsibilities for which it is not well suited. The first responsibility is forwarding to the real model. Because other table model decorators will need almost exactly the same code, that responsibility is too general, and should be moved up the class hierarchy. The second responsibility is sorting, which in this example is a bubble sort. That sorting algorithm is hardcoded into the sort decorator, which means sorting cannot be changed without rewriting the entire decorator. The sorting algorithm is too specific, so it should be moved down the hierarchy. 

Figure 8 shows the result of refactoring the sort decorator as recommended in the preceding paragraph. 


[i]Figure 8. Refactored sorting decorator class diagram. Click on thumbnail to view full-size image[/i]

The refactoring split the original TableSortDecorator into three different classes: 
  • TableModelDecorator: Implements TableModel by forwarding to the real model 

  • TableSortDecorator: Extends TableModelDecorator and adds an abstract sort method that subclasses must implement 

  • TableBubbleSortDecorator: Extends TableSortDecorator and implements the bubble sort 

By refactoring the sort decorator, we can reuse the code that forwards to the real table model. Encapsulating that code in TableModelDecorator lets us easily develop other table model decorator types, such as the filter decorators -- which are not discussed in this article -- shown in Figure 8. 

The abstract TableSortDecorator class defers the implementation of its sort() method to subclasses, making it trivial to implement sort decorators with a different sorting algorithm. 

Example 6 lists the TableModelDecorator class. 

Example 6. TableModelDecorator 

  1. import javax.swing.table.TableModel;
  2. import javax.swing.event.TableModelListener;
  3. // TableModelDecorator implements TableModelListener. That
  4. // listener interface defines one method: tableChanged(), which
  5. // is called when the table model is changed. That method is
  6. // not implemented in this abstract class; it's left for
  7. // subclasses to implement.
  8. public abstract class TableModelDecorator 
  9.                         implements TableModelTableModelListener {
  10.    public TableModelDecorator(TableModel model) {
  11.       this.realModel = model;   
  12.       realModel.addTableModelListener(this);
  13.    }
  14.    // The following 9 methods are defined by the TableModel
  15.    // interface; all of those methods forward to the real model.
  16.    public void addTableModelListener(TableModelListener l) {
  17.       realModel.addTableModelListener(l);
  18.    }
  19.    public Class getColumnClass(int columnIndex) {
  20.       return realModel.getColumnClass(columnIndex);
  21.    }
  22.    public int getColumnCount() {
  23.       return realModel.getColumnCount();   
  24.    }
  25.    public String getColumnName(int columnIndex) {
  26.       return realModel.getColumnName(columnIndex);
  27.    }
  28.    public int getRowCount() {
  29.       return realModel.getRowCount();   
  30.    }
  31.    public Object getValueAt(int rowIndex, int columnIndex) {
  32.       return realModel.getValueAt(rowIndex, columnIndex);
  33.    }
  34.    public boolean isCellEditable(int rowIndex, int columnIndex) {
  35.       return realModel.isCellEditable(rowIndex, columnIndex);
  36.    }
  37.    public void removeTableModelListener(TableModelListener l) {
  38.       realModel.removeTableModelListener(l);
  39.    }
  40.    public void setValueAt(Object aValue, 
  41.                         int rowIndex, int columnIndex) {
  42.       realModel.setValueAt(aValue, rowIndex, columnIndex);
  43.    }
  44.    // The getRealModel method is used by subclasses to
  45.    // access the real model.
  46.    protected TableModel getRealModel() {
  47.       return realModel;
  48.    }
  49.    private TableModel realModel; // We're decorating this model
  50. }


Notice that public TableModelDecorator methods do nothing but forward to the real model. That makes them good defaults for other classes to inherit when they extend TableModelDecorator. Example 7 lists TableSortDecorator. 

Example 7. TableSortDecorator 

  1. import javax.swing.table.TableModel;
  2. public abstract class TableSortDecorator extends 
  3.                                          TableModelDecorator {
  4.    // Extensions of TableSortDecorator must implement the
  5.    // abstract sort method, in addition to tableChanged. The
  6.    // latter is required because TableModelDecorator
  7.    // implements the TableModelListener interface.
  8.    abstract public void sort(int column);
  9.    public TableSortDecorator(TableModel realModel) {
  10.       super(realModel);
  11.    }
  12. }


The abstract TableSortDecorator class defines one abstract method -- sort() -- that must be implemented by concrete subclasses. The TableBubbleSortDecorator, listed in Example 8, is one such class. 

Example 8. TableBubbleSortDecorator 

  1. import javax.swing.table.TableModel;
  2. import javax.swing.event.TableModelEvent;
  3. public class TableBubbleSortDecorator extends TableSortDecorator {
  4.    // The lone constructor must be passed a reference to a
  5.    // TableModel. This class decorates that model with additional
  6.    // sorting functionality.
  7.    public TableBubbleSortDecorator(TableModel model) {
  8.       super(model);
  9.       allocate();
  10.    }
  11.    // tableChanged is defined in TableModelListener, which
  12.    // is implemented by TableSortDecorator.
  13.    public void tableChanged(TableModelEvent e) {
  14.       allocate();   
  15.    }
  16.    // Two TableModel methods are overridden from
  17.    // TableModelDecorator ...
  18.    public Object getValueAt(int row, int column) {
  19.       return getRealModel().getValueAt(indexes[row], column);
  20.    }
  21.    public void setValueAt(Object aValue, int row, int column) {
  22.       getRealModel().setValueAt(aValue, indexes[row], column);
  23.    }
  24.    // The following methods perform the bubble sort ...
  25.    public void sort(int column) {
  26.       int rowCount = getRowCount();
  27.       for(int i=0; i < rowCount; i++) {
  28.          for(int j = i+1; j < rowCount; j++) {
  29.             if(compare(indexes[i], indexes[j], column) < 0) {
  30.                swap(i,j);
  31.             }
  32.          }
  33.       }
  34.    }
  35.    private void swap(int i, int j) {
  36.       int tmp = indexes[i];
  37.       indexes[i] = indexes[j];
  38.       indexes[j] = tmp;
  39.    }
  40.    private int compare(int i, int j, int column) {
  41.       TableModel realModel = getRealModel();
  42.       Object io = realModel.getValueAt(i,column);
  43.       Object jo = realModel.getValueAt(j,column);
  44.       int c = jo.toString().compareTo(io.toString());
  45.       return (c < 0) ? -1 : ((c > 0) ? 1 : 0);
  46.    }
  47.    private void allocate() {
  48.       indexes = new int[getRowCount()];
  49.       for(int i=0; i < indexes.length; ++i) {
  50.          indexes[i] = i;         
  51.       }
  52.    }
  53.    private int indexes[];
  54. }


With the refactoring of the original table sort decorator, we now have a hierarchy of table decorators that we can extend at will. 

Decorator applicability

In Java, developers typically use inheritance to add functionality to objects. Base classes implement shared behavior, and extensions add functionality. For example, instead of the sort and filter decorators discussed above, you could implement SortModel, FilterModel, and, perhaps, SortFilterModel classes. 

Decorators prove more flexible than inheritance because the relationship between decorators and the objects they decorate can change at runtime, but relationships between base classes and their extensions are fixed at compile time. 

Of course, both inheritance and the Decorator pattern have their places; in fact, inheritance is used to implement the Decorator pattern. So when should you use the Decorator pattern? It's useful when: 
  • You have many simple behaviors you would like to combine in numerous ways at runtime. Implementing the functionality with inheritance would result in a subclass explosion for every possible combination of behaviors. 

  • You need to transparently add functionality to individual objects at runtime. Transparently means that existing classes need not have been modified to support the extra functionality; indeed, those classes are not aware that they are being decorated. 

  • You want to restrict the use of an object's public methods. Instead of forwarding calls to a restricted method, a decorator can veto a method by throwing an exception from the wrapper method. 


 
相关文章
 
页面功能  【加入收藏】 【推荐给朋友】 【字体:  】 【关闭】   


EasyJF.com 2006 隐私政策 使用EasyJF前必读