策略模式(Strategy Pattern)中體現(xiàn)了兩個非常基本的面向?qū)ο笤O(shè)計(jì)的基本原則:封裝變化的概念;編程中使用接口,而不是對接口實(shí)現(xiàn)。策略模式的定義如下:
定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。策略模式使這些算法在客戶端調(diào)用它們的時候能夠互不影響地變化。
策略模式使開發(fā)人員能夠開發(fā)出由許多可替換的部分組成的軟件,并且各個部分之間是弱連接的關(guān)系。弱連接的特性使軟件具有更強(qiáng)的可擴(kuò)展性,易于維護(hù);更重要的是,它大大提高了軟件的可重用性。
為了說明策略模式,我們將首先討論一下在Swing中是如何利用策略模式來繪制組件邊界的,然后討論在Swing中使用策略模式帶來的好處,最后討論如何在軟件中實(shí)現(xiàn)策略模式。
Swing邊框
對所有的Swing組件,例如按鈕、列表單等,都還可以繪制邊框。在Swing中提供了各種邊框類型,例如bevel、etched、line、titled等。Swing組件的邊框是通過JComponent類來繪制的,該類是所有Swing組件的基類,實(shí)現(xiàn)了所有Swing組件公共的功能。在JComponent中有一個paintBorder()方法,該方法為組件繪制邊框。Swing的開發(fā)人員可以象下面的例子中所示那樣來繪制邊框:
// 一段實(shí)現(xiàn)paintBorder()方法代碼
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;
...
}
}
|
請注意上面的代碼只是一種假設(shè),事實(shí)上Swing的開發(fā)人員并沒有這樣實(shí)現(xiàn)paintBorder()方法。在上面的代碼中,在JComponent中繪制邊框的代碼被直接寫入了paintBorder()方法中,這意味著JComponent和繪制邊框的功能被緊密地結(jié)合在了一起。很自然地大家會聯(lián)想到如果需要實(shí)現(xiàn)一種新的邊框類型,開發(fā)人員必須修改至少三處代碼:首先增加一個常量,該常量代表新添加的邊框的類型值;其次需要在Switch語句中增加一個case語句;最后開發(fā)人員需要實(shí)現(xiàn)paintXXXBorder()方法,其中XXX代表新邊框的名稱。
很顯然要擴(kuò)展上面paintBorder()方法的功能是一件很困難的事情,不僅僅是因?yàn)殚_發(fā)人員需要增加一種新的邊框類型,更麻煩的是開發(fā)人員很難修改JComponent類。JComponent類已經(jīng)被編譯到了Swing的開發(fā)工具中,如果開發(fā)人員想修改它的話,必須獲得Swing的源代碼,修改后重新編譯Swing。同時在用戶的計(jì)算機(jī)上與需要使用新編譯的Swing API。另外所有的Swing組件都可以使用開發(fā)人員新添加的邊框類型。有可能開發(fā)人員只希望新的邊框被某些組件使用,但是現(xiàn)在開發(fā)人員無法對使用該邊框的組件進(jìn)行限制。
開發(fā)人員有更好的實(shí)現(xiàn)方法嗎?答案就是策略模式。通過策略模式,可以將JComponent和實(shí)現(xiàn)繪制邊框的代碼分離開來,這樣開發(fā)人員在增加或修改繪制邊框的代碼使就不需要修改JComponent的代碼。通過應(yīng)用策略模式,開發(fā)人員將變化的概念(在這個例子中是繪制邊框)封裝起來,然后通過一個Border接口,使程序能夠重用繪制邊框的功能。下面讓我們來看JComponent是如何利用策略模式來實(shí)現(xiàn)繪制邊框的功能的:
// Swing中paintBorder()方法的源代碼
protected void paintBorder(Graphics g) {
Border border = getBorder();
if (border != null) {
border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
}
}
|
上面的paintBorder()方法通過一個border對象繪制了組件的邊框。這樣border對象替代了前一個例子中的JComponent封裝了邊框繪制的功能。我們還應(yīng)該注意到JComponent將一個對自己的引用傳遞給了Border.paintBorder()方法,這是因?yàn)锽order的實(shí)例必須知道它對應(yīng)的組件的信息,這種方式通常被稱為委托。通過這種方式,一個對象可以將功能委托給另一個對象來實(shí)現(xiàn)。
在JComponent類中引用了一個Border對象,通過JComponent.getBorder()方法可以獲得該Border對象。下面的代碼演示了如何設(shè)定和獲得Border對象:
...
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;
}
|
當(dāng)開發(fā)人員通過JComponent.setBorder()方法設(shè)定了一個組件的邊框后,JComponent類發(fā)出一個屬性更新事件。如果新的邊框和以前的邊框不同的話,setBorder()方法就重新繪制邊框。getBorder()方法僅僅返回對Border對象的引用。圖1顯示了Border的類結(jié)構(gòu)圖:

