1984年,我以机械工程学位从大学毕业,开始了软件工程师的职业生涯。自学C语言之后,1985年从事了用于Unix的50,000行用户图形界面(GUI)开发。整个过程非常轻松愉快。 1985年底,我的编码工作完成了,之后我考虑开始其他的项目——或者说我认为我可以进行新的项目了。但很快我收到了一系列bug报告和新增的需求,为修正错误我开始努力阅读这50,000行代码。这个工作却非常艰难。 整个程序就像真正用卡片做成的房子一样,几乎每天都会轰然倒下。即使是最微小的变化我也要花费几个小时来恢复程序的稳定性。 可能我碰巧发现了一个重要的软件工程原则:开发阶段轻松愉快,然后项目部署后就去找下一份工作。然而,实际上我的困难源于我对面向对象(object-oriented,OO)软件开发基本原则——封装的无知。我的程序就是个大型的switch语句集合,在不同情况下调用不同的函数——这导致了代码的紧耦合以及整个软件难以适应变化。 在Java设计模式这篇文章,我会讨论策略模式,它可能是最基础的设计模式吧。如果在1984年的时候我知道策略模式的话,有很大一部分工作就可以避免了。 策略模式在GOF的设计模式一书的第一章,作者讨论了若干条OO设计原则,这些原则包括了很多设计模式的核心。策略模式体现了这样两个原则——封装变化和对接口编程而不是对实现编程。设计模式的作者把策略模式定义如下:
策略模式将整个软件构建为可互换部分的松耦合的集合,而不是单一的紧耦合系统。松耦合的软件可扩展性更好,更易于维护且重用性好。 为理解策略模式,我们首先看一下Swing如何使用策略模式绘制组件周围的边框。接着讨论Swing使用策略模式带来的好处,最后说明在你的软件中如何实现策略模式。 Swing 边框几乎所有的Swing组件都可以绘制边框,包括面板、按钮、列表等等。Swing也提供了组件的多种边框类型:bevel(斜面边框),etched(浮雕化边框),line(线边框),titled(标题边框)以及compound(复合边框)等。Swing组件的边框使用
// A hypothetical JComponent.paintBorder methodprotected void paintBorder(Graphics g) { switch(getBorderType()) { case LINE_BORDER: paintLineBorder(g); break; case ETCHED_BORDER: paintEtchedBorder(g); break; case TITLED_BORDER: paintTitledBorder(g); break; ... }} 示例1 绘制Swing边框的错误方式 示例1中 如果你想实现一种新的边框类型,可以想见这样的结果——需要修改 很显然,扩展前面的 可见,如果 那么运用OO思想如何实现呢?使用策略模式解耦 // The actual implementation of the JComponent.paintBorder() methodprotected void paintBorder(Graphics g) { Border border = getBorder(); if (border != null) { border.paintBorder(this, g, 0, 0, getWidth(), getHeight()); }} 示例2 绘制Swing边框的正确方式 前面的 注意
...private Border border;...public void setBorder(Border border) { Border oldBorder = this.border; this.border = border; firePropertyChange('border', oldBorder, border); if (border != oldBorder) { if (border == null || oldBorder == null || !(border.getBorderInsets(this). equals(oldBorder.getBorderInsets(this)))) { revalidate(); } repaint(); }}...public Border getBorder() { return border;} 示例3 Swing组件边框的setter和getter方法 使用 图1为边框和 图1 Swing边框
我们已经知道了 创建新的边框类型图2 新边框类型 图2显示了具有三个面板的Swing应用。每个面板设置自定义的边框,每个边框对应一个 示例4为 import java.awt.*;import javax.swing.*;import javax.swing.border.*;public class HandleBorder extends AbstractBorder { protected Color lineColor; protected int thick; public HandleBorder() { this(Color.black, 6); } public HandleBorder(Color lineColor, int thick) { this.lineColor = lineColor; this.thick = thick; } public void paintBorder(Component component, Graphics g, int x, int y, int w, int h) { Graphics copy = g.create(); if(copy != null) { try { copy.translate(x,y); paintRectangle(component,copy,w,h); paintHandles(component,copy,w,h); } finally { copy.dispose(); } } } public Insets getBorderInsets() { return new Insets(thick,thick,thick,thick); } protected void paintRectangle(Component c, Graphics g, int w, int h) { g.setColor(lineColor); g.drawRect(thick/2,thick/2,w-thick-1,h-thick-1); } protected void paintHandles(Component c, Graphics g, int w, int h) { g.setColor(lineColor); g.fillRect(0,0,thick,thick); // upper left g.fillRect(w-thick,0,thick,thick); // upper right g.fillRect(0,h-thick,thick,thick); // lower left g.fillRect(w-thick,h-thick,thick,thick); // lower right g.fillRect(w/2-thick/2,0,thick,thick); // mid top g.fillRect(0,h/2-thick/2,thick,thick); // mid left g.fillRect(w/2-thick/2,h-thick,thick,thick); // mid bottom g.fillRect(w-thick,h/2-thick/2,thick,thick); // mid right } } 示例4 HandleBorder类
示例5为Swing应用。 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]);="" }=""> 示例5 使用handleBorder 前面的应用创建了三个面板( 回想一下示例2,当 // The following listing is from// javax.swing.border.EtchedBorderpublic 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);} 示例6 从组件获取信息的Swing边框
实现策略模式策略模式相对比较简单,在软件中容易实现:
具体的 你也可以检查一下你现有的类,看看它们是否是紧耦合的,这时可以考虑使用策略对象。通常情况下,这些包括switch语句的需要改进的地方与我在文章开头讨论的非常相似。 作业一些Swing组件的渲染和编辑条件比其他的更加复杂。讨论如何在列表类( 上一次的作业上一次的作业要求重新实现 简单来说,我创建了抽象类 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; }} 示例1H 抽象装饰器类 尽管
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); }} 示例2H 修正的TableSortDecorator 现在可以使用JDK内建的对代理模式的支持实现 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;" }=""> 示例3H 修正的TableBubbleSortDecorator 使用JDK内建的对代理模式的支持和设计良好的基类,通过继承 邮件给我的一封邮件里这样写到: 根据我在树上选择的节点工具栏要显示特定的按钮。我创建了工具栏装饰器,它的构造函数参数为 很多设计模式可以达到功能扩展的目的;比如在Java设计模式中,你已经知道如何使用代理模式,装饰器模式和策略模式来扩展功能。由于他们都可以实现相同的目标(功能扩展),在具体情况下使用哪个模式就很难判断。 装饰器模式的主要解决问题的点在于:在运行时结合多种行为;比如理解代理设计模式一文的“上一次得作业”部分,我展示了Swing表格排序和过滤相结合的方法。 TableSortDecorator sortDecorator = new TableBubbleSortDecorator(table.getModel());TableFilterDecorator filterDecorator = new TableHighPriceFilter(sortDecorator);table.setModel(filterDecorator); 前面的代码中,过滤装饰器装饰了排序装饰器,排序装饰器装饰了表格模型;结果表格模型可以排序和过滤数据。 对于邮件中的问题,使用工具栏按钮与其他行为组合不太合适,所以装饰器模式可能不合适。这种情况代理模式看来更好,在编译阶段而不是运行时就可以获取代理和真实对象的关系,从而扩展功能。 |
|