您的位置:首页 > 其它

设计模式之策略模式(转)

2014-08-22 14:33 162 查看
本文由 ImportNew - hejiani 翻译自 javaworld。欢迎加入Java小组。转载请参见文章末尾的要求。

1984年,我以机械工程学位从大学毕业,开始了软件工程师的职业生涯。自学C语言之后,1985年从事了用于Unix的50,000行用户图形界面(GUI)开发。整个过程非常轻松愉快。

1985年底,我的编码工作完成了,之后我考虑开始其他的项目——或者说我认为我可以进行新的项目了。但很快我收到了一系列bug报告和新增的需求,为修正错误我开始努力阅读这50,000行代码。这个工作却非常艰难。

整个程序就像真正用卡片做成的房子一样,几乎每天都会轰然倒下。即使是最微小的变化我也要花费几个小时来恢复程序的稳定性。

可能我碰巧发现了一个重要的软件工程原则:开发阶段轻松愉快,然后项目部署后就去找下一份工作。然而,实际上我的困难源于我对面向对象(object-oriented,OO)软件开发基本原则——封装的无知。我的程序就是个大型的switch语句集合,在不同情况下调用不同的函数——这导致了代码的紧耦合以及整个软件难以适应变化。

在Java设计模式这篇文章,我会讨论策略模式,它可能是最基础的设计模式吧。如果在1984年的时候我知道策略模式的话,有很大一部分工作就可以避免了。


策略模式

在GOF的设计模式一书的第一章,作者讨论了若干条OO设计原则,这些原则包括了很多设计模式的核心。策略模式体现了这样两个原则——封装变化和对接口编程而不是对实现编程。设计模式的作者把策略模式定义如下:

Define a family of algorithms, encapsulate each one, and make them interchangeable. [The] Strategy [pattern] lets the algorithm vary independently from clients that use it.(策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而变化。)

策略模式将整个软件构建为可互换部分的松耦合的集合,而不是单一的紧耦合系统。松耦合的软件可扩展性更好,更易于维护且重用性好。

为理解策略模式,我们首先看一下Swing如何使用策略模式绘制组件周围的边框。接着讨论Swing使用策略模式带来的好处,最后说明在你的软件中如何实现策略模式。


Swing 边框

几乎所有的Swing组件都可以绘制边框,包括面板、按钮、列表等等。Swing也提供了组件的多种边框类型:bevel(斜面边框),etched(浮雕化边框),line(线边框),titled(标题边框)以及compound(复合边框)等。Swing组件的边框使用
JComponent
类绘制,它是所有Swing组件的基类,实现了所有Swing组件的常用功能。

JComponent
实现了
paintBorder()
,该方法用来绘制组件周围的边框。假如Swing的创建者使用类似示例1的方法实现
paintBorder()
:
// A hypothetical JComponent.paintBorder method
protected 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中
JComponent.paintBorder()
方法在
JComponent
硬编码了边框的绘制。

如果你想实现一种新的边框类型,可以想见这样的结果——需要修改
JComponent
类的至少三个地方:首先,添加与新边框类型相关的新的整数值。第二,switch语句中添加case语句。第三,实现
paintXXXBorder()
方法,
XXX
表示边框类型。

很显然,扩展前面的
paintBorder()
吃力不讨好。你会发现不仅
paintBorder()
很难扩展新类型,而且
JComponent
类不是你首先要修改的位置,它是Swing工具包的一部分,这意味着你将不得不重新编译类和重建全部工具包。你也必须要求你的用户使用你自己的Swing版本而不是标准版,Swing下一次发布后这些工作依然要做。此外,因为你为
JComponent
类添加了新的边框绘制功能,无论你是否喜欢每个Swing组件都可以访问该功能的现状——你不能把你的新边框限制到具体的组件类型。

可见,如果
JComponent
类使用示例1中的switch语句实现其功能,Swing组件就不能被扩展。

那么运用OO思想如何实现呢?使用策略模式解耦
JComponent
与边框绘制的代码,这样无需修改
JComponent
类就实现了边框绘制算法的多样性。使用策略模式封装变化,即绘制边框方法的变化,以及对接口编程而不是对实现编程,提供一个
Border
接口。接下来就看看
JComponent
如何使用策略模式绘制边框。示例2为
JComponent.paintBorder()
方法:
// The actual implementation of the JComponent.paintBorder() method
protected void paintBorder(Graphics g) {
Border border = getBorder();
if (border != null) {
border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
}
}


示例2 绘制Swing边框的正确方式

前面的
paintBorder()
方法绘制了有边框物体的边框。在这种情况下,边框对象封装了边框绘制算法,而不是
JComponent
类。

注意
JComponent
把自身的引用传递给
Border.paintBorder()
,这样边框对象就可以从组件获取信息,这种方式通常称为委托。通过传递自身的引用,一个对象将功能委托给另一对象(查看“理解代理设计模式”一文)。

JComponent
类引用了边框对象,作为
JComponent.getBorder()
方法的返回值,示例3为相关的setter方法。

图2 新边框类型

图2显示了具有三个面板的Swing应用。每个面板设置自定义的边框,每个边框对应一个
HandleBorder
实例。绘图程序通常使用handleBorder对象来移动对象和改变对象大小。

示例4为
HandleBorder
类:
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类

HandleBorder
类继承自
javax.swing.border.AbstractBorder
,覆盖
paintBorder()
getBorderInsets()
方法。尽管
HandleBorder
的实现不太重要,但是我们可以容易地创建新边框类型,因为Swing使用了策略模式绘制组件边框。

示例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

前面的应用创建了三个面板(
javax.swing.JPanel
实例)和三个边框(
HandleBorder
实例)。注意通过调用
JComponent.setBorder()
可以为面板简单设置具体的边框。

回想一下示例2,当
JComponent
调用
Border.paintBorder()
时,组件引用传递给组件的边框——一种委托方式。正如我前面提到的,开发人员经常将策略模式与委托共同使用。该
HandleBorder
类未使用组件引用,但是其他边框会用到引用从组件获取信息。比如示例6为这种类型边框
javax.swing.border.EtchedBorder
paintBorder()
方法:
// 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);
}


示例6 从组件获取信息的Swing边框

javax.swing.border.EtchedBorder.paintBorder()
方法使用它的组件引用获取组件的阴影和高亮颜色信息。


实现策略模式

策略模式相对比较简单,在软件中容易实现:

为你的策略对象定义
Strategy
接口

编写
ConcreteStrategy
类实现
Strategy
接口

在你的
Context
类中,保持对“`Strategy“对象的私有引用。

在你的
Context
类中,实现
Strategy
对象的settter和getter方法。

Strategy
接口定义了
Strategy
对象的行为;比如Swing边框的
Strategy
接口为
javax.swing.Border
接口。

具体的
ConcreteStrategy
类实现了
Strategy
接口;比如,Swing边框的
LineBorder
EtchedBorder
类为
ConcreteStrategy
类。
Context
类使用
Strategy
对象;比如
JComponent
类为
Context
对象。

你也可以检查一下你现有的类,看看它们是否是紧耦合的,这时可以考虑使用策略对象。通常情况下,这些包括switch语句的需要改进的地方与我在文章开头讨论的非常相似。


作业

一些Swing组件的渲染和编辑条件比其他的更加复杂。讨论如何在列表类(
javax.swing.JList
)使用策略模式渲染列表项。

原文链接: javaworld 翻译: ImportNew.com - hejiani

译文链接: http://www.importnew.com/12752.html

[ 转载请保留原文出处、译者和译文链接。]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: