Java设计模式之修饰模式篇 |
时间:2014-05-04 10:31:59 来源:JSP天空网 作者:未知 |
最近我给女朋友买了一款可以更换外壳的手机。现在的外壳是红色的,如果我想用这款手机的时候,会更换成银灰色的外壳。但是我不能随意更换天线或者话筒,因为这些功能模块在手机生产的时候就已经被固定了。
软件中的修饰者(decorator),和手机的外壳一样,封装了一些可以替换的功能。例如下面是一段替换Swing中表模型的代码:
TableSortDecorator sortDecorator = new TableSortDecorator(table.getModel()); table.setModel(sortDecorator);
在这段代码中,程序首先将表模型包装在一个修饰对象中。以后当表对它的模型进行操作的时候,它实际上操作的是排序修饰对象(sortDecorator),该修饰对象在表模型中加入了排序功能,而将其他基本的功能委托给缺省的表模型,在修饰模型中,这个缺省的表模型又被称为真实对象(real subject)。
在Java的编程中,基类和子类的继承关系在编译的时候就被固定了,就像手机的天线和话筒一样。由于继承关系是静态的,开发人员无法在程序运行时改变对象的行为。但是通过修饰者开发人员可以在运行时拼装对象,因此修饰模式提供了一种比继承更灵活的功能扩充模式。
修饰模式(Decorator Pattern)
在运行时将特定的功能绑定在对象上,这就是修饰模式的核心。修饰模式比继承更加灵活,因为后者是在编译时就将特定的功能绑定到类上。
下面然我们来看一个简单的I/O例子:
FileReader frdr = new FileReader(filename); LineNumberReader lrdr = new LineNumberReader(frdr);
这段代码中创建了一个Reader:lrdr。它从一个文件中读取数据并跟踪文件的行号。在第一行创建的frdr对象能够从文件中读取数据,而第二行给lrdr增加了跟踪行号的功能。在运行时(runtime),修饰者将方法调用传递给它所修饰的真实对象。在上面的例子中,lrdr将方法调用传递给它修饰的真实对象frdr。修饰者除了能够进行方法传递外,还能够增加类的功能。例如在上面的例子中,lrdr能够跟踪当前的文件流读入数据的行号。
而下面的例子显示了如何在程序中使用修饰者lrdr。程序将数据按行从文件中读出后,加上行号输出到屏幕上。
try { LineNumberReader lrdr = new LineNumberReader(new FileReader(filename));
for(String line; (line = lrdr.readLine()) != null;)rticle.txt { System.out.print(lrdr.getLineNumber() + ": " + line); } } catch(java.io.FileNotFoundException fnfx) { fnfx.printStackTrace(); } catch(java.io.IOException iox) { iox.printStackTrace(); }
修饰者的静态和动态特性
工程学上经常提到静态和动态的概念。静态方法研究那些变化或位移相对较小的对象,例如桥梁或建筑,而动态方法研究那些变化和移动较快的对象,例如发动机。在软件工程中也有相应的概念,静态方法研究在编译时类之间的关系,而动态方法研究在运行时类参与的一些的事件。在这一节中,我将用UML类图来展示修饰者的静态特性,用UML时序图来展示修饰者的动态特性。
修饰者的静态特性
修饰者通过增加功能来修饰被修饰对象(Decorated,也就是真实对象)。下面的UML类图展示了修饰者和真实对象之间的关系。
图1 修饰者和被修饰者的关系
修饰者继承了被修饰者或者实现了被修饰者的接口,同时修饰者还保存了对被修饰者实例的引用,这个实例就是修饰者修饰的对象。为了说明这些类在到底是如何关联的,图2中举了一个Java SDK的java.io.package中的实际例子。
图2 一个真实的修饰模型例子
BufferedReader和FilterReader就是图1中演示的抽象类,他们都继承了抽象类Reader,并且将方法调用传递给Reader对象。由于继承了修饰者类,因此LineNumberReader和PushbackReader也是修饰者类。
修饰者的动态特性
在运行时,修饰者将方法调用传递给被修饰者,如图3所示:
图3 修饰者的动态特性
修饰者通常将对被修饰者的调用包装起来,图3描述了这种特性。图4描述了上面的I/O例子中修饰者的动态特性:
图4 I/O例子中修饰者的动态特性
现在大家对修饰模式以及它的静态和动态特性有一个比较明确的认识了。让我们通过一个完整的例子来说明如何在代码中实现修饰模式。
排序和过滤修饰
修饰者主要是用于给被修饰者增加功能。在下面的例子中,我们会给Swing中的表增加排序和过滤的功能。在介绍例子之前,先简单介绍一下如何使用Swing中的JTable类。
import javax.swing.*; import javax.swing.table.*; public class Test extends JFrame { public static void main(String args[]) { Test frame = new Test(); frame.setTitle("Swing表的例子"); frame.setBounds(300, 300, 450, 300); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.show(); } public Test() { TableModel model = new TestModel(); getContentPane().add(new JScrollPane(new JTable(model))); } private static class TestModel extends AbstractTableModel { final int rows = 100, cols = 10; public int getRowCount() { return rows; } public int getColumnCount() { return cols; } public Object getValueAt(int row, int col) { return "(" + row + "," + col + ")"; } } }
该程序创建了一个100×10的表。表对象由三个部分组成:表模型、视图和事件控制器。表中的数据保存在表模型中,视图控制数据的显示,而事件控制器控制对事件的响应。图5是运行这个程序的结果。
图5 Swing表的例子
排序修饰者
图6中的应用程序包含了一张两列的表,一列是货物名称,一列是价格。通过单击列头可以根据货物的价格对表进行排序。下面是这个程序的代码:
图6 排序修饰者的例子
//Test.java import java.awt.*; import java.awt.event.*;
import java.util.Locale; import java.util.ResourceBundle;
import javax.swing.*; import javax.swing.table.*;
public class Test extends JFrame { public static void main(String args[]) { SwingApp.launch(new Test(), "排序修饰者", 300, 300, 450, 250); } public Test() { // 生成修饰者的实例,该实例用于修饰Swing Table原有的表模型 // 该实例必须是final的,因为它会被内嵌类引用。 final TableSortDecorator decorator = new TableBubbleSortDecorator(table.getModel()); // 将表的模型设定为修饰者。因为修饰者实现了TableModel接口, // 因此Swing Table对象不知道修饰者和真实对象之间的差别。 table.setModel(decorator); getContentPane().add(new JScrollPane(table), BorderLayout.CENTER); // 在界面中添加一个状态区 getContentPane().add(SwingApp.getStatusArea(), BorderLayout.SOUTH); SwingApp.showStatus("进行排序前"); // 获得对表中列头的引用。 JTableHeader hdr = (JTableHeader)table.getTableHeader(); // 当单击鼠标单击列头时,调用修饰者的sort()方法。 hdr.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { TableColumnModel tcm = table.getColumnModel(); int vc = tcm.getColumnIndexAtX(e.getX()); int mc = table.convertColumnIndexToModel(vc); // 进行排序 decorator.sort(mc); // 更新状态区 SwingApp.showStatus(headers[mc] + " 排序中"); } }); } final String[] headers = { "品名", "价格/每斤." }; JTable table = new JTable(new Object[][] { {"苹果", "1.2"}, {"芒果", "4"}, {"柠檬", "2.5"},{"香蕉", "0.8"}, {"桔子", "1.8"}, {"西瓜", "0.5"}, {"橘子", "2.5"}, {"樱桃", "3.6"}, {"柚子", "0.8"}, {"葡萄", "2.2"}, }, headers); } class SwingApp extends WindowAdapter { private SwingApp() {} // 该类不能被初始化 public static void launch(final JFrame f, String title, final int x, final int y, final int w, int h) { launch(f,title,x,y,w,h,null); } public static void launch(final JFrame f, String title, final int x, final int y, final int w, int h, String propertiesFilename) { statusArea.setBorder(BorderFactory.createEtchedBorder()); statusArea.setLayout(new FlowLayout(FlowLayout.LEFT,0,0)); statusArea.add(status); status.setHorizontalAlignment(JLabel.LEFT); if(propertiesFilename != null) { resources = ResourceBundle.getBundle( propertiesFilename, Locale.getDefault()); }
f.setTitle(title); f.setBounds(x,y,w,h); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } static public JPanel getStatusArea() { return statusArea; } static public void showStatus(String s) { status.setText(s); } static Object getResource(String key) { if(resources != null) { return resources.getString(key); } return null; } static private JPanel statusArea = new JPanel(); static private JLabel status = new JLabel(" "); static private ResourceBundle resources; }
在上面的代码中,排序修饰者修饰了Swing表原有的模型。当用户在列头上单击鼠标的时候,程序调用修饰者的sort()方法。在研究排序修饰者的代码前,然我们来看一下图7中的类结构图:
图7 排序修饰者的类结构图
由于TableSortDecorator的实例修饰表模型,TableSortDecorator实现了TableModel接口。TableSortDecorator还保持了对它修饰的真实对象的引用。下面是TableSortDecorator的代码:
// TableSortDecorator.java class TableSortDecorator implements TableModel,TableModelListener{ public TableSortDecorator(TableModel model) { this.realModel = model; realModel.addTableModelListener(this); allocate(); } // 下面的九个方法是TableModel接口定义的。 public void addTableModelListener(TableModelListener l) { realModel.addTableModelListener(l); } public Class getColumnClass(int columnIndex) { return realModel.getColumnClass(columnIndex); } public int getColumnCount() { return realModel.getColumnCount(); } public String getColumnName(int columnIndex) { return realModel.getColumnName(columnIndex); } public int getRowCount() { return realModel.getRowCount(); } public boolean isCellEditable(int rowIndex, int columnIndex) { return realModel.isCellEditable(rowIndex, columnIndex); } public void removeTableModelListener(TableModelListener l) { realModel.removeTableModelListener(l); } public Object getValueAt(int row, int column) { return getRealModel().getValueAt(indexes[row], column); } public void setValueAt(Object aValue, int row, int column) { getRealModel().setValueAt(aValue, indexes[row], column); } // getRealModel方法被子类用来引用真实对象。 protected TableModel getRealModel() { return realModel; } // tableChanged()方法是TableModelListener接口定义的, // TableSortDecorator实现了TableModelListener接口。 public void tableChanged(TableModelEvent e) { allocate(); } // 进行冒泡排序 public void sort(int column) { int rowCount = 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 = getRealModel(); 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[getRowCount()]; for(int i=0; i < indexes.length; ++i) { indexes[i] = i; } } private TableModel realModel; // 真实对象 private int indexes[]; }
在进行排序的时候,排序修饰者并没有改变它修饰的真实对象,而是通过了一个数组来保存列的位置。当其他对象向它请求特定行和列的数据的时候,它通过行的值作为数组的索引并返回数组中相应位置的值。通过这种方式,排序修饰者在不改变表结构的前题下将排序功能叠加到了表结构上。TableSortDecorator同时还实现了TableModelListener接口并将自己注册为一个监听者。当真实对象,也就是原有的表对象发出一个表更改的事件后,修饰者将在数组中重新对行的位置进行排列,相应的代码在tableChanged()方法中。还需要注意的是TableSortDecorator有11个公有方法,其中9个方法会被传递给真实对象。
对排序修饰者的进一步改进
上面的排序修饰者可以给任何的表模型增加排序功能。但是TableSortDecorator类的代码重用性能并不是很好,这是因为它实现了两个不应该由它实现的功能:第一个功能是将方法调用传递给真实对象,这是由于其他的表模型修饰者也会使用完全相同的代码,由于该功能的普适性,它应该被移到类层次中较高的层次上;第二个是排序,在上面的例子中使用的是冒泡排序法,而排序的算法在类层次中是非常特殊的部分,因此需要被移到较低的层次上。图8展示了根据上面两点意见修改后的排序修饰者的类图。
图8 经过修改后的排序修饰者的类图
经过修改后TableSortDecorator被分解成三个部分:
? TableModelDecorator:实现了TableModel接口,将方法调用传递给真实对象。
? TableSortDecorator:继承了TableModelDecorator接口,增加了一个抽象方法sort()。
? TableBubbleSortDecorator:继承了TableSortDecorator接口并实现了冒泡排序。
通过分解TableSortDecorator,我们可以重用将方法调用传递给真实对象的代码。将TableModelDecorator中的代码封装起来使我们很容易对表模型添加其它的修饰者,例如过滤修饰者(TableFilterDecorator,)。抽象类TableSortDecorator将sort()方法的实现推迟到该类的子类中实现,因此可以在子类中实现不同的排序算法。下面是这些类的代码:
// TableModelDecorator.java import javax.swing.table.TableModel; import javax.swing.event.TableModelListener; // TableModelDecorator继承了TableModelListener。 // 当表模型发生变化的时候,会调用tableChanged()方法。 // 该方法在抽象类中没有实现,而是在继承该类的子类中实现。 public abstract class TableModelDecorator implements TableModel, TableModelListener { public TableModelDecorator(TableModel model) { this.realModel = model; realModel.addTableModelListener(this); } // 下面的九个方法定义在TableModel接口中。 public void addTableModelListener(TableModelListener l) { realModel.addTableModelListener(l); } public Class getColumnClass(int columnIndex) { return realModel.getColumnClass(columnIndex); } public int getColumnCount() { return realModel.getColumnCount(); } public String getColumnName(int columnIndex) { return realModel.getColumnName(columnIndex); } public int getRowCount() { return realModel.getRowCount(); } public Object getValueAt(int rowIndex, int columnIndex) { return realModel.getValueAt(rowIndex, columnIndex); } public boolean isCellEditable(int rowIndex, int columnIndex) { return realModel.isCellEditable(rowIndex, columnIndex); } public void removeTableModelListener(TableModelListener l) { realModel.removeTableModelListener(l); } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { realModel.setValueAt(aValue, rowIndex, columnIndex); } // getRealModel方法被子类用来引用真实对象。 protected TableModel getRealModel() { return realModel; } private TableModel realModel; // 真实对象 }
注意到TableModelDecorator的构造函数仅仅实现了传递方法调用的功能。
// TableSortDecorator.java import javax.swing.table.TableModel; public abstract class TableSortDecorator extends TableModelDecorator { // 实现TableSortDecorator接口的类必须实现sort()方法。除此之外, // 还需要实现TableModelDecorator中的tableChanged()方法。 abstract public void sort(int column); public TableSortDecorator(TableModel realModel) { super(realModel); } } // TableBubbleSortDecorator.java import javax.swing.table.TableModel; import javax.swing.event.TableModelEvent; public class TableBubbleSortDecorator extends TableSortDecorator { // 该类的构造函数需要一个TableModel的实例作为参数。 // 该类为表模型增加了排序功能。 public TableBubbleSortDecorator(TableModel model) { super(model); allocate(); } // tableChanged()方法是被定义在TableModelListener接口中的。 // TableModelDecorator继承了TableModelListener接口。 public void tableChanged(TableModelEvent e) { allocate(); } // 两个TableModel中的方法被重载。 public Object getValueAt(int row, int column) { return getRealModel().getValueAt(indexes[row], column); } public void setValueAt(Object aValue, int row, int column) { getRealModel().setValueAt(aValue, indexes[row], column); } // 简单的冒泡排序 public void sort(int column) { int rowCount = 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 = getRealModel(); 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[getRowCount()]; for(int i=0; i < indexes.length; ++i) { indexes[i] = i; } } private int indexes[]; }
修饰模型的应用范围
在Java中,开发人员可以通过继承向对象中添加功能。基类可以实现公共功能,子类实现特殊的功能。例如在上面的例子中,开发人员可以实现SortModel和FilterModel类来替代排序和过滤修饰者。但是修饰模型比继承更加灵活,这是因为修饰者和被修饰者之间的关系可以在运行时被改变,而子类和基类之间的关系在编译时就被固定下来。
通常在下面的情况下会使用到修饰模型:
? 如果开发人员需要将不同的简单功能在运行时组合在一起。如果通过继承的方法将对功能不同的组合方法生成大量的子类,而使用修饰模式可以减少代码量。
? 如果开发人员在运行时需要透明地向某个对象添加功能。透明在这里指需要增加功能的对象不能够被修改。
? 如果开发人员需要限制使用一个对象的公共方法。当程序调用被限制的方法时,修饰者可以根据实际情况决定传递方法调用还是抛出异常。 |
|
|
|