您现在的位置是:首页-> 米鼠技术 ->命令模式(Commandnbsp;Pattern)

命令模式(Commandnbsp;Pattern)


        

第30章 命令模式


    

描述:


    通常,面向对象应用程序是由一组能够提供有限的、专注于功能的相互交互的对象集合组成。为了响应用户的交互动作,应用程序执行一系列的处理。为了响应用户的请求,应用程序使用不同的对象提供的服务处理请求。根据实现,应用可以指定一个对象,这个指定的对象可以调用不同对象上的操作,被称为调用者(invoker)。(译者注:其实个人认为这个invoker在这里主动地成分更大,它是偏向于调用其他对象,这个区别就像英语employ这个动词是雇佣的意思,employee是员工??被雇佣的对象,而employer是雇主??雇佣的主动者,那么在这里的invoker是一个道理)。调用者(invoker)可以看作为客户应用程序的一部分。那些包含提供处理请求服务实现的对象集合成为接受对象(Receiver Object)。
 
Figure 30.1: Object Interaction?Before Applying the Command Pattern 
    在这种设计中,发出请求的应用程序和提供处理请求服务的接受对象(Receiver Object)集合之间彼此是紧密关联的,因为它们之间直接相互交互。这导致了在调用者(invoker)实现中会包含大量的if条件语句。
 
  1.     … 
  2. if (RequestType=TypeA){ 
  3.   //do something 
  4. if (RequestType=TypeB){ 
  5.   //do something 
  6.      … 

   当一种新的处理类型需要加入的应用时,现存的代码需要进行修改??这违背了面向对象开放?关闭的基本原则。
  1.      … 
  2. if (RequestType=TypeA){ 
  3.   //do something 
  4.      … 
  5. if (RequestType=NewType){ 
  6.   //do something 
  7.      … 

   使用命令模式,代表客户发布请求的调用者(invoker)和服务的处理者??接受对象(Receiver Object)之间不具有耦合性。命令模式推荐创建一个可以为响应客户请求而执行处理或者采取动作的抽象。指定这个抽象声明一个被其它不同的具体的实现者所实现的共同接口,这些具体的实现者称之为命令对象(Command Objects)。每一个命令对象(Command Object)代表一种不同的客户请求和对其进行的相应处理。在图30.2中,command接口代表了这个抽象。它声明一个excute方法,它由两个具体的实现者(类)??ConcreteCommand_1和ConcreteCommand_2。
 
Figure 30.2: Command Object Hierarchy 
一个给定的命令对象(command Object)负责提供处理它所代表的请求的功能,但是命令对象并不包含此功能的真实实现,它是通过使用接受对象(Receiver Object)来提供处理功能。(如图30.3)
 
Figure 30.3: Class Association??After the Command Pattern Is Applied 
当客户应用程序响应用户(或者其他应用)的交互而需要提供服务时:
(1)    客户创建需要的接受对象(Receiver Object)。
(2)    客户创建一个相应的命令对象(Command Object),并用在步骤1中创建的接受对象(Receiver Object)配置这个命令对象。
(3)    客户创建一个调用者的实例(invoker), 并用在步骤2中创建的命令对象(Command Object)配置调用者。
(4)    调用者调用命令对象(Command Object)上的execute方法。
(5)    作为execute方法实现的一部分,特定的命令对象(Command Object)调用它所包含的接受对象上的必要方法来提供需要的服务。
在新的设计中:
(1)    客户/调用者(invoker)不直接与接受对象(Receiver)进行交互,它们之间完全不耦合。
(2)    当应用程序需要提供新的类型的时候,可以增加一个新的命令(Command)对象。这不需要修改调用者(invoker)的代码。因此新的设计保证了面向对象的开放?封闭的原则。
(3)    因为一个请求被指定为对象的形式,那么就存在以下可能性:
1)    把命令对象保存到持久介质上。
以后进行执行。
应用反向处理,实现恢复特性。
2)    把不同的命令对象分组成一个独立的单元。
以下是一个FTP(File Transfer Protocol)客户事例应用程序,对于在实际应用程序中如何应用命令模式提供了很好的参考。
        

例子1:


        让我们建立一个应用程序来模拟FTP客户端的工作。在Java中,简单的FTP客户用户接口可以使用如下组件设计。
(1)    两个JList对象用于本地和远程文件系统的显示。
(2)    四个JButton对象用于初始不同的请求类型:上传、下载、删除和退出。
用户接口组件摆列在一个frame中,如图30.4
 
Figure 30.4: Simple FTP Client UI Display 
每一个JButton对象创建的时候,实现了ActionListener接口的Button-Handler类的一个事例是一个ActionListener的集合。
  1. public class FTPGUI extends JFrame { 
  2.      … 
  3.      … 
  4.   //Create buttons 
  5.   btnUpload = new JButton(FTPGUI.UPLOAD); 
  6.   btnUpload.setMnemonic(KeyEvent.VK_U); 
  7.      … 
  8.      … 
  9.   ButtonHandler vf = new ButtonHandler(); 
  10.   btnUpload.addActionListener(vf); 
  11.   btnDownload.addActionListener(vf); 
  12.   btnDelete.addActionListener(vf); 
  13.   btnExit.addActionListener(vf); 
  14.      … 
  15.      … 
  16. }//end of class 

    因为这个相同的ButtonHandler实例是所有JButton对象的Action-Listener的一个集合,actionPerformed方法被所有的JButton对象调用。因此,ButtonHandler对象必须检测哪一个按钮被按下,并执行相应的处理。
从Listing30.1中可以看到,actionPerformed方法的代码因为条件语句很不规则。当有很多按钮和菜单对象需要添加到FTP用户界面时,actionPerformed方法的代码就很混乱。同时,一个新的按钮对象被添加时,现在的actionPerformed方法的代码必须修改。这违反了面向对象的开放??关闭原则。
Listing 30.1: ButtonHandler Class 
  1. class ButtonHandler implements ActionListener { 
  2.  public void actionPerformed(ActionEvent e) { 
  3.    //if statements - for different types of client requests 
  4.    if (e.getActionCommand().equals(FTPGUI.EXIT)) { 
  5.     System.exit(1); 
  6.    } 
  7.    if (e.getActionCommand().equals(FTPGUI.UPLOAD)) { 
  8.     int index = localList.getSelectedIndex(); 
  9.     String selectedItem = 
  10.       localList.getSelectedValue().toString(); 
  11.     ((DefaultListModel) localList.getModel()).remove( 
  12.       index); 
  13.     ((DefaultListModel) remoteList.getModel()). 
  14.     addElement(selectedItem); 
  15.    } 
  16.    if (e.getActionCommand().equals(FTPGUI.DOWNLOAD)) { 
  17.     int index = remoteList.getSelectedIndex(); 
  18.     String selectedItem = 
  19.      remoteList.getSelectedValue().toString(); 
  20.     ((DefaultListModel) remoteList.getModel()).remove( 
  21.      index); 
  22.     ((DefaultListModel) localList.getModel()). 
  23.     addElement(selectedItem); 
  24.    } 
  25.    if (e.getActionCommand().equals(FTPGUI.DELETE)) { 
  26.     int index = localList.getSelectedIndex(); 
  27.     if (index >= 0) { 
  28.      ((DefaultListModel) localList.getModel()). 
  29.      remove(index); 
  30.     } 
  31.     index = remoteList.getSelectedIndex(); 
  32.      if (index >= 0) { 
  33.       ((DefaultListModel) remoteList.getModel()). 
  34.       remove(index); 
  35.      } 
  36.     } 
  37.   } 

    让我们使用命令模式重新设计这个应用。应用命令模式,我们把不同按钮对象相关联的功能抽象一个Command-face接口。
  1. interface CommandInterface { 
  2.   public void processEvent(); 

   不同的按钮对象可以实现这个接口,独自成为自己的命令对象。但是,这是不推荐的,因为:
(1)    JButton类是一个高复用的类,在用Java Swing库创建应用用户界面的许多场合,JButton类都被使用。实现特定的CommandInterface接口可能不是适用于所有的情况。
(2)    如果JButton类实现了CommandInterface接口,它需要实现处理在用户界面(接口)上不同的JButton对象所对应的诸如上传、下载等不同请求的功能。这会增加不相关的功能到JButton类中??导致低聚和。另外:
1)    这会导致不规则的条件语句。
2)    每增加一个新的按钮到用户界面上(接口),它都需要改变现在的processEvent方法的实现。这违背了面向对象的开放?关闭的原则。
为了解决这些问题,对应不同请求类型的按钮类被设计为JButton类的子类(Listing30.2)。这些子类实现CommandInterface接口。作为processEvent方法实现的一部分,JButton类的每一个子类提供处理它所代表的不同请求的功能。
Listing 30.2: JButton Subclasses to Perform Different FTP Operations 
  1. class UploadButton extends JButton 
  2.   implements CommandInterface { 
  3.   public void processEvent() { 
  4.     int index = localList.getSelectedIndex(); 
  5.     String selectedItem = 
  6.      localList.getSelectedValue().toString(); 
  7.     ((DefaultListModel) localList.getModel()).remove( 
  8.      index); 
  9.     ((DefaultListModel) remoteList.getModel()).addElement( 
  10.      selectedItem); 
  11.   } 
  12.   public UploadButton(String name) { 
  13.     super(name); 
  14.   } 
  15. class DownloadButton extends JButton 
  16.   implements CommandInterface { 
  17.   public void processEvent() { 
  18.     int index = remoteList.getSelectedIndex(); 
  19.     String selectedItem = 
  20.      remoteList.getSelectedValue().toString(); 
  21.     ((DefaultListModel) remoteList.getModel()).remove( 
  22.      index); 
  23.     ((DefaultListModel) localList.getModel()).addElement( 
  24.      selectedItem); 
  25.   } 
  26.   public DownloadButton(String name) { 
  27.     super(name); 
  28.   } 
  29. class DeleteButton extends JButton 
  30.   implements CommandInterface { 
  31.   public void processEvent() { 
  32.     int index = localList.getSelectedIndex(); 
  33.     if (index >= 0) { 
  34.      ((DefaultListModel) localList.getModel()).remove( 
  35.        index); 
  36.     } 
  37.     index = remoteList.getSelectedIndex(); 
  38.     if (index >= 0) { 
  39.      ((DefaultListModel) remoteList.getModel()).remove( 
  40.        index); 
  41.     } 
  42.   } 
  43.   public DeleteButton(String name) { 
  44.      super(name); 
  45.   } 
  46. class ExitButton extends JButton 
  47.   implements CommandInterface { 
  48.   public void processEvent() { 
  49.     System.exit(1); 
  50.   } 
  51.   public ExitButton(String name) { 
  52.     super(name); 
  53.   } 
  54.  


Figure 30.5: FTP UI?Command Object Hierarchy 
FTP用户界面可以使用这些新的JButton的子类来构成。应用程序其他部分保持不变,actionPerformed方法非常简单,只有两行代码。
  1. class buttonHandler implements ActionListener { 
  2.   public void actionPerformed(ActionEvent e) { 
  3.     CommandInterface CommandObj = 
  4.      (CommandInterface) e.getSource(); 
  5.     CommandObj.processEvent(); 
  6.   } 
    
    在新的设计中,不管是增加新的按钮还是菜单项,需要创建一个实现了CommandInterface接口新的命令对象(Command Object)。一个新的命令对象可以在不需要修改现在的actionPerformed方法的情况下,无缝的添加到应用中。这种设计的负面影响是导致了大量的类。

例子2:


让我们建立一个在图书馆中管理项目数据库中项目的应用程序。特定的图书馆包括如下项目:书、CD、video和DVD。这些项目被分成类别,一个给定项目可以属于一个或者多个类别。例如,
一个新的电影video可以同时属于video类别和新发布(NewReleases)类别。
    让我们定义两个类??Item和Category?(Listing30.3)分别代表数据库中特定的条目和条目的分类。
Listing 30.3: Item and Category Classes 
  1. public class Item { 
  2.   private HashMap categories; 
  3.   private String desc; 
  4.   public Item(String s) { 
  5.     desc = s; 
  6.     categories = new HashMap(); 
  7.   } 
  8.   public String getDesc() { 
  9.     return desc; 
  10.   } 
  11.   public void add(Category cat) { 
  12.     categories.put(cat.getDesc(), cat); 
  13.   } 
  14.   public void delete(Category cat) { 
  15.     categories.remove(cat.getDesc()); 
  16.   } 
  17. public class Category { 
  18.   private HashMap items; 
  19.   private String desc; 
  20.   public Category(String s) { 
  21.     desc = s; 
  22.     items = new HashMap(); 
  23.   } 
  24.   public String getDesc() { 
  25.     return desc; 
  26.   } 
  27.   public void add(Item i) { 
  28.     items.put(i.getDesc(), i); 
  29.     System.out.println("Item '" + i.getDesc() + 
  30.                        "' has been added to the '" + 
  31.                        getDesc() + "' Category "); 
  32.   } 
  33.   public void delete(Item i) { 
  34.     items.remove(i.getDesc()); 
  35.     System.out.println("Item '" + i.getDesc() + 
  36.                        "' has been deleted from the '" + 
  37.                        getDesc() + "' Category "); 
  38.   } 
 

Figure 30.6: Item-Category Association 
从Item和Category类的设计和实现可以看到,Category对象维护了当前成员项目的一个列表。同样,一个Item对象也维护了一个它所归属的类别的类表。为了简单,让我们假定图书馆项目管理应用仅处理增加和删除项目。应用命令模式,处理增加和删除项目请求的动作可以设计为共同接口CommandInterface的实现。CommandInterface提供一个处理增加和删除项目请求的抽象。CommandInterface实现者??AddCommand和DeleteCommand??在图30.7中分别代表了增加和删除项目的请求。
 
Figure 30.7: Command Object Hierarchy 
Let us further define an invoker ItemManager class.
  1. public class ItemManager { 
  2.   CommandInterface command; 
  3.   public void setCommand(CommandInterface c) { 
  4.     command = c; 
  5.   } 
  6.   public void process() { 
  7.     command.execute(); 
  8.   } 

ItemManager类:
(1)    包含Command对象
(2)    调用Command对象的execute方法,作为自己process方法实现的一部分。
(3)    提供setCommand方法允许客户对象配置Command对象
客户CommandTest使用调用者(invoker) ItemManager得到增加和删除项目请求的处理。
应用程序流程:
增加和删除一个项目,客户CommandTest(Listing 30.4)
(1)    创建必须的Item和Category对象,这些对象扮演接受(Receiver)对象的角色。
(2)    创建一个对应现在请求的适当的命令(Command)对象,把步骤1中创建的接受(Receiver)对象在命令(Command)对象创建时传递给它。
(3)    创建一个ItemManager类的一个实例,用在步骤2中创建的命令(Command)对象配置它。
(4)    调用ItemManager上的process()方法,ItemManager调用命令(Command)对象上的execute方法。命令(Command)对象翻过来调用需要的接受(Receiver)对象上的方法。不同的Item和Category接受(Receiver)对象执行真正的请求处理。为了保持例子的简单,在实现中没有数据库访问逻辑的实现。Item和Category对象的实现只是简单的现实信息。
Listing 30.4: Client CommandTest Class 
  1. public class CommandTest { 
  2.   public static void main(String[] args) { 
  3.     //Add an item to the CD category 
  4.     //create Receiver objects 
  5.     Item CD = new Item("A Beautiful Mind"); 
  6.     Category catCD = new Category("CD"); 
  7.     //create the command object 
  8.     CommandInterface command = new AddCommand(CD, catCD); 
  9.     //create the invoker 
  10.     ItemManager manager = new ItemManager(); 
  11.     //configure the invoker 
  12.     //with the command object 
  13.     manager.setCommand(command); 
  14.     manager.process(); 
  15.     //Add an item to the CD category 
  16.     CD = new Item("Duet"); 
  17.     catCD = new Category("CD"); 
  18.     command = new AddCommand(CD, catCD); 
  19.     manager.setCommand(command); 
  20.     manager.process(); 
  21.     //Add an item to the New Releases category 
  22.     CD = new Item("Duet"); 
  23.     catCD = new Category("New Releases"); 
  24.     command = new AddCommand(CD, catCD); 
  25.     manager.setCommand(command); 
  26.     manager.process(); 
  27.     //Delete an item from the New Releases category 
  28.     command = new DeleteCommand(CD, catCD); 
  29.     manager.setCommand(command); 
  30.     manager.process(); 
  31.   } 

当客户程序运行时,会显示下面的信息:
Item 'A Beautiful Mind' has been added to the 'CD' Category 
Item 'Duet' has been added to the 'CD' Category 
Item 'Duet' has been added to the 'New Releases' Category 
Item 'Duet' has been deleted from the 'New Releases' 
Category 
类图30.8描述了整个类的关联关系:
 

Figure 30.8: Class Association 
在图30.9的顺序图中显示了当客户CommandTest使用命令对象(Command)增加项目时,消息的流图。
 
Figure 30.9: Message Flow When an Item Is Added to a Category 

附件为原码:
附件:35.rar(18K) 


热点文章
最新项目
相关文章 最新文章