1984年,我以機械工程學位從大學畢業,開始了軟體工程師的職業生涯。自學C語言之後,1985年從事了用於Unix的50,000行使用者圖形介面(GUI)開發。整個過程非常輕鬆愉快。
1985年底,我的程式設計工作完成了,之後我考慮開始其他的專案——或者說我認為我可以進行新的專案了。但很快我收到了一系列bug報告和新增的需求,為修正錯誤我開始努力閱讀這50,000行程式碼。這份工作卻非常艱難。
整個程序就像真正用卡片做成的房子一樣,幾乎每天都會轟然倒下。即使是最微小的變化我也要花幾個小時來恢復程式的穩定性。
可能我剛好發現了一個重要的軟體工程原則:開發階段輕鬆愉快,然後專案部署後就去找下一份工作。然而,實際上我的困難源自於我對物件導向(object-oriented,OO)軟體開發基本原則—封裝的無知。我的程式就是一個大型的switch語句集合,在不同情況下呼叫不同的函數——這導致了程式碼的緊密耦合以及整個軟體難以適應變化。
在Java設計模式這篇文章,我會討論策略模式,它可能是最基礎的設計模式吧。如果在1984年的時候我知道策略模式的話,有很大一部分工作就可以避免了。
策略模式
在GOF的設計模式一書的第一章,作者討論了若干條OO設計原則,這些原則包括了許多設計模式的核心。策略模式體現了這樣兩個原則-封裝變更和對介面#程式而不是對實作程式設計。 設計模式的作者把策略模式定義如下:
Define a family of algorithms, encapsulate each one, and make them interchangeable. [The] Strategy [pattern] lets the algorithm vary independently from clients that use it.(策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而變化。)
策略模式將整個軟體建構成可互換部分的鬆散耦合的集合,而不是單一的緊密耦合系統。鬆散耦合的軟體可擴展性更好,更易於維護且重用性好。
為理解策略模式,我們先來看看Swing如何使用策略模式來繪製元件周圍的邊框。接著討論Swing使用策略模式的好處,最後說明在你的軟體中如何實現策略模式。
Swing 邊框
幾乎所有的Swing元件都可以繪製邊框,包括面板、按鈕、清單等等。 Swing也提供了元件的多種邊框類型:bevel(斜面邊框),etched(浮雕化邊框),line(線邊框),titled(標題邊框)以及compound(複合邊框)等。 Swing元件的邊框使用JComponent
類別繪製,它是所有Swing元件的基底類,實作了所有Swing元件的常用功能。
JComponent
實作了paintBorder()
,該方法用來繪製元件周圍的邊框。假如Swing的創建者使用類似範例1的方法實作paintBorder()
:
// A hypothetical JComponent.paintBorder method 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; ... } }
範例1 繪製Swing邊框的錯誤方式
範例1中JComponent .paintBorder()
方法在JComponent
硬編碼了邊框的繪製。
如果你想實作一種新的邊框類型,你可以想見這樣的結果-需要修改JComponent
類別的至少三個地方:首先,新增與新邊框類型相關的新的整數值。第二,switch語句中加入case語句。第三,實作paintXXXBorder()
方法,XXX
表示邊框類型。
很顯然,擴充前面的paintBorder()
吃力不討好。你會發現不僅paintBorder()
很難擴展新類型,而且JComponent
類別不是你首先要修改的位置,它是Swing工具包的一部分,這意味著你將不得不重新編譯類別和重建全部工具包。你也必須要求你的用戶使用你自己的Swing版本而不是標準版,Swing下一次發布後這些工作依然要做。此外,因為你為JComponent
類別添加了新的邊框繪製功能,無論你是否喜歡每個Swing元件都可以存取該功能的現狀——你不能把你的新邊框限製到特定的元件類型。
可見,如果JComponent
類別使用範例1中的switch語句實現其功能,Swing元件就不能被擴充。
那么运用OO思想如何实现呢?使用策略模式解耦JComponent
与边框绘制的代码,这样无需修改JComponent
类就实现了边框绘制算法的多样性。使用策略模式封装变化,即绘制边框方法的变化,以及对接口编程而不是对实现编程,提供一个Border
接口。接下来就看看JComponent
如何使用策略模式绘制边框。示例2为JComponent.paintBorder()
方法:
// The actual implementation of the JComponent.paintBorder() method protected void paintBorder(Graphics g) { Border border = getBorder(); if (border != null) { border.paintBorder(this, g, 0, 0, getWidth(), getHeight()); } }
示例2 绘制Swing边框的正确方式
前面的paintBorder()
方法绘制了有边框物体的边框。在这种情况下,边框对象封装了边框绘制算法,而不是JComponent
类。
注意JComponent
把自身的引用传递给Border.paintBorder()
,这样边框对象就可以从组件获取信息,这种方式通常称为委托。通过传递自身的引用,一个对象将功能委托给另一对象。
JComponent
类引用了边框对象,作为JComponent.getBorder()
方法的返回值,示例3为相关的setter方法。
... 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; }
示例3 Swing组件边框的setter和getter方法
使用JComponent.setBorder()
设置组件的边框时,JComponent
类触发属性改变事件,如果新的边框与旧边框不同,组件重新绘制。getBorder()
方法简单返回Border
引用。
图1为边框和JComponent
类之间关系的类图。
图1 Swing边框
JComponent
类包含Border
对象的私有引用。注意由于Border
是接口不是类,Swing组件可以拥有任意类型的实现了Border
接口的边框(这就是对接口编程而不是对实现编程的含义)。
我们已经知道了JComponent
是如何通过策略模式实现边框绘制的,下面创建一种新边框类型来测试一下它的可扩展性。
创建新的边框类型
图2 新边框类型
图2显示了具有三个面板的Swing应用。每个面板设置自定义的边框,每个边框对应一个HandleBorder
实例。绘图程序通常使用handleBorder对象来移动对象和改变对象大小。
示例4为HandleBorder
类:
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); // upper left g.fillRect(w-thick,0,thick,thick); // upper right g.fillRect(0,h-thick,thick,thick); // lower left g.fillRect(w-thick,h-thick,thick,thick); // lower right g.fillRect(w/2-thick/2,0,thick,thick); // mid top g.fillRect(0,h/2-thick/2,thick,thick); // mid left g.fillRect(w/2-thick/2,h-thick,thick,thick); // mid bottom g.fillRect(w-thick,h/2-thick/2,thick,thick); // mid right } }
示例4 HandleBorder类
HandleBorder
类继承自javax.swing.border.AbstractBorder
,覆盖paintBorder()
和getBorderInsets()
方法。尽管HandleBorder
的实现不太重要,但是我们可以容易地创建新边框类型,因为Swing使用了策略模式绘制组件边框。
示例5为Swing应用。
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("Creating a New Border Type"); 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]); } } }
示例5 使用handleBorder
前面的应用创建了三个面板(javax.swing.JPanel
实例)和三个边框(HandleBorder
实例)。注意通过调用JComponent.setBorder()
可以为面板简单设置具体的边框。
回想一下示例2,当JComponent
调用Border.paintBorder()
时,组件引用传递给组件的边框——一种委托方式。正如我前面提到的,开发人员经常将策略模式与委托共同使用。该HandleBorder
类未使用组件引用,但是其他边框会用到引用从组件获取信息。比如示例6为这种类型边框javax.swing.border.EtchedBorder
的paintBorder()
方法:
// The following listing is from // 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); }
示例6 从组件获取信息的Swing边框
javax.swing.border.EtchedBorder.paintBorder()
方法使用它的组件引用获取组件的阴影和高亮颜色信息。
实现策略模式
策略模式相对比较简单,在软件中容易实现:
为你的策略对象定义
Strategy
接口编写
ConcreteStrategy
类实现Strategy
接口在你的
Context
类中,保持对“`Strategy“对象的私有引用。在你的
Context
类中,实现Strategy
对象的settter和getter方法。
Strategy
接口定义了Strategy
对象的行为;比如Swing边框的Strategy
接口为javax.swing.Border
接口。
具体的ConcreteStrategy
类实现了Strategy
接口;比如,Swing边框的LineBorder
和EtchedBorder
类为ConcreteStrategy
类。Context
类使用Strategy
对象;比如JComponent
类为Context
对象。
你也可以检查一下你现有的类,看看它们是否是紧耦合的,这时可以考虑使用策略对象。通常情况下,这些包括switch语句的需要改进的地方与我在文章开头讨论的非常相似。
作业
一些Swing组件的渲染和编辑条件比其他的更加复杂。讨论如何在列表类(javax.swing.JList
)使用策略模式渲染列表项。
上一次的作业
上一次的作业要求重新实现TableBubbleSortDecorator
。在“装饰你的代码”一文首先讨论了JDK内建的对代理模式的支持。
简单来说,我创建了抽象类Decorator
实现java.lang.reflect.InvocationHandler
接口。Decorator
类引用了装饰对象(或者说代理模式中的真实对象)。示例1H为Decorator
类。
import java.lang.reflect.InvocationHandler; public abstract class Decorator implements InvocationHandler { // The InvocationHandler interface defines one method: // invoke(Object proxy, Method method, Object[] args). That // method must be implemented by concrete (meaning not // abstract) extensions of this class. private Object decorated; protected Decorator(Object decorated) { this.decorated = decorated; } protected synchronized Object getDecorated() { return decorated; } protected synchronized void setDecorated(Object decorated) { this.decorated = decorated; } }
示例1H 抽象装饰器类
尽管Decorator
类实现了InvocationHandler
接口,但是它没有实现该接口的唯一方法invoke(Object proxy, Method method, Object[] methodArguments)
。因为Decorator
类是抽象的,Decorator
的扩展是具体类的话必须实现invoke()
方法。
Decorator
类是所有装饰器的基类。示例2H为Decorator
类的扩展,具体的表排序装饰器。注意TableSortDecorator
没有实现invoke()
方法,它是抽象的。
import javax.swing.table.TableModel; import javax.swing.event.TableModelListener; public abstract class TableSortDecorator extends Decorator implements TableModelListener { // Concrete extensions of this class must implement // tableChanged from TableModelListener abstract public void sort(int column); public TableSortDecorator(TableModel realModel) { super(realModel); } }
示例2H 修正的TableSortDecorator
现在可以使用JDK内建的对代理模式的支持实现TableBubbleSortDecorator
:
import java.lang.reflect.Method; import javax.swing.table.TableModel; import javax.swing.event.TableModelEvent; public class TableBubbleSortDecorator extends TableSortDecorator { private int indexes[]; private static String GET_VALUE_AT = "getValueAt"; private static String SET_VALUE_AT = "setValueAt"; public TableBubbleSortDecorator(TableModel model) { super(model); allocate(); } // tableChanged is defined in TableModelListener, which // is implemented by TableSortDecorator. public void tableChanged(TableModelEvent e) { allocate(); } // invoke() is defined by the java.lang.reflect.InvocationHandler // interface; that interface is implemented by the // (abstract) Decorator class. Decorator is the superclass // of TableSortDecorator. public Object invoke(Object proxy, Method method, Object[] args) { Object result = null; TableModel model = (TableModel)getDecorated(); if(GET_VALUE_AT.equals(method.getName())) { Integer row = (Integer)args[0], col = (Integer)args[1]; result = model.getValueAt(indexes[row.intValue()], col.intValue()); } else if(SET_VALUE_AT.equals(method.getName())) { Integer row = (Integer)args[1], col = (Integer)args[2]; model.setValueAt(args[0], indexes[row.intValue()], col.intValue()); } else { try { result = method.invoke(model, args); } catch(Exception ex) { ex.printStackTrace(System.err); } } return result; } // The following methods perform the bubble sort ... public void sort(int column) { TableModel model = (TableModel)getDecorated(); int rowCount = model.getRowCount(); for(int i=0; i < rowCount; i++) { for(int j = i+1; j < rowCount; j++) { if(compare(indexes[i], indexes[j], column) < 0) { swap(i,j); } } } } private void swap(int i, int j) { int tmp = indexes[i]; indexes[i] = indexes[j]; indexes[j] = tmp; } private int compare(int i, int j, int column) { TableModel realModel = (TableModel)getDecorated(); Object io = realModel.getValueAt(i,column); Object jo = realModel.getValueAt(j,column); int c = jo.toString().compareTo(io.toString()); return (c < 0) ? -1 : ((c > 0) ? 1 : 0); } private void allocate() { indexes = new int[((TableModel)getDecorated()). getRowCount()]; for(int i=0; i < indexes.length; ++i) { indexes[i] = i; } } }
示例3H 修正的TableBubbleSortDecorator
使用JDK内建的对代理模式的支持和设计良好的基类,通过继承Decorator
及实现invoke()
方法很容易实现装饰器。
邮件
给我的一封邮件里这样写到:
根据我在树上选择的节点工具栏要显示特定的按钮。我创建了工具栏装饰器,它的构造函数参数为JToolBar
工具栏。装饰器包含一个showButtonForNode()
方法根据节点改变按钮。我调用在树的选择监听器的valueChanged()
方法中调用showButtonForNode()
方法。
这样使用装饰器模式正确吗?
很多设计模式可以达到功能扩展的目的;比如在Java设计模式中,你已经知道如何使用代理模式,装饰器模式和策略模式来扩展功能。由于他们都可以实现相同的目标(功能扩展),在具体情况下使用哪个模式就很难判断。
装饰器模式的主要解决问题的点在于:在运行时结合多种行为;比如理解代理设计模式一文的“上一次得作业”部分,我展示了Swing表格排序和过滤相结合的方法。
TableSortDecorator sortDecorator = new TableBubbleSortDecorator(table.getModel()); TableFilterDecorator filterDecorator = new TableHighPriceFilter(sortDecorator); table.setModel(filterDecorator);
前面的代码中,过滤装饰器装饰了排序装饰器,排序装饰器装饰了表格模型;结果表格模型可以排序和过滤数据。
对于邮件中的问题,使用工具栏按钮与其他行为组合不太合适,所以装饰器模式可能不合适。这种情况代理模式看来更好,在编译阶段而不是运行时就可以获取代理和真实对象的关系,从而扩展功能。
以上是Java設計模式之策略模式的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

類加載器通過統一的類文件格式、動態加載、雙親委派模型和平台無關的字節碼,確保Java程序在不同平台上的一致性和兼容性,實現平台獨立性。

Java編譯器生成的代碼是平台無關的,但最終執行的代碼是平台特定的。 1.Java源代碼編譯成平台無關的字節碼。 2.JVM將字節碼轉換為特定平台的機器碼,確保跨平台運行但性能可能不同。

多線程在現代編程中重要,因為它能提高程序的響應性和資源利用率,並處理複雜的並發任務。 JVM通過線程映射、調度機制和同步鎖機制,在不同操作系統上確保多線程的一致性和高效性。

Java的平台獨立性是指編寫的代碼可以在任何安裝了JVM的平台上運行,無需修改。 1)Java源代碼編譯成字節碼,2)字節碼由JVM解釋執行,3)JVM提供內存管理和垃圾回收功能,確保程序在不同操作系統上運行。

Javaapplicationscanindeedencounterplatform-specificissuesdespitetheJVM'sabstraction.Reasonsinclude:1)Nativecodeandlibraries,2)Operatingsystemdifferences,3)JVMimplementationvariations,and4)Hardwaredependencies.Tomitigatethese,developersshould:1)Conduc

云计算显著提升了Java的平台独立性。1)Java代码编译为字节码,由JVM在不同操作系统上执行,确保跨平台运行。2)使用Docker和Kubernetes部署Java应用,提高可移植性和可扩展性。

Java'splatformindependenceallowsdeveloperstowritecodeonceandrunitonanydeviceorOSwithaJVM.Thisisachievedthroughcompilingtobytecode,whichtheJVMinterpretsorcompilesatruntime.ThisfeaturehassignificantlyboostedJava'sadoptionduetocross-platformdeployment,s

容器化技術如Docker增強而非替代Java的平台獨立性。 1)確保跨環境的一致性,2)管理依賴性,包括特定JVM版本,3)簡化部署過程,使Java應用更具適應性和易管理性。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

Dreamweaver Mac版
視覺化網頁開發工具

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

WebStorm Mac版
好用的JavaScript開發工具