[Composite UI][OB之BuilderStrategy扩展(二)]EventBrokerStrategy
********************************************************************
* 版权声明
*
* 本文以Creative Commons的协议发布,请严格遵循该授权协议。
* 本文首发于博客园, 此声明为本文章中不可或缺的一部分。
* 作者网名: 浪子
* 作者EMAIL:dayichen (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的魅力充分的展现在我的眼前。我相信随着不断的深入,一定还有更多的惊喜等待我们去发现。
六、相关链接:
- patterns & practices- Composite UI Application
- patterns & practices- Object Builder
- [Composite UI][OB之BuilderStrategy扩展(一)]CAB之BuilderStrategy概览
- [浪子学编程]ObjectBuilder相关系列