UML软件工程组织

如何使用Decorator模式
作者:disneytiger译
Java程序员知道可以通过扩展一个类来改变类的行为和扩展一个类的功能。这个行为被称为继承,它是面向对象编程的一个重要的特性.

举例来说,如果你想得到一个带有边框的Swing类型标签,你可以子类化javax.swing.JLabel类。然而,子类化并不总是有效。当继承不能解决问题的时候,你不得不求助与其它的方式。比如,使用Decorator模式。

这篇文章解释了Decorator模式是什么,并说明什么时候应该子类化,什么时候应该采用Decorate模式。

在Java语言中关键字extends被提供来子类化(扩展)一个类。具有丰富的面向对象编程经验的程序员知道子类化的威力。通过扩展一个类,我们能够改变这个类的行为。以列表1所讲的JBorderLabel类为例,它扩展了javax.swing.JLabel类,除了多了一个边框,它和JLabel类具有相同的外观和行为。

列表 1 -- 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(Graphicsg)

作为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

列表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对象的一个引用。
 
【关于作者】

本系列文章由disneytiger翻译,disneytiger ,java 爱好者,Matrix j2ee翻译小组成员!可以点击http://www.matrix.org.cn/user_view.asp?username=disneytiger查看他的信息.

【原文:】http://www.onjava.com/pub/a/onjava/2001/04/26/j2ee.html?page=1

版权所有:UML软件工程组织