设置密码

验证码错误,请重新填写

免费建站旅程马上开始

开始建站
建站中

已有帐号?直接登录

首页>森动学院>网站建设教程 > 如何利用命令模式做软件设计?
如何利用命令模式做软件设计?
发布时间: 2014-06-27

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

问题
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?

解决方案
命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式又称为动作(Action)模式或事务(Transaction)模式。

Command Pattern: Encapsulate a request as an object, thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.

适用性
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互 - 使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
系统需要在不同的时间指定请求、将请求排队和执行请求 - 一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作 - 命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果
系统需要将一组操作组合在一起,即支持宏命令。
需要在不同的时间指定请求、将请求排队
如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型。
 
优点和缺点
使用命令模式的优点和缺点

命令允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点:

降低系统的耦合度:Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
Command是头等的对象。它们可像其他的对象一样被操纵和扩展。
组合命令:你可将多个命令装配成一个组合命令,即可以比较容易地设计一个命令队列和宏命令。一般说来,组合命令是Composite模式的一个实例。
增加新的Command很容易,因为这无需改变已有的类。
可以方便地实现对请求的Undo和Redo。


命令模式的缺点如下:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
 
命令模式概述
Command Pattern模式

 

模式的组成
抽象命令类(Command): 声明执行操作的接口。调用接收者相应的操作,以实现执行的方法Execute。
具体命令类(ConcreteCommand): 创建一个具体命令对象并设定它的接收者。通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
调用者(Invoker): 要求该命令执行这个请求。通常会持有命令对象,可以持有很多的命令对象。
接收者(Receiver): 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者,只要它能够实现命令要求实现的相应功能。
客户类(Client): 创建具体的命令对象,并且设置命令对象的接收者。真正使用命令的客户端是从Invoker来触发执行
 
命令模式通过对命令的封装,将命令的请求(调用者Invoker)和执行(接收者Receiver)进行了责任分离,委派给不同的对象,不仅使得调用者和执行者之间实现了解耦(命令的请求方就不需要知道接收方的接口,也不需要知道命令是如何执行的具体情况),还使得可以记录命令的执行记录,添加执行日志,使得命令的控制、执行、取消和重做变得容易。

Delphi的Action就采用了这种模式,其中Windows控件(比如按钮,菜单)是调用者,Action是命令,而接收者是Action的OnExecute事件的实现者。当然,Delphi为了实现设计期间或者运行期间控件的变灰,控件的Caption能在需要时与Action保持一致,在中间加了一个ActionLink类层次结构,采用的是桥模式,控件将控件与Action之间的通信交给ActionLink来完成,而AcionLink对象的创建则采用的是工厂方法(工厂就是控件本身),只不过它利用了GetActionLinkClass函数做得更加巧妙。而Action的具体执行采用事件的方式,又使得命令和接收者进一步解耦,这样命令就不需要知道接收者的接口,也不用维护对接收者的引用,接收者也不需要知道命令的具体细节,只需要提供一个符合要求的事件处理方法即可。

下面是示例代码:

/// <summary>

/// Command类,定义一个执行操作的接口,也可以是一个接口。

/// </summary>

public abstract class Command_Command

{
  protected Command_Receiver _Receiver;

  public abstract void Execute();

  public Command_Receiver Receiver

  {
   get
   {
    return _Receiver;

   }

   set

   {

    _Receiver = value;

   }
  }
}

public class Command_ConcreateCommand : Command_Command

    {
  public Command_ConcreateCommand()
  {
  }
  public override void Execute()
  {
   if(System.Windows.Forms.MessageBox.Show("你想执行操作么?","系统提示!",

    System.Windows.Forms.MessageBoxButtons.YesNo)==System.Windows.Forms.DialogResult.Yes)
   {
    if(this._Receiver!=null)
    {
     this._Receiver.Action();
    }
   }
  }
    }
public class Command_Receiver
{
  public Command_Receiver()
  {
  }
  public void Action()
  {
   System.Windows.Forms.MessageBox.Show("Command Execute!");
  }
}
public class Command_Invoker
{
  private Command_Command command;
  public Command_Invoker()
  {
  }
  public void SetCommand(Command_Command command)
  {
   this.command = command;
  }
  public void Execute()
  {
   this.command.Execute();
  }
}
public class Command_Client
{
  public static void Test()
  {
   //建立具体命令

   Command_ConcreateCommand command = new Command_ConcreateCommand();

   //建立具体接收者

   Command_Receiver r1 = new Command_Receiver();

   //链接命令与接收者

   command.Receiver = r1;

            //创建调用者

   Command_Invoker invoker = new Command_Invoker();

   //设置调用者的命令子类

   invoker.SetCommand(command);

   //执行调用操作

   invoker.Execute();
  }
}


文章来源:森动网小鱼儿,转载请注明出处!