>  기사  >  Java  >  Java 디자인 패턴의 전략 패턴에 대한 자세한 소개

Java 디자인 패턴의 전략 패턴에 대한 자세한 소개

黄舟
黄舟원래의
2017-03-23 11:09:421650검색

1984년에 대학에서 기계공학 학위를 취득하고 소프트웨어 엔지니어로 경력을 시작했습니다. 그는 C 언어를 독학한 뒤 1985년 유닉스용 5만 줄 그래픽 사용자 인터페이스(GUI) 개발에 참여했다. 전체 과정은 매우 편안하고 즐거웠습니다.

1985년 말에 코딩 작업이 완료되었고, 그 후 다른 프로젝트를 시작할까, 아니면 새로운 프로젝트를 시작할 수 있을까 하는 생각이 들었습니다. 하지만 곧 일련의 버그 보고서와 새 추가 요청을 받았고, 오류를 수정하기 위해 이 50,000줄의 코드를 열심히 읽으면서 작업하기 시작했습니다. 이 일은 매우 어렵습니다.

프로그램 전체가 거의 매일 무너지는 카드로 만든 실제 집과 같습니다. 아주 작은 변화라도 프로그램의 안정성을 복원하는 데 몇 시간이 걸렸습니다.

어쩌면 중요한 소프트웨어 엔지니어링 원칙을 발견했을 수도 있습니다. 즉, 개발 단계에서 즐거운 시간을 보내고 프로젝트가 배포되면 다음 작업으로 넘어가는 것입니다. 하지만 제가 겪는 어려움은 사실 객체지향(OO) 소프트웨어 개발의 기본 원칙인 캡슐화에 대한 무지에서 비롯됩니다. 내 프로그램은 다양한 상황에서 다양한 기능을 호출하는 대규모 스위치 문 모음입니다. 이로 인해 코드가 긴밀하게 결합되고 전체 소프트웨어가 변경 사항에 적응하기가 어렵습니다.

Java에서디자인 패턴이 글에서는 가장 기본적인 디자인 패턴이라 할 수 있는 전략 패턴에 대해 이야기해보겠습니다. 내가 1984년에 전략 패턴에 대해 알았더라면 이 작업의 많은 부분을 피할 수 있었을 것입니다.

전략 패턴

GOF 디자인 패턴 책의 첫 번째 장에서 저자는 다양한 디자인 패턴의 핵심을 포함하는 몇 가지 OO 디자인 원칙을 논의합니다. 전략 패턴은 변경 사항 캡슐화구현을 프로그래밍하는 대신 인터페이스 프로그래밍이라는 두 가지 원칙을 구현합니다. 디자인 패턴의 저자는 전략 패턴을 다음과 같이 정의합니다.

알고리즘 계열을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만듭니다. 알고리즘은 이를 사용하는 클라이언트와 독립적으로 다릅니다. (전략 패턴은 일련의 알고리즘을 정의하고 각 알고리즘을 캡슐화하며 상호 교환 가능하게 만듭니다. 전략 패턴을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 달라질 수 있습니다.)

전략 패턴은 긴밀하게 결합된 단일 시스템이 아닌 상호 교환 가능한 부분의 느슨하게 결합된 모음으로 전체 소프트웨어를 구축합니다. 느슨하게 결합된 소프트웨어는 확장성이 더 뛰어나고 유지 관리가 더 쉬우며 재사용이 더 쉽습니다.

전략 패턴을 이해하기 위해 먼저 Swing이 전략 패턴을 사용하여 구성 요소 주위에 경계를 그리는 방법을 살펴보겠습니다. 다음으로 Swing에서 전략 패턴을 사용하면 얻을 수 있는 이점에 대해 논의하고 마지막으로 소프트웨어에서 전략 패턴을 구현하는 방법을 설명합니다.

스윙 테두리

패널, 버튼, 목록 등을 포함한 거의 모든 Swing 구성요소에서 테두리를 그릴 수 있습니다. Swing은 또한 구성 요소에 대해 경사(베벨 테두리), 에칭(엠보싱 테두리), 선(선 테두리), 제목(제목 테두리) 및 복합(복합 테두리) 등 다양한 테두리 유형을 제공합니다. Swing 컴포넌트의 테두리는 모든 Swing 컴포넌트의 기본 클래스이자 모든 Swing 컴포넌트의 공통 기능을 구현하는 JComponent 클래스를 사용하여 그려집니다.

JComponent은 구성 요소 주위에 테두리를 그리는 데 사용되는 paintBorder()을 구현합니다. Swing 작성자가 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과 유사한 방법을 사용한다고 가정합니다. 예제 1 Swing 테두리를 그리는 잘못된 방법

예 1에서 JComponent.paintBorder() 방법은 어렵습니다. -JComponent에 코딩된 테두리 그림입니다.

새 테두리 유형을 구현하려면 결과를 상상할 수 있습니다. JComponent 클래스의 세 곳 이상을 수정해야 합니다. 먼저 새 테두리와 관련된 새 를 추가합니다. 정수 값을 입력하세요. 둘째, switch 문에 Case 문을 추가합니다. 셋째, paintXXXBorder() 메소드를 구현하는데, XXX는 테두리 유형을 나타냅니다.

물론 이전 paintBorder()을 연장하는 것은 고마운 일입니다. 새로운 유형을 확장하는 것이 어려울 뿐만 아니라 paintBorder() 클래스가 수정하는 첫 번째 장소가 아니라 Swing 툴킷의 일부이므로 클래스를 다시 컴파일하고 다시 빌드해야 한다는 것을 알게 될 것입니다. 전체 툴킷. 또한 사용자에게 표준 버전 대신 자신만의 Swing 버전을 사용하도록 요구해야 합니다. 이 작업은 다음 Swing 릴리스 후에도 수행되어야 합니다. 또한 JComponent 클래스에 새로운 테두리 그리기 기능을 추가했기 때문에 이제 모든 Swing 구성 요소가 해당 기능에 액세스할 수 있는 방식이 마음에 들든 새 테두리를 특정 구성 요소 유형으로 제한할 수 없습니다. JComponent

클래스 JComponent가 예제 1의 스위치 문을 사용하여 해당 기능을 구현하는 경우 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.EtchedBorderpaintBorder()方法:

// 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()方法使用它的组件引用获取组件的阴影和高亮颜色信息。

实现策略模式

策略模式相对比较简单,在软件中容易实现:

  1. 为你的策略对象定义Strategy接口

  2. 编写ConcreteStrategy类实现Strategy接口

  3. 在你的Context类中,保持对“`Strategy“对象的私有引用。

  4. 在你的Context类中,实现Strategy对象的settter和getter方法。

Strategy接口定义了Strategy对象的行为;比如Swing边框的Strategy接口为javax.swing.Border接口。

具体的ConcreteStrategy类实现了Strategy接口;比如,Swing边框的LineBorderEtchedBorder类为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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.