ACTIVE OBJECT 模式
ACTIVE OBJECT 模式
——《敏捷软件开发 原则、模式与实践(c#版)》第21章
ACTIVE OBJECT 模式是建立在COMMAND模式的基础上。这是实现多线程控制的一项古老的技术。该模式有多种使用方式,为许多工业系统提供了一个简单的多任务核心。
想法很简单。考虑代码清单1-1和代码清单1-2。ActiveObjectEngine 对象维护了一个 ICommand 对象的链表。用户可以向该引擎增加新的命令,或者调用 Run()。Run() 函数只是遍历链表,执行并去除每个命令。
代码清单1-1
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace ActiveObject
{
public class ActiveObjectEngine
{
ArrayList itsCommands = new ArrayList();
public void AddCommand(ICommand aCommand)
{
itsCommands.Add(aCommand);
}
public void Run()
{
while (itsCommands.Count > 0)
{
ICommand c = (ICommand)itsCommands[0];
itsCommands.RemoveAt(0);
c.Execute();
}
}
}
}
代码清单1-2
{
void Execute();
}
这似乎没有给人太深刻的印象。但是想象一下如果链表中的一个 ICommand 对象会克隆自己并把克隆对象放到链表的尾部,会发生什么呢?这个链表永远不会为空,Run() 函数永远不会返回。
考虑一下代码清单1-3中的测试用例。它创建了一个SleepCommand 对象。其中它向 SleepCommand 的构造函数中传了一个1,000ms的延迟。接着把 SleepCommand 对象放入到 ActiveObjectEngine 中。调用了 Run() 后,它将等待指定的毫秒数。
代码清单1-3
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace ActiveObject
{
[TestFixture]
public class TestSleepCommand
{
private class WakeUpCommand : ICommand
{
public bool executed = false;
public void Execute()
{
this.executed = true;
}
}
[Test]
public void TestSleep()
{
WakeUpCommand wakeup = new WakeUpCommand();
ActiveObjectEngine e = new ActiveObjectEngine();
SleepCommand c = new SleepCommand(1000, e, wakeup);
e.AddCommand(c);
DateTime start =DateTime.Now;
e.Run();
DateTime stop = DateTime.Now;
double sleepTime = (stop - start).TotalMilliseconds;
Assert.IsTrue(sleepTime >= 1000,
string.Format("SleepTime {0} expected > 1000",sleepTime.ToString()));
Assert.IsTrue(sleepTime <= 1100,
string.Format("SleepTime {0} expected < 1100", sleepTime.ToString()));
Assert.IsTrue(wakeup.executed, "Command Executed");
}
}
}
我们来仔细看看这个测试用例。SleepCommand 的构造函数有 3 个参数。
第一个:是延迟的毫秒数;
第二个:是在其中运行该命令的ActiveObjectEngine 对象;
第三个:是一个名为 wakeup 的另一个命令对象。
测试的意图是 SleepCommand 会等待指定数目的毫秒,然后执行 wakeup 命令。
代码清单1-4展示了 SleepCommand 的实现。在执行时,SleepCommand 检查自己以前是否已经执行过,如果没有,就记录下开始时间。如果没有过延迟时间,就把自己再加到 ActiveObjectEngine 中。如果过了延迟时间,就把 wakeup 命令对象加到 ActiveObjectEngine 中。
代码清单1-4
using System.Collections.Generic;
using System.Text;
namespace ActiveObject
{
public class SleepCommand : ICommand
{
private ICommand wakeupCommand = null;
private ActiveObjectEngine engine = null;
private long SleepTime = 0;
private DateTime startTime;
private bool started = false;
public SleepCommand(long milliSeconds, ActiveObjectEngine e,
ICommand wakeupCommand)
{
this.SleepTime = milliSeconds;
this.engine = e;
this.wakeupCommand = wakeupCommand;
}
public void Execute()
{
DateTime currentTime = DateTime.Now;
if (!started)
{
started = true;
this.startTime = currentTime;
this.engine.AddCommand(this);
}
else
{
TimeSpan elapsedTime = currentTime - startTime;
if (elapsedTime.TotalMilliseconds < SleepTime)
{
this.engine.AddCommand(this);
}
else
{
this.engine.AddCommand(this.wakeupCommand);
}
}
}
}
}
我们可以对该程序和等待一个事件的多线程程序做一个类比。当多线程程序中的一个线程等待一个事件时,它通常使用一些操作系统调用来阻塞自己知道事件发生。代码清单1-4中的程序并没有阻塞。相反,如果所等待的(elapsedTime.TotalMilliseconds < SleepTime)这个事件没有发生,线程只是把自己放回到 ActiveObjectEngine 中。
采用该技术的变体去构建多线程系统已经是并且将会一直是一个常见的实践。这种类型的线程成为 run-to-completion 任务(RTC),因为每个 Command 实例在下一个 Command 实例可以运行之前就运行完成了。RTC 的名字意味着 Command 实例不会阻塞。
Command 实例一经运行就一定得完成的特性赋予了RTC线程有趣的优点,那就是它们共享同一个运行栈。和传统的多线程系统的线程不通,不必为每个RTC线程定义或者分配各自的运行时栈。这在需要大量线程的内存受限系统中是一个强大的优势。
继续我们的例子,代码清单1-5展示了一个简单的程序,其中使用了SleepCommand 并展示了它的多线程行为。该程序成为 DelayedType 。
代码清单1-5
using System.Collections.Generic;
using System.Text;
namespace ActiveObject
{
public class DelayedTyper : ICommand
{
private long itsDelay;
private char itsChar;
private static bool stop = false;
private static ActiveObjectEngine engin =
new ActiveObjectEngine();
private class StopCommand : ICommand
{
public void Execute()
{
DelayedTyper.stop = true;
}
}
public static void Main(string[] args)
{
engin.AddCommand(new DelayedTyper(100, 'A'));
engin.AddCommand(new DelayedTyper(300, 'B'));
engin.AddCommand(new DelayedTyper(500, 'C'));
engin.AddCommand(new DelayedTyper(700, 'D'));
ICommand stopCommand = new StopCommand();
engin.AddCommand(new SleepCommand(2000, engin, stopCommand));
engin.Run();
Console.ReadLine();
}
public DelayedTyper(long delay, char c)
{
this.itsDelay = delay;
this.itsChar = c;
}
public void Execute()
{
Console.Write(itsChar);
if (!stop)
{
DelayAndRepeat();
}
}
private void DelayAndRepeat()
{
engin.AddCommand(new SleepCommand(itsDelay, engin, this));
}
}
}
请注意 DelayedType 实现了 ICommand 接口。 它的 execute 方法只是打印出在构造时传入的字符,检查 stop 标志,并在该标志没有被设置时调用 DelayAndRepeat。DelayAndRepeat 方法使用构造时传入的延迟构造了一个 SleepCommand 对象。难后把构造后的 SleepCommand 对象插入到 ActiveObjectEngine 中。
该ICommand 对象的行为很容易预测。实际上,它维持着一个循环,在循环中重复地打印一个指定的字符并等待一个指定的延迟。当 stop 标志被设置时,就退出循环。
DelayedTyper 的 Main 函数创建了一个 SleepCommand 对象,该对象会在一段时间后设置 stop 标志。运行该程序会打印出一个简单的由 A、B、C、D 组成的字符串。运行结果:
ABCDAABAACABADAABACAABADACABAAABACADB
End。
示例代码:ActiveObject.rar