策略模式(Strategy Pattern)中體現了兩個非常基本的面向對象設計的基本原則:封裝變化的概念;編程中使用接口,而不是對接口實現。策略模式的定義如下:
定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。策略模式使這些算法在客戶端調用它們的時候能夠互不影響地變化。
策略模式使開發人員能夠開發出由許多可替換的部分組成的軟件,并且各個部分之間是弱連接的關系。弱連接的特性使軟件具有更強的可擴展性,易于維護;更重要的是,它大大提高了軟件的可重用性。
為了說明策略模式,我們將首先討論一下在Swing中是如何利用策略模式來繪制組件邊界的,然后討論在Swing中使用策略模式帶來的好處,最后討論如何在軟件中實現策略模式。
Swing邊框
對所有的Swing組件,例如按鈕、列表單等,都還可以繪制邊框。在Swing中提供了各種 邊框類型,例如bevel、etched、line、titled等。Swing組件的邊框是通過JComponent類來繪制的,該類是所有Swing 組件的基類,實現了所有Swing組件公共的功能。在JComponent中有一個paintBorder()方法,該方法為組件繪制邊框。Swing的 開發人員可以象下面的例子中所示那樣來繪制邊框:
// 一段實現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; ... } }
|
請注意上面的代碼只是一種假設,事實上Swing的開發人員并沒有這樣實現 paintBorder()方法。在上面的代碼中,在JComponent中繪制邊框的代碼被直接寫入了paintBorder()方法中,這意味著 JComponent和繪制邊框的功能被緊密地結合在了一起。很自然地大家會聯想到如果需要實現一種新的邊框類型,開發人員必須修改至少三處代碼:首先增 加一個常量,該常量代表新添加的邊框的類型值;其次需要在Switch語句中增加一個case語句;最后開發人員需要實現paintXXXBorder ()方法,其中XXX代表新邊框的名稱。
很顯然要擴展上面paintBorder()方法的功能是一件很困難的事情,不僅 僅是因為開發人員需要增加一種新的邊框類型,更麻煩的是開發人員很難修改JComponent類。JComponent類已經被編譯到了Swing的開發 工具中,如果開發人員想修改它的話,必須獲得Swing的源代碼,修改后重新編譯Swing。同時在用戶的計算機上與需要使用新編譯的Swing API。另外所有的Swing組件都可以使用開發人員新添加的邊框類型。有可能開發人員只希望新的邊框被某些組件使用,但是現在開發人員無法對使用該邊框 的組件進行限制。
開發人員有更好的實現方法嗎?答案就是策略模式。通過策略模式,可以將 JComponent和實現繪制邊框的代碼分離開來,這樣開發人員在增加或修改繪制邊框的代碼使就不需要修改JComponent的代碼。通過應用策略模 式,開發人員將變化的概念(在這個例子中是繪制邊框)封裝起來,然后通過一個Border接口,使程序能夠重用繪制邊框的功能。下面讓我們來看 JComponent是如何利用策略模式來實現繪制邊框的功能的:
// 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封裝了邊框繪制的功能。我們還應該注意到JComponent將一個對自己的引用傳遞給了 Border.paintBorder()方法,這是因為Border的實例必須知道它對應的組件的信息,這種方式通常被稱為委托。通過這種方式,一個對 象可以將功能委托給另一個對象來實現。
在JComponent類中引用了一個Border對象,通過JComponent.getBorder()方法可以獲得該Border對象。下面的代碼演示了如何設定和獲得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; }
|
當開發人員通過JComponent.setBorder()方法設定了一個組件的 邊框后,JComponent類發出一個屬性更新事件。如果新的邊框和以前的邊框不同的話,setBorder()方法就重新繪制邊框。 getBorder()方法僅僅返回對Border對象的引用。圖1顯示了Border的類結構圖:
圖1 Border的類結構圖
通過類結構圖我們可以看到,JComponent類中保存了一個對Border對象的引用。由于Border是一個接口,Swing組件可以使用任何一個實現了Border接口的類。
現在我們已經知道了JComponent是如何利用策略模式來繪制組件的邊框的。下面讓我們通過實現一個新的邊框類型來測試一下它的可擴展性。
實現一個新的邊框類型
圖2中是一個有三個JPanel對象的小程序,每個JPanel對象有各自不同的邊框,每個邊框對應一個HandleBorder實例。
圖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是如何實現的其實并不重要,重要的是由于Swing使用了策略模型,開發人員能夠很方便地增加新的邊框類型。下面的代碼顯示了如 何使用HandleBorder類。在這個例子中創建了三個JPanel對象,并對每個JPanel對象設定一個HandleBorder實例作為邊框。
// 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("實現一個新的邊框類型"); 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]); } } }
|
還記得在上面的例子中曾提到在有些情況下,對組件的引用會作為參數傳遞給 Border.paintBorder()方法。雖然上面的HandleBorder類沒有保存對組件的引用,但是有些情況下Border接口的實現類會 使用到對組件的引用并從中獲得關于組件的信息。例如在EtchedBorder中,paintBorder()方法通過對組件的引用獲得它對應的組件的陰 影和高光色:
// 下面的代碼截取自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); }
|
如何實現策略模型
通過以下步驟,開發人員可以很容易地在軟件中實現策略模型:
1.對策略對象定義一個公共接口。
2.編寫策略類,該類實現了上面的公共接口。
3.在使用策略對象的類中保存一個對策略對象的引用。
4.在使用策略對象的類中,實現對策略對象的set和get方法。
在Swing邊框的例子中,公共接口是javax.swing.Border。策略類是LineBorder、EtchedBorder、HandleBorder等。而使用策略對象的類是JComponent。
轉載:http://fly-net-cn.javaeye.com/blog/78615