[Composite UI][OB之BuilderStrategy扩展(二)]EventBrokerStrategy

 

********************************************************************
*                                                 版权声明
*
* 本文以Creative Commons的知识共享署名-非商业性使用-相同方式共享协议发布,请严格遵循该授权协议。
* 本文首发于博客园, 此声明为本文章中不可或缺的一部分。
* 作者网名:    浪子
* 作者EMAILdayichen (at)163.com
* 作者BLOG:  Http://Www.Cnblogs.Com/Walkingboy
*
********************************************************************

[Composite UI][OB之BuilderStrategy扩展(二)]CAB之EventBrokerStrategy

-Written by 浪子@cnblogs.com  (06-09-21)

 

摘要:

在CAB中,我们只要为某个Event贴上EventPublication的Attribute,则我们就可以在相关的方法上利用EventSubscription这个Attribute就可以轻松订阅相关EventPublication。是不是有点Web2.0的味道了:)。其实其本质只是动态创建委托而已,本文将为你揭开EventBrokerStrategy的神秘面纱。

 

一、Attribute:EventPublication And EventSubscription

EventPublicationAttribute和EventSubscriptionAttribute是互相呼应的两个Customer Attribute。

请先看EventPublication的约束:

[AttributeUsage(AttributeTargets.Event, AllowMultiple=true)]
public sealed class EventPublicationAttribute : Attribute
它是只能使用在Event对象上面的。
why?
因为我们需要的是动态创建Delegate,而且是创建Delegate列表(Event?),不仅仅是一个。
 
那EventPublication都为我们的创建动作提供了什么有用的信息了?
public string Topic

标识,也有人称它为URL,因为在官方的Demo里都以"topic://EventBrokerQuickStart/StartProcess"的形式出现。

那可不可以自定义格式?

当然可以了,比如我可以使用"cnblogs://walkingboy.cnblogs.com/StartProcess",其实它只是一个标识的key而已。

public PublicationScope scope;
事件发布的范围,顾名思义,看下它的枚举内容
public enum PublicationScope
{
   //全局
   Global,
   //WorkItem本身
   WorkItem,
   //所有子类	
   Descendants,
}
而EventSubscription自然与EventPublication遥相呼应了,同样具有Topic属性。但是它不需要发布范围的界定,取而代之的是
public ThreadOption threadOption,事件的fire环境,看下它的枚举内容:
/// <summary>
/// Specifies on which <see cref="Thread"/> an <see cref="EventTopic"/> subscriber will be called.
/// </summary>
public enum ThreadOption
{
/// <summary>
/// The call is done on the same <see cref="Thread"/> on which the <see cref="EventTopic"/> was fired.
/// </summary>
Publisher,
/// <summary>
/// The call is done asynchronously on a background <see cref="Thread"/>.
/// </summary>
Background,
/// <summary>
/// The call is done is done on the UI <see cref="Thread"/>.
/// </summary>
UserInterface,
}
二、Dynamic create delegate and add EventHanlder
暂且不表OB的具体实现过程,EventBrokerStrategy的最终目的就是创建一个委托,并注册到相关事件,具体的实现都集中在Publication和Subscription两个类中。
/// <summary>
/// Initializes a new instance of the <see cref="Publication"/> class
/// </summary>
public Publication(EventTopic topic, object publisher, string eventName, WorkItem workItem, PublicationScope scope)
{
   this.topic = topic;
   this.wrPublisher = new WeakReference(publisher);
   this.scope = scope;
   this.eventName = eventName;
   this.wrWorkItem = new WeakReference(workItem);
   //获取事件引用
   EventInfo publishedEvent =
publisher.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
   if (publishedEvent == null)
   {
      throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture,
Properties.Resources.CannotFindPublishedEvent, eventName));
   }   
   //检查是否EventHandlerType是否互相匹配
   ThrowIfInvalidEventHandler(publishedEvent);
   //检查是否可写(Static)
   ThrowIfEventIsStatic(publishedEvent);
   //Dynamic create a delegate
   Delegate handler = Delegate.CreateDelegate(publishedEvent.EventHandlerType, this, this.GetType().GetMethod("PublicationHandler"));    //Add this delegate to the publishedEvent    publishedEvent.AddEventHandler(publisher, handler); }
是不是很简单:)
其实如果我们是手工code的话,也只不过是创建delegate ,然后通过“+=”来实现事件注册而已。CAB并没有做其他的东西,只是将我们从人肉编程的困境中解脱出来,利用Reflection技术帮我们做完了我们要做的事情而已。
Publication的工作相对简单些,只需要做一个正确的事件注册即可了,难点其实在于如何动态获取Subscribers的Delegate引用,并实现ThreadOption的约束?简单来说即如何动态创建与Subscribers相关联的Delegate。
那这些Handlers由哪里来呢?
所谓“羊毛出在羊身上”。Publication自然不会凭空自己捏造一个handler出来,它会根据Attribute找到相关的Subscriber,然后从它身上获取所要的handler。
这样一来,Subscription自己就必须提供这样的属性,才能正确订阅Publication的事件通知。
 