圖1 Border的類結(jié)構(gòu)圖
通過類結(jié)構(gòu)圖我們可以看到,JComponent類中保存了一個對Border對象的引用。由于Border是一個接口,Swing組件可以使用任何一個實(shí)現(xiàn)了Border接口的類。
現(xiàn)在我們已經(jīng)知道了JComponent是如何利用策略模式來繪制組件的邊框的。下面讓我們通過實(shí)現(xiàn)一個新的邊框類型來測試一下它的可擴(kuò)展性。
實(shí)現(xiàn)一個新的邊框類型
圖2中是一個有三個JPanel對象的小程序,每個JPanel對象有各自不同的邊框,每個邊框?qū)?yīng)一個HandleBorder實(shí)例。

圖2 新的邊框類型
// HandleBorder.java
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);
g.fillRect(w-thick,0,thick,thick);
g.fillRect(0,h-thick,thick,thick);
g.fillRect(w-thick,h-thick,thick,thick);
g.fillRect(w/2-thick/2,0,thick,thick);
g.fillRect(0,h/2-thick/2,thick,thick);
g.fillRect(w/2-thick/2,h-thick,thick,thick);
g.fillRect(w-thick,h/2-thick/2,thick,thick);
}
}
|
HandleBorder類繼承了javax.swing.border.AbstractBorder類并重寫了paintBorder()和getBorderInsets()。HandleBorder是如何實(shí)現(xiàn)的其實(shí)并不重要,重要的是由于Swing使用了策略模型,開發(fā)人員能夠很方便地增加新的邊框類型。下面的代碼顯示了如何使用HandleBorder類。在這個例子中創(chuàng)建了三個JPanel對象,并對每個JPanel對象設(shè)定一個HandleBorder實(shí)例作為邊框。
// Test.java
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("實(shí)現(xiàn)一個新的邊框類型");
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]);
}
}
}
|
還記得在上面的例子中曾提到在有些情況下,對組件的引用會作為參數(shù)傳遞給Border.paintBorder()方法。雖然上面的HandleBorder類沒有保存對組件的引用,但是有些情況下Border接口的實(shí)現(xiàn)類會使用到對組件的引用并從中獲得關(guān)于組件的信息。例如在EtchedBorder中,paintBorder()方法通過對組件的引用獲得它對應(yīng)的組件的陰影和高光色:
// 下面的代碼截取自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);
}
|
如何實(shí)現(xiàn)策略模型
通過以下步驟,開發(fā)人員可以很容易地在軟件中實(shí)現(xiàn)策略模型:
1.對策略對象定義一個公共接口。
2.編寫策略類,該類實(shí)現(xiàn)了上面的公共接口。
3.在使用策略對象的類中保存一個對策略對象的引用。
4.在使用策略對象的類中,實(shí)現(xiàn)對策略對象的set和get方法。
在Swing邊框的例子中,公共接口是javax.swing.Border。策略類是LineBorder、EtchedBorder、HandleBorder等。而使用策略對象的類是JComponent。
|