使用Decorator模式
Java程序员知道可以通过扩展一个类来改变类的行为和扩展一个类的功能。这个行为被称为继承,它是面向对象编程的一个重要的特性.
举例来说,如果你想得到一个带有边框的Swing类型标签,你可以子类化javax.swing.JLabel类。然而,子类化并不总是有效。当继承不能解决问题的时候,你不得不求助与其它的方式。比如,使用Decorator模式。
这篇文章解释了Decorator模式是什么,并说明什么时候应该子类化,什么时候应该采用Decorate模式。
在Java语言中关键字extends被提供来子类化(扩展)一个类。具有丰富的面向对象编程经验的程序员知道子类化的威力。通过扩展一个类,我们能够改变这个类的行为。以列表1所讲的JBorderLabel类为例,它扩展了javax.swing.JLabel类,除了多了一个边框,它和JLabel类具有相同的外观和行为。
the JBorderLabel class, an example of subclassing
package decorator;
import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.Icon;
public class JBorderLabel extends JLabel {
public JBorderLabel() {
super();
}
public JBorderLabel(String text) {
super(text);
}
public JBorderLabel(Icon image) {
super(image);
}
public JBorderLabel(String text, Icon image, int horizontalAlignment) {
super(text, image, horizontalAlignment);
}
public JBorderLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
要理解JBorderLabel如何工作,我们首先要了解Swing绘它的组件的原理。 JLabel类同其它的Swing组件一样,继承至javax.swing.Jcomponent.Swing。它们都是通过调用JComponent组件的paint方法来画界面。我们可以通过重载JComponent的公开方法paint来修改一个组件画界面的行为。下面是一个JComponent的paint方法的定义。
public void paint(Graphics g)
作为paint方法的参数传进来的对象Graphics是一个绘图面板。为了优化绘图这个操作,paint方法被分割成三个具有保护(protected)属性的方法:paintComponent, paintBorder, paintChildren。paint方法调用这三个方法同时将它接受到的Graphics实例传递给这三个方法。下面是这三个函数的一个声明:
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
你可以通过重载这些方法来定制你自己的绘制组件的方式。
JBorderLabel类重载了javax.swing.JComponent的paintComponent方法。类JborderLabel的paintComponent方法首先调用父类的paintComponent得到一个Jlabel.它保持了自己的长和宽,通过java.awt.Graphics实例的drawRect方法画一个矩形。图1显示了一个JBorderLabel类的一个实例。正如图所示的一样,出了多了一个边框外,它和JLabel外观是一样的。
这个例子中子类化工作得相当好。我们来看看子类化不合适的案例。如果你打算让其它的组件都具有同一行为(比如:画一个边框),那么你必须做很多的子类化操作。在列表1中,子类化看起来很简单是因为例子中你仅仅需要重载一个方法。当你有太多的子类需要创建时你的代码将变得很复杂,出错的机会也增大了。(你必需要复制(reproduce)你的子类需要支持的父类的构造函数,就像JBorderLabel类一样)。在这个时候,最好的方式是使用Decorator模式。
Decorator模式
在Erich Gamma等编写的《Design Patterns : Elements of Reusable Object-Oriented Software》一书中,Decorator模式被归类为结构模式。Decorator模式提供了子类化的一个替代方案。子类化和Decorator模式的主要区别是:采用子类化,你同一个类打交道;使用Decorator模式,你可以动态的修改多个对象。当你扩展(Extend)一个类的时候,你对儿子类的改变将会影响到这个儿子类所有的实例。采用Decorator模式,你所作的改变只会影响到你打算改变的那个对象。
理解JComponent类对于书写装饰者类很重要,我们通过这个装饰者类来改变Swing组件的用户界面。在前面部分我解释了JComponent是如何画它的用户界面的,我们可以通过文档查找来了解这个类的所有的成员。我们要意识到JComponent有子组件,当JComponent被画的时候,这些子组件也将被画。
创建一个从JComponent扩展过来的Swing装饰者。这个装饰者的构造函数接受一个类型为JComponent的参数。可以传递任一一个需要改变行为的Swing对象给装饰者。这个装饰者将传进来的这个组件作为自己的子组件。并不是直接将Swing组件增加到JFrame或JPannel或其它容器,而是先将Swing组件添加到修饰者,再把修饰者增加给容器类。因为一个修饰者也是一个JComponent类型的对象,容器不能将他们区分开来。这个装饰者是这个容器的一个子组件。当容器让装饰者重画的时候,这个装饰者paint方法将被调用。
举例来说,假设你有一个JLabel类,你打算把它传给一个称之为frame1的JFrame类。使用如下相似的代码:
frame.getContentPane().add(new JLabel("a label"));用MyDecorator来修饰JLabel的代码和它很相似,如下:(记住,MyDecorator类的构造函数应该接受一个JComponent类的输入参数)
frame.getContentPane().add(new MyDecorator(new JLabel("a label")));
这篇文章示例了两个Decorator模式的例子。第一个例子是BorderDecorator.这个类被用来修饰JComponent,以便让JComponent具有一个边框。当把一个由BorderDecorator修饰的JLabel增加到JFrame,这个JLabel看起来就像JBorderLabel的一个实例。这说明,子类化不是必须的。更好的是,你能够传递任何一个Swing组件给BorderDecorator,这些被传递的组件都会给予一个边框。在这个例子中,通过创建了一个类BorderDecorator来改变不同类型的实例的行为。
第二个例子是ResizableDecorator。这个装饰着为每一个传给它的Swing组件增加一个小按钮到左上角。当用户点击这个按钮的时候,这个组件将会最小化为这个按钮。
BorderDecorator类
我们以BorderDecorator开始。这个类表示的装饰者会为Swing组件增加一个边框。示例代码如列表2
the BorderDecorator class
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,它的构造函数接受一个JComponet类型的参数。这个BorderDecorator类有一个类型为JComponent的属性child,它是传进来的Jcomponent对象的一个引用。
构造函数将被修饰的组件赋值给child变量,并且将这个组件作为一个子组件增加给装饰者。注意,我们使用了BorderLayout作为装饰者的布局。这意味着被增加的这个JComponent将占据这个装饰者的整个区域。
现在,让我们关注一下paint方法。它首先调用了父类的paint方法。这-步操作将画出装饰者,在第一次得到装饰者的长宽以后,我们在装饰者所在区域的边缘画一个长方形。
Figure 1 shows a JFrame with three components:
• An instance of JBorderLabel.
• A decorated JLabel.
• A decorated JCheckBox.
Figure 1 -- comparing subclassing and the Decorator pattern
JBorderLabel的一个实例和一个被装饰过的JLabel对象实例从外表看没有什么不同。这说明,Decorator模式可以作为子类化的一个替代方案。第三个组件证明,你能够使用同一个装饰者去扩展不同对象的实例的行为。从这点来看,装饰者是一个(超类)superior,因为仅仅需要创建一个类(BorderDecorator)就可以扩张不同类型的多个对象的功能。
显示了图1中的JFrame类的实现代码。
-- using the BorderDecorator class
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);
getContentPan