|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 3
Example 5 lists the Swing application shown in Figure 2:
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
public class Test extends JFrame {
public static void main(String[] args) {
JFrame frame = new Test();
frame.setBounds(100, 100, 500, 200);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.show();
}
public Test() {
super("Creating a New Border Type");
Container contentPane = getContentPane();
JPanel[] panels = { new JPanel(),
new JPanel(), new JPanel() };
Border[] borders = { new HandleBorder(),
new HandleBorder(Color.red, 8),
new HandleBorder(Color.blue, 10) };
contentPane.setLayout(
new FlowLayout(FlowLayout.CENTER,20,20));
for(int i=0; i < panels.length; ++i) {
panels[i].setPreferredSize(new Dimension(100,100));
panels[i].setBorder(borders[i]);
contentPane.add(panels[i]);
}
}
}
The preceding application creates three panels (instances of javax.swing.JPanel) and three borders (instances of HandleBorder). Notice how you can easily specify borders for those panels by calling JComponent.setBorder().
Recall from Example 2 that a reference to a component passed to the component's border when JComponent calls Border.paintBorder()—a form of delegation. As I mentioned earlier, developers frequently use delegation with the Strategy pattern. The HandleBorder class does not use that component reference, but some borders use that reference to obtain information from their component.
For example, Example 6 lists the paintBorder() method for such a border, the javax.swing.border.EtchedBorder:
// The following listing is from
// javax.swing.border.EtchedBorder
public void paintBorder(Component component, Graphics g, int x, int y,
int width, int height) {
int w = width;
int h = height;
g.translate(x, y);
g.setColor(etchType == LOWERED? getShadowColor(component) : getHighlightColor(component));
g.drawRect(0, 0, w-2, h-2);
g.setColor(etchType == LOWERED? getHighlightColor(component) : getShadowColor(component));
g.drawLine(1, h-3, 1, 1);
g.drawLine(1, 1, w-3, 1);
g.drawLine(0, h-1, w-1, h-1);
g.drawLine(w-1, h-1, w-1, 0);
g.translate(-x, -y);
}
The javax.swing.border.EtchedBorder.paintBorder() method uses its component reference to find its component's shadow and highlight colors.
The Strategy pattern, as one of the simpler design patterns, proves easy to implement in your own software:
Strategy interface for your strategy objects
ConcreteStrategy classes that implement the Strategy interface, as appropriate
Context class, maintain a private reference to a Strategy object.
Context class, implement public setter and getter methods for the Strategy object
The Strategy interface defines your Strategy objects' behavior; for example, the Strategy interface for Swing borders is the javax.swing.Border interface.
The concrete ConcreteStrategy classes implement the Strategy interface; for example, for Swing borders, the LineBorder and EtchedBorder classes are ConcreteStrategy classes.
The Context class uses Strategy objects; for example, for Swing borders, the JComponent class is a Context class.
You may also examine your existing classes to see if they have tightly coupled functionality that's a candidate for Strategy objects. Typically, such candidates include switch statements similar to the one I discussed at the beginning of this article.
Some Swing components have more sophisticated rendering and editing requirements than others. Discuss how Swing uses the Strategy
pattern in its list class (javax.swing.JList) to render list cells.
Your last assignment asked you to reimplement the TableBubbleSortDecorator I first discussed in "Decorate Your Java Code" (JavaWorld, December 2001) with the JDK's built-in Proxy pattern support.
As a convenience, I created an abstract Decorator class that implements the java.lang.reflect.InvocationHandler interface. The Decorator class also references the decorated object (or in Proxy pattern parlance, the real subject). Example 1H lists the Decorator class:
import java.lang.reflect.InvocationHandler;
public abstract class Decorator implements InvocationHandler {
// The InvocationHandler interface defines one method:
// invoke(Object proxy, Method method, Object[] args). That
// method must be implemented by concrete (meaning not
// abstract) extensions of this class.
private Object decorated;
protected Decorator(Object decorated) {
this.decorated = decorated;
}
protected synchronized Object getDecorated() {
return decorated;
}
protected synchronized void setDecorated(Object decorated) {
this.decorated = decorated;
}
}
Although the Decorator class claims to implement the InvocationHandler interface, it does not implement the only method defined by that interface: invoke(Object proxy, Method method, Object[] methodArguments). Because of that omission, the Decorator class is abstract, and Decorator extensions must implement the invoke() method if they wish to be concrete.
The Decorator class acts as a base class for all decorators. Example 2H lists a Decorator extension specific to table sort decorators. Notice that TableSortDecorator does not implement the invoke() method, and therefore is also an abstract class:
import javax.swing.table.TableModel;
import javax.swing.event.TableModelListener;
public abstract class TableSortDecorator
extends Decorator
implements TableModelListener {
// Concrete extensions of this class must implement
// tableChanged from TableModelListener
abstract public void sort(int column);
public TableSortDecorator(TableModel realModel) {
super(realModel);
}
}
Now you can write a TableBubbleSortDecorator that uses the JDK's built-in Proxy pattern support:
import java.lang.reflect.Method;
import javax.swing.table.TableModel;
import javax.swing.event.TableModelEvent;
public class TableBubbleSortDecorator extends TableSortDecorator {
private int indexes[];
private static String GET_VALUE_AT = "getValueAt";
private static String SET_VALUE_AT = "setValueAt";
public TableBubbleSortDecorator(TableModel model) {
super(model);
allocate();
}
// tableChanged is defined in TableModelListener, which
// is implemented by TableSortDecorator.
public void tableChanged(TableModelEvent e) {
allocate();
}
// invoke() is defined by the java.lang.reflect.InvocationHandler
// interface; that interface is implemented by the
// (abstract) Decorator class. Decorator is the superclass
// of TableSortDecorator.
public Object invoke(Object proxy, Method method,
Object[] args) {
Object result = null;
TableModel model = (TableModel)getDecorated();
if(GET_VALUE_AT.equals(method.getName())) {
Integer row = (Integer)args[0],
col = (Integer)args[1];
result = model.getValueAt(indexes[row.intValue()],
col.intValue());
}
else if(SET_VALUE_AT.equals(method.getName())) {
Integer row = (Integer)args[1],
col = (Integer)args[2];
model.setValueAt(args[0], indexes[row.intValue()],
col.intValue());
}
else {
try {
result = method.invoke(model, args);
}
catch(Exception ex) {
ex.printStackTrace(System.err);
}
}
return result;
}
// The following methods perform the bubble sort ...
public void sort(int column) {
TableModel model = (TableModel)getDecorated();
int rowCount = model.getRowCount();
for(int i=0; i < rowCount; i++) {
for(int j = i+1; j < rowCount; j++) {
if(compare(indexes[i], indexes[j], column) < 0) {
swap(i,j);
}
}
}
}
private void swap(int i, int j) {
int tmp = indexes[i];
indexes[i] = indexes[j];
indexes[j] = tmp;
}
private int compare(int i, int j, int column) {
TableModel realModel = (TableModel)getDecorated();
Object io = realModel.getValueAt(i,column);
Object jo = realModel.getValueAt(j,column);
int c = jo.toString().compareTo(io.toString());
return (c < 0) ? -1 : ((c > 0) ? 1 : 0);
}
private void allocate() {
indexes = new int[((TableModel)getDecorated()).
getRowCount()];
for(int i=0; i < indexes.length; ++i) {
indexes[i] = i;
}
}
}
With the JDK's built-in Proxy pattern support and a well-designed base class, you can easily implement decorators by extending
Decorator and implementing invoke().
In an email to me, Ken Ballard wrote:
A toolbar shows certain buttons depending on what node I select in a tree. I created a toolbar decorator that takes a toolbar (JToolBar) in its constructor. The decorator has ashowButtonForNode()method that changes the toolbar's buttons depending upon the node. I invoke theshowButtonForNode()method in thevalueChanged()method of a tree selection listener.
Is this a good use of the Decorator pattern?
Numerous design patterns let you extend functionality; for example, in Java Design Patterns you've seen how to extend functionality with the Proxy, Decorator, and Strategy patterns. You can have difficulty knowing which pattern to use in a specific situation because groups of patterns achieve the same goal (for example, Proxy, Decorator, and Strategy all extend functionality). Closely related patterns' subtle differences determine which represents the best choice for a specific situation. Sometimes, more than one design pattern can be appropriate.
The Decorator pattern's major selling point: it can combine multiple behaviors at runtime; for example, in the "Homework from Last Time" section from "Take Control with the Proxy Design Pattern," I showed how to combine sorting and filtering for a Swing table:
TableSortDecorator sortDecorator = new TableBubbleSortDecorator(table.getModel()); TableFilterDecorator filterDecorator = new TableHighPriceFilter(sortDecorator); table.setModel(filterDecorator);
In the preceding code, a filter decorator decorates a sort decorator, which in turn decorates a table's model; as a result, the table model in question sorts and filters its data.
Ken would not likely combine switching toolbar buttons with other behavior, as is the case for sorting and filtering, so the Decorator pattern may prove overkill. In this case, the Proxy pattern seems more appropriate, as it also extends functionality by fixing the relationship between the proxy and the real subject at compile time, but does not compose behaviors at runtime.
Read more about Core Java in JavaWorld's Core Java section.