internal EventTopicFireDelegate GetHandler()
{
   return EventTopicFireHandler;
}
private void EventTopicFireHandler(object sender, EventArgs e,List<Exception> exceptions, TraceSource traceSource)
{
   object subscriber = wrSubscriber.Target;
   if (subscriber == null)
   {
      return;
   }
   switch (threadOption)
   {
      case ThreadOption.Publisher:
         CallOnPublisher(sender, e, exceptions);
      break;
      case ThreadOption.Background:
         CallOnBackgroundWorker(sender, e, traceSource);
         break;
      case ThreadOption.UserInterface:
         CallOnUserInterface(sender, e, exceptions);
         break;
      default:
         break;
   }
}
代码言简意赅,就无需做解释了。subscription在自身内创建了一个委托EventTopicFireDelegate,并在Delegate内部实现了ThreadOption的约束
三、How to call the subscribers/fire the delegate
“万事俱备,只欠东风。”
Subscription已经提供了自身的handler,而Publication也找到了合适的handler,并进行注册。那他们之间如何通信呢?
其实在创建Delegate的时候,还要一个小小的技巧:“无参委托”和“有参委托”。
叫法上或许有些不正确(我是按js里面动态实现自定义事件的注册机制的说法来定义的)。
我们从Publication 创建Delegate引用的地方可以看到这两者之间的转换是如何进行的。
Delegate handler = Delegate.CreateDelegate(publishedEvent.EventHandlerType, this, this.GetType().GetMethod("PublicationHandler"));
publishedEvent.AddEventHandler(publisher, handler);
显而易见,Publication注册到事件里的委托并不直接就是Subscription所提供的handler,而是自身的一个函数“PublicationHandler”:
public void PublicationHandler(object sender, EventArgs e)
从这个函数的参数类型就可以看出,这是一个最基本的委托,我把它称为“无参委托”(只有最标准的object 和 Eventargs,并不是真的没有任何参数)。
而这显然是不够用的,我们的事件一般都有自己的Customer EventArgs类型。所以这里的PublicationHandler只是巧妙的做为一个Proxy出现的,它利用自身的属性作为参数再构造另外一个事件的引用
public void PublicationHandler(object sender, EventArgs e)
{
   topic.Fire(this, sender, e);
}
这个时候它就实现了把自己所带有的信息传递到真正要触发的Hanlder上了。
(有了解过JS事件机制实现的人,应该对这种做法非常的熟悉了,因为JS并没有提供Event的扩展机制,所以一般通过此类似的方法实现事件注册)
EventTopic这个类,在EventBroker中起到关键的作用,它负责Publication和Subscription之间的协同工作,并实现PublicationScope的约束。
 
public virtual void Fire(object sender, EventArgs e, WorkItem workItem, PublicationScope scope)
{
   Guard.EnumValueIsDefined(typeof(PublicationScope), scope, "scope");
   if (enabled)
   {
      if (traceSource != null)
         traceSource.TraceInformation(Properties.Resources.EventTopicTraceFireStarted, name);
      Clean();
      switch (scope)
      {
         case PublicationScope.WorkItem:
            CallSubscriptionHandlers(sender, e, GetWorkItemHandlers(workItem));
            break;
         case PublicationScope.Global:
            CallSubscriptionHandlers(sender, e, GetAllHandlers());
            break;
         case PublicationScope.Descendants:
            CallSubscriptionHandlers(sender, e, GetDescendantsHandlers(workItem));
            break;
         default:
            throw new ArgumentException(Properties.Resources.InvalidPublicationScope);
      }
      if (traceSource != null)
         traceSource.TraceInformation(Properties.Resources.EventTopicTraceFireCompleted, name);
   }
}
由其中的switch可以看出,对于PublicationScop的约束,其实只通过如何获取不同的Handler集合来实现的。
四、OB的具体实现:
思路理到这个程度上,OB是如何具体实现EventBrokerStrategy的也就渐渐浮出水面了。似乎没有必要一段一段的祥解它的具体代码了。
因为这个过程当中,只是做了一些初始化的工作(比如EventTopic中就做了很多初始化的集合操作),并将搜集并设置相关的上下文关系。
五、小结:
初探CAB,确实让人惊讶,它将 metadata programme的魅力充分的展现在我的眼前。我相信随着不断的深入,一定还有更多的惊喜等待我们去发现。
六、相关链接:
 
posted @ 2006-09-22 13:13  浪子  阅读(3162)  评论(4编辑  收藏  举报