《二》
下面的文段展示了装饰模式的两个实例。第1个例子中我们创建了BorderDecorator类,用来装饰一个JComponent组件,使其含有一个边框。当我们把一个用BorderDecorator类装饰过的JLabel添加进JFrame中时,它看起来和JBorderLabel例子中的实例一样;但是,我们不再需要创建子类。更好的是,你可以把所有Swing组件传递给BorderDecorator对象,这样它们就都有边框了。在这个例子中,你写了只一个类(BorderDecorator),就可以改变不同类型的组件表现了。
第2个例子用到了ResizableDecorator类,它可以在任一Swing组件的界面左边添加一个小按钮。当用户点击这个按钮时,按钮所在的组件将缩小至该按钮的位置和大小。
我们来看看BorderDecorator类,该类可以给Swing组件添加一个边框。下面是代码。
package decorator;
import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
public class BorderDecorator extends JComponent {
// decorated component
protected JComponent child;
public BorderDecorator(JComponent component) {
child = component;
this.setLayout(new BorderLayout());
this.add(child);
}
public void paint(Graphics g) {
super.paint(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
注意BorderDecorator类继承自JComponent(这保证了BorderDecorator类也是Swing组件类)并且有个接收JComponent对象作为参数的构造方法,该参数即代表要被装饰的对象。该类还定义了一个叫child的组件来引用要被装饰的JComponent对象。
在构造方法中把要装饰的组件赋值给child,然后把child作为装饰器的子组件。我们把装饰器类的布局设置成了BorderLayout。这意味着被添加的组件(即child所引用的组件)将占据装饰器类的整个区域。
现在来看一眼paint方法。它首先调用了父类的同名方法以绘制该装饰器类自身及其子组件。然后我们在装饰器类的周围画了一个矩形,该矩形的长与高都是根据该装饰器类自身的长与高来设置。下图展示了一个包含3个组件的JFrame。
组件1.一个JBorderLabel的实例
组件2.一个用BorderDecorator装饰的JLabel
组件3.一个用BorderDecorator装饰的JCheckBox
Figure 1 -- comparing subclassing and the Decorator pattern
如上图所示,在外观表现上JBorderLabel与一个用BorderDecorator装饰的JLabel没有区别。这也说明装饰模式(在某些方面)可以作为继承的一个替补。第3个组件也正明了你可以用同一个装饰器来改变一个不同类型的对象的表现。在这个意义下,装饰模式更加优越,因你只需创建一个类(BorderDecorator)来扩展不同类型对象的功能。
下面是Figure 1中程序的代码。
package decorator;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Frame1 extends JFrame {
JBorderLabel label1 =
new JBorderLabel("JLabel Subclass");
BorderDecorator label2 =
new BorderDecorator(new JLabel("Decorated JLabel"));
BorderDecorator checkBox1 =
new BorderDecorator(new JCheckBox("Decorated JCheckBox"));
public Frame1() {
try {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(null);
label1.setBounds(new Rectangle(10, 10, 120, 25));
this.getContentPane().add(label1, null);
label2.setBounds(new Rectangle(10, 60, 120, 25));
this.getContentPane().add(label2, null);
checkBox1.setBounds(new Rectangle(10, 110, 160, 25));
this.getContentPane().add(checkBox1, null);
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Frame1 frame1 = new Frame1();
frame1.setBounds(0, 0, 200, 200);
frame1.setVisible(true);
}
}
再来看第2个例子。
ResizableDecorator是另一种装饰器。它没有覆写父类的paint方法;它只是给组件添加一个按钮,当该按钮被点击时,它能改变和恢复自身大小。ResizableDecorator类的代码如下:
Listing 4 -- the ResizableDecorator class
import javax.swing.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.Rectangle;
public class ResizableDecorator extends JComponent {
// decorated component
protected JComponent child;
private JButton button = new JButton();
boolean minimum = false;
private Rectangle r;
public ResizableDecorator(JComponent component) {
child = component;
this.setLayout(new BorderLayout());
this.add(child);
child.setLayout(null);
button.setBounds(0, 0, 8, 8);
child.add(button);
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
button_actionPerformed(e);
}
});
}
void button_actionPerformed(ActionEvent e) {
if (minimum) {
this.setBounds(r);
}
else {
r = this.getBounds();
this.setBounds(r.x, r.y, 8, 8);
}
minimum = !minimum;
}
}
该类同样继承自JComponent,构造方法以JComponent对象作为参数。除了一个含有引用要被装饰的组件的child变量外,它还声明了一个JButton。
该类构造方法的前3行把传递来的要被装饰的组件作为装饰器类的子组件。
this.setLayout(new BorderLayout());
this.add(child);
然后把JButton作为子组件添加到要被装饰的对象中。子组件(child)的布局为绝对布局,JButton是8 x 8像素大小,然后添加到要被装饰组件的左边。
button.setBounds(0, 0, 8, 8);
child.add(button);
public void actionPerformed(ActionEvent e) {
button_actionPerformed(e);
}
});
当被添加进容器中时,一个被ResizableDecorator装饰过的组件,在其界面左边会有一个小按钮。按钮被点击时,将执行button_actionPerfomed 方法中的代码。首先,该方法会判断minimum的值,这个布尔值用来说明这个装饰组件是否已缩到最小。初始时,这个值是false,所以会执行else代码块
在上面的代码中,把装饰组件的大小赋值给Rectangle对象r并且把装饰组件设置到8 x 8像素大小(结果是装饰界面上只剩下一个小按钮)。然后改变minimum的值: 当第二次点击按钮时,minimum的值是true,if代码段被执行:
r = this.getBounds();
this.setBounds(r.x, r.y, 8, 8);
}
this.setBounds(r);
}
下图展示了一个包含三个装饰组件的JFrame:
点击按钮后——>
下面是上图程序的代码:
package decorator;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Frame2 extends JFrame {
ResizableDecorator label1 =
new ResizableDecorator(new JLabel(" Label1"));
ResizableDecorator button1 =
new ResizableDecorator(new JButton("Button"));
BorderDecorator label2 =
new BorderDecorator(new ResizableDecorator(
new JLabel(" doubly decorated")
));
public Frame2() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Frame2 frame = new Frame2();
frame.setBounds(0, 0, 200, 200);
frame.setVisible(true);
}
private void jbInit() throws Exception {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.getContentPane().setLayout(null);
label1.setBounds(new Rectangle(10, 10, 120, 25));
label2.setBounds(new Rectangle(10, 60, 120, 25));
button1.setBounds(new Rectangle(10, 110, 120, 25));
this.getContentPane().add(label1, null);
this.getContentPane().add(label2, null);
this.getContentPane().add(button1, null);
}
}
注意:你可以使用多个装饰器来装饰一个JComponent,如上label2实例化代码。当然,如果你需要许多装饰器来共同工作,可以考虑使用抽象装饰类,让其它装饰类都派生自它。这样,你可以把通用的方法放在抽象类中。
Summary总结
这篇文章通过两个Swing例子程序比较了继承与装饰模式。尽管在Swing组件中使用装饰模式来改变组件外观是常见的,但是装饰模式并不只是用在改变用户界面上。
P.S:第一次写翻译难免有不当之处,欢迎大家指出文中的错误,如果有(⊙0⊙)。翻译加排版总共花了我5个钟头,第一次这么下功夫做事,呵呵。