ACTIVE OBJECT 模式 C# 版

《敏捷软件开发——原则、模式与实践》
Robert C.Martin 著,邓辉译,孟岩审,清华大学出版社,2003年9月第1版

第13章 COMMAND模式和ACTIVE OBJECT模式


  没有人天生就具有命令他人的权利。
    ——Denis Diderot(1713-1784,法国哲学家,百科全书编者)


  在近几年记述过的所有设计模式中,我认为COMMAND模式是最简单、最优雅的模式之一。但是我们将会看到,这种简单性是带有欺骗性的。COMMAND模式的适用范围是非常宽广的。
  如图13.1所示,COMMAND模式简单得几乎可笑。程序13.1中的代码并没有起到削弱这种印象的作用。该模式仅由一个具有惟一方法的接口组成,这似乎是荒谬的。
<<interface>>
Command
+do()
图13.1 COMMAND模式

程序13.1 Command.java
public interface Command
{
  public void do();
}
  但是,事实上,该模式横过了一条非常有趣的界线。而这个交界处正是所有有趣的复杂性之所在。大多数类都是一组方法和相应的一组变量的结合。COMMAND模式不是这样的。它只是封装了一个没有任何变量的函数。
  从严格的面向对象意义上来讲,这种做法是被强烈反对的——因为它具有功能分解的味道。它把函数层面的任务提升到了类的层面。这简直是对面向对象的亵渎!然而,在这两个思维范式(paradigm)的碰撞处,有趣的事情发生了。

13.1 简单的COMMAND

  (略,请参看原书)

13.2 事务操作

  (略,请参看原书)

13.3 UNDO

  (略,请参看原书)

13.4 ACTIVE OBJECT模式

  ACTIVE OBJECT模式是我最喜欢使用COMMAND模式的地方之一。这是实现多线程控制的一项古老技术。该模式有多种使用方式,为许多工业系统提供了一个简单的多任务核心。
  想法很简单。考虑程序13.2和程序13.3。ActiveObjectEngine对象维护了一个Command对象的链表。用户可以向该引擎(engine)增加新的命令,或者调用run()。run()函数只是遍历链表,执行并去除每个命令。
(原书中是java程序,现翻译为C#程序,需要C#2.0编译器。根据C#的惯例,接口名称以I开头,方法名称的第一个字母大写。)
程序13.2 ActiveObjectEngine.cs
None.gif using  System.Collections.Generic;
None.gif
None.gif
namespace  Asd.Chap13
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public class ActiveObjectEngine
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    Queue
<ICommand> itsCommands = new Queue<ICommand>();
InBlock.gif    
InBlock.gif    
public void AddCommand(ICommand c)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      itsCommands.Enqueue(c);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
public void Run()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
while (itsCommands.Count > 0) itsCommands.Dequeue().Execute();
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
程序13.3 Command.cs
None.gif namespace  Asd.Chap13
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public interface ICommand
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
void Execute();
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public static class Pub
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
public static long TicksPerMillisecond = 10000;
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
  这似乎没有给人太深刻的印象。但是想象一下如果链表中一个Command对象会克隆自己并把克隆对象放到链表的尾部,会发生什么呢?这个链表永远不会为空,run()函数永远不会返回。
  考虑一下程序13.4中的测试用例。它创建了一个SleepCommand对象。其中,它向SleepCommand的构造函数中传了一个1000ms的延迟。接着把SleepCommand对象放入到ActiveObjectEngine中。调用run()后,它等待指定数目的毫秒。
程序13.4 TestSleepCommand.cs
None.gif using  System;
None.gif
using  NUnit.Framework;
None.gif
using  Asd.Chap13;
None.gif
None.gif
namespace  NUnit.Tests.Asd.Chap13
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  [TestFixture]
InBlock.gif  
public class SleepCommandTest
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
class WakeupCommand : ICommand
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
public void Execute()
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        commandExecuted 
= true;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
static bool commandExecuted;
InBlock.gif    
InBlock.gif    [Test]
InBlock.gif    
public void Sleep()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      ICommand wakeup 
= new WakeupCommand();
InBlock.gif      ActiveObjectEngine e 
= new ActiveObjectEngine();
InBlock.gif      SleepCommand c 
= new SleepCommand(1000, e, wakeup);
InBlock.gif      e.AddCommand(c);
InBlock.gif      
long start = DateTime.Now.Ticks / Pub.TicksPerMillisecond;
InBlock.gif      e.Run();
InBlock.gif      
long stop = DateTime.Now.Ticks / Pub.TicksPerMillisecond;
InBlock.gif      
long sleepTime = stop - start;
InBlock.gif      Assert.IsTrue(sleepTime 
> 1000"SleepTime " + sleepTime.ToString() + " expected > 1000");
InBlock.gif      Assert.IsTrue(sleepTime 
< 1100"SleepTime " + sleepTime.ToString() + " expected < 1100");
InBlock.gif      Assert.IsTrue(commandExecuted, 
"Command Executed");
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
  我们来仔细看看这个测试用例。SleepCommand的构造函数有3个参数。第一个是延迟的毫秒数。第二个是在其中运行该命令的ActiveObjectEngine对象。最后一个是名为wakeup的另一个命令对象。测试的意图是SleepCommand会等待指定数目的毫秒,然后执行wakeup命令。
  程序13.5展示了SleepCommand的实现。在执行时,SleepCommand检查自己以前是否已经执行过,如果没有,就记录下开始时间。如果没有过延迟时间,就把自己再加到ActiveObjectEngine中。如果过了延迟时间,就把wakeup命令对象加到ActiveObjectEngine中。
程序13.5 SleepCommand.cs
None.gif using  System;
None.gif
None.gif
namespace  Asd.Chap13
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public class SleepCommand : ICommand
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    ICommand wakeupCommand;
InBlock.gif    ActiveObjectEngine engine;
InBlock.gif    
long sleepTime;
InBlock.gif    
long startTime;
InBlock.gif    
bool started;
InBlock.gif    
InBlock.gif    
public SleepCommand(long milliseconds, ActiveObjectEngine e, ICommand wakeupCommand)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      sleepTime 
= milliseconds;
InBlock.gif      engine 
= e;
InBlock.gif      
this.wakeupCommand = wakeupCommand;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
public void Execute()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
long currentTime = DateTime.Now.Ticks / Pub.TicksPerMillisecond;
InBlock.gif      
if (!started)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        started 
= true;
InBlock.gif        startTime 
= currentTime;
InBlock.gif        engine.AddCommand(
this);
ExpandedSubBlockEnd.gif      }

InBlock.gif      
else if ((currentTime - startTime) < sleepTime)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        engine.AddCommand(
this);
ExpandedSubBlockEnd.gif      }

InBlock.gif      
else
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        engine.AddCommand(wakeupCommand);
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
  (请使用如下的命令编译以上程序:
csc /t:library /out:SleepCommand.dll SleepCommand.cs ActiveObjectEngine.cs Command.cs
csc /t:library /out:TestSleepCommand.dll /r:c:\NUnit\bin\nunit.framework.dll /r:SleepCommand.dll TestSleepCommand.cs
其中 NUint 请到 http://www.nunit.org 下载。然后运行 nunit-gui.exe 的结果如下:
ASD-01.PNG
  )
  我们可以对该程序和等待一个事件的多线程程序做一个类比。当多线程程序中的一个线程等待一个事件时,它通常使用一些操作系统调用来阻塞自己直到事件发生。程序13.5中的程序并没有阻塞。相反,如果所等待的((currentTime-startTime)<sleepTime)这个事件没有发生,它只是把自己放回到ActiveObjectEngine中。
  采用该技术的变体(variations)去构建多线程系统已经是并且将会一直是一个很常见的实践。这种类型的线程被称为run-to-completion任务(RTC),因为每个Command实例在下一个Command实例可以运行之前就运行完成了。RTC的名字意味着Command实例不会阻塞。
  Command实例一经运行就一定得完成的特性赋予了RTC线程有趣的优点,那就是它们共享同一个运行时堆栈。和传统的多线程系统中的线程不同,不必为每个RTC线程定义或者分配各自的运行时堆栈。这在需要大量线程的内存受限系统中是一个强大的优势。
  继续我们的例子,程序13.6展示一个简单的程序,其中使用了SleepCommand并展示了它的多线程行为。该程序被称为DelayedTyper。
程序13.6 DelayedTyper.cs
None.gif using  System;
None.gif
None.gif
namespace  Asd.Chap13
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public class DelayedTyper : ICommand
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
class StopCommand : ICommand
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
public void Execute()
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        stop 
= true;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
long itsDelay;
InBlock.gif    
char itsChar;
InBlock.gif    
static ActiveObjectEngine engine = new ActiveObjectEngine();
InBlock.gif    
static bool stop = false;
InBlock.gif
InBlock.gif    
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      engine.AddCommand(
new DelayedTyper(100'1'));
InBlock.gif      engine.AddCommand(
new DelayedTyper(300'3'));
InBlock.gif      engine.AddCommand(
new DelayedTyper(500'5'));
InBlock.gif      engine.AddCommand(
new DelayedTyper(700'7'));
InBlock.gif      ICommand stopCommand 
= new StopCommand();
InBlock.gif      engine.AddCommand(
new SleepCommand(20000, engine, stopCommand));
InBlock.gif      engine.Run();
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
public DelayedTyper(long delay, char c)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      itsDelay 
= delay;
InBlock.gif      itsChar 
= c;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
public void Execute()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      Console.Write(itsChar);
InBlock.gif      
if (!stop) DelayAndRepeat();
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
void DelayAndRepeat()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      engine.AddCommand(
new SleepCommand(itsDelay, engine, this));
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
  (编译:csc /t:exe /out:DelayedTyper.exe /r:SleepCommand.dll DelayedTyper.cs)
  请注意DelayedTyper实现了Command接口。它的execute方法只是打印出在构造时传入的字符,检查stop标志,并在该标志没有被设置时调用delayAndRepeat。delayAndRepeat方法使用构造时传入的延迟构造了一个SleepCommand对象,再把构造后的SleepCommand对象插入ActiveObjectEngine中。
  该Command对象的行为很容易预测。实际上,它维持着一个循环,在循环中重复地打印一个指定的字符并等待一个指定的延迟。当stop标志被设置时,就退出循环。
  DelayedTyper的main函数创建了几个DelayedTyper的实例并把它们放入ActiveObjectEngine中,每个实例都有自己的字符和延迟。接着创建了一个SleepCommand对象,该对象会在一段时间后设置stop标志。运行该程序会打印出一个简单的由'1'、'3'、'5'以及'7'组成的字符串。再次运行该程序会打印出一个相似,但是有差别的字符串。这里是两次有代表性的运行结果 (我用我机器上的运行结果代替了原书中的运行结果):
13571113115137113151113171513111311571...
13571113151317111315131171135111311571...
  这些字符串之所以有差别是因为CPU时钟和实时时钟没有完美的同步。这种不可确定的行为是多线程系统的特点。

13.5 结论

  COMMAND模式的简单性掩盖了它的多功能性。COMMAND模式可以应用于多种不同的美妙用途,范围涉及数据库事务操作、设备控制、多线程核心以及GUI的do/undo管理。
  有人认为COMMAND模式不符合面向对象的思维范式(paradigm),因为它对函数的关注超过了类。这也许是真的,但是在实际的软件开发中,COMMAND模式是非常有用的。

参考文献

1. Gamma, et al. Design Patterns. Reading, MA: Addision-Wesley, 1995.
2. Lavender, R.G., and D.C.Schmidt. Active Object: An Object Behavioral Pattern for Concurrent Programming, in "Pattern Languages of Program Design" (J.O.Coplien, J.Vlissides, and N.kerth, eds.). Reading, MA: Addision-Wesley, 1996.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值