前言

话说EventBroker这玩意已经不是什么新鲜货了,记得第一次接触这玩意是在进第二家公司的时候,公司产品基础架构层中集成了分布式消息中间件,在.net基础服务层中使用EventBroker的模式将消息组装成事件,支持同域、跨域和跨机器进行事件的发布和订阅,后来才知道这玩意叫做EventBroker。不得不承认,这是一个非常聪明的东西,它在内部高度封装了消息和事件的处理,将上层应用的事件和委托的依赖进行解耦,并且提供非常简洁的方式进行开发。OK,本篇文章只是实现了一个简化版本的EventBroker,功能非常有限,也没有集成消息组件,所以也不支持跨域和跨机器的分布式应用了,甚至没有经过严格测试(比如并发处理、跨线程处理等等),社区上更好的实现很多。所以必须声明:只是实验性代码,纯粹练手,带着学习的态度写代码,coding不易,大家不喜勿喷

准备

本篇主要使用.net C#技术,包括委托、反射、弱引用和弱事件模式,写代码也是为了学习这些,找找码感。说明:为了不影响阅读,所贴代码均有一些阉割,如需源码,后续我把附件传上来,不过这源码真没啥东西,我上传就是自己做一个记录,存储下来

思路

思路不用多说,网上搜一下会有很多信息,我比较懒,直接贴一张网上的图可以明确表现出系统的交互场景和交互流程

应用

在应用层使用EventBroker,需要在应用中针对对象进行Register,一般中大型项目使用IOC容器都提供在对象创建时注入特征,比如:

// 示范代码
private void OnObjectBuild(ObjectModel builder, object instance)
{
var broker = DIContainer.Resolve<IEventBroker>();
broker.Register(instance);
}

这样比较方便,避免在程序中手动Register,不过我这里只是简单实现,不涉及IOC和任何设计的东西,注册对象后,在我们的类中可以像如下的方式实现:

[EventPublication("topic://eventbreaker/winform/message", EventScope = EventScope.Local)]
public event EventHandler<EventModelArgs<string>> MessageArrived;

以上是事件定义,我们可以把事件委托引用的方法定义在另一个类,而且两者完全不用依赖彼此

[EventSubscription("topic://eventbreaker/winform/message", EventScope = EventScope.Local)]
public void MessageReceived(object sender, EventModelArgs<string> message)
{
PrintOut(message.Data);
}

源码

  • 源码目录结构,看看是不是比别的版本精简了?

  • EventBroker,我暂且把这个叫事件总线,内部封装EventTopic,事件的发布者和订阅者信息都间接存储在总线上,除此之外,总线向外部提供统一的API接口,方便调用,相关接口如下:
namespace TinyEventBroker
{
public class EventBroker
{
private readonly EventTopicCollection topics
= new EventTopicCollection(); /// <summary>
/// 事件主题集合列表
/// </summary>
public EventTopicCollection Topics
{
get { return topics; }
} /// <summary>
/// 事件场景
/// </summary>
public virtual EventScope EventScope { get { return EventScope.Local; } } /// <summary>
/// 将对象注入到事件总线上
/// </summary>
public void Register(object target)
{
BindingFlags bingdings = BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic;
RegisterPublication(target, bingdings);
RegisterSubscription(target, bingdings);
} /// <summary>
/// 注册发布者
/// </summary>
private void RegisterPublication(object target, BindingFlags bindings)
{
// ……
} /// <summary>
/// 注册订阅者
/// </summary>
/// <param name="target"></param>
/// <param name="bindings"></param>
private void RegisterSubscription(object target, BindingFlags bindings)
{
// ……
}
}
}
  • EventTopic,这个我把它称为事件主题,一个主题可以包含多个发布者和订阅者并提供一些处理,主题管理器内部将事件进行桥接,并动态调用订阅者方法,相关接口如下:
public class EventTopic
{
public EventTopic() { }
public EventTopic(string name)
: this()
{
this.Name = name;
} /// <summary>
/// 事件主题标识
/// </summary>
public string Name { get; set; } private List<EventPublication> publishers = new List<EventPublication>();
/// <summary>
/// 发布者列表
/// </summary>
internal List<EventPublication> Publishers
{
get { return publishers; }
set { publishers = value; }
} private List<EventSubscription> subscribers = new List<EventSubscription>();
/// <summary>
/// 订阅者列表
/// </summary>
internal List<EventSubscription> Subscribers
{
get { return subscribers; }
set { subscribers = value; }
} /// <summary>
/// 增加发布者
/// </summary>
internal void AddPublication(EventPublication publisher)
{
EventContext.Instance.WriteTo("主题:{0} 添加发布者{1}", Name, publisher);
publisher.EventFired += OnEventFired;
publishers.Add(publisher);
} /// <summary>
/// 增加订阅者
/// </summary>
internal void AddSubscription(EventSubscription subscriber)
{
EventContext.Instance.WriteTo("主题:{0} 添加订阅者{1}", Name, subscriber);
subscribers.Add(subscriber);
} /// <summary>
/// 移除发布者
/// </summary>
internal void RemovePublication(EventPublication publisher)
{
EventContext.Instance.WriteTo("主题:{0} 移除发布者{1}", Name, publisher);
publishers.Remove(publisher);
} /// <summary>
/// 移除订阅者
/// </summary>
internal void RemoveSubscription(EventSubscription subscriber)
{
EventContext.Instance.WriteTo("主题:{0} 移除订阅者{1}", Name, subscriber);
subscribers.Remove(subscriber);
} internal void OnEventFired(EventPublication publication, object sender, EventArgs args)
{
// ......
} private void CheckInvalidPublications()
{
// ......
} private void CheckInvalidSubscriptions()
{
// ......
}
}
  • Publisher,订阅者,使用弱引用来存储目标对象,防止目标对象无法释放造成内存泄露
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace TinyEventBroker
{
/// <summary>
/// 发布者
/// </summary>
internal class EventPublication
{
/// <summary>
/// 弱引用对象包装器
/// </summary>
private readonly WeakReference wrapper;
/// <summary>
/// 事件名称
/// </summary>
private readonly string eventName;
/// <summary>
/// 事件处理委托类型
/// </summary>
private readonly Type eventHandleType; /// <summary>
/// 对象名称,用于显示
/// </summary>
private readonly string targetName; /// <summary>
/// 事件定义,用来桥接对象的Event
/// </summary>
public event TopicEventHandler EventFired; public EventPublication(object target, string eventName)
{
wrapper = new WeakReference(target);
this.eventName = eventName;
this.targetName = target.GetType().Name; EventInfo info = target.GetType().GetEvent(EventName);
eventHandleType = info.EventHandlerType; Delegate handler = Delegate.CreateDelegate(
EventHandleType, this,
this.GetType().GetMethod("OnEventFired"));
info.AddEventHandler(target, handler);
} /// <summary>
/// 获取对象引用
/// </summary>
public object Target
{
get { return wrapper.Target; }
} /// <summary>
/// 对象名称,用于显示
/// </summary>
public string TargetName
{
get { return targetName; }
} /// <summary>
/// 判断对象是否存活
/// </summary>
public bool IsAlive
{
get { return wrapper.IsAlive; }
} /// <summary>
/// 事件名称
/// </summary>
public string EventName
{
get { return eventName; }
} /// <summary>
/// 事件处理类型
/// </summary>
public Type EventHandleType
{
get { return eventHandleType; }
} public virtual void OnEventFired(object sender, EventArgs e)
{
var handle = EventFired;
if (handle != null)
handle(this, sender, e);
} public override string ToString()
{
return string.Format("[{0} - {1}]", targetName, eventName);
}
}
}
  • 订阅者,依然使用弱引用来保存目标对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace TinyEventBroker
{
/// <summary>
/// 订阅者
/// </summary>
public class EventSubscription
{
/// <summary>
/// 弱引用对象包装器
/// </summary>
private readonly WeakReference wrapper;
/// <summary>
/// 方法名
/// </summary>
private readonly string methodName; /// <summary>
/// 对象名称,仅作显示
/// </summary>
private readonly string targetName; public EventSubscription(object target, string methodName)
{
this.wrapper = new WeakReference(target);
this.methodName = methodName;
targetName = target.GetType().Name;
} /// <summary>
/// 对象是否存活
/// </summary>
public bool IsAlive
{
get { return wrapper.IsAlive; }
} /// <summary>
/// 订阅者对象方法名
/// </summary>
public string MethodName { get { return methodName; } }
/// <summary>
/// 订阅者对象,通过弱引用包装访问
/// </summary>
public object Target { get { return wrapper.Target; } }
/// <summary>
/// 订阅者对象显式名
/// </summary>
public string TargetName { get { return targetName; } } internal virtual void HandleEvent(EventPublication publication, object sender, EventArgs args)
{
Delegate handler = Delegate.CreateDelegate(
publication.EventHandleType, Target, methodName); handler.DynamicInvoke(sender, args);
} public override string ToString()
{
return string.Format("[{0} - {1}]", targetName, methodName);
}
}
}
  • EventScope,这东西只是保留属性,以后扩展的时候用

初步测试结果

引用

针对弱引用这个东东,其实直接使用弱事件模式,微软已经封装好了的

Weak Event PatternsWeak Event in C#

结语

本篇只是EventBroker的简单实现,纯粹就是学习练手,我想,如果后续我有充足的时间,或者工作环境允许,我可以实现得更好,比如可以在内部封装多钟策略,异常处理策略、异步事件策略、处理并发、线程安全的考虑等等。更而甚之,可以将它进行抽象和扩展,加入消息中间件,实现分布式发布者和订阅者,那样就比较有实用价值了。不过写代码水平和效率有限,时间和精力也有限,工作是工作学习是学习,区区一文,聊表我的热情和开发,让代码和生活可以持续下去吧!

源码下载

C#编程实践—EventBroker简单实现的更多相关文章

  1. [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍

    阅读<Java并发编程实践>一书后整理的思维导图.

  2. 高性能javascript学习笔记系列(5) -快速响应的用户界面和编程实践

    参考高性能javascript 理解浏览器UI线程  用于执行javascript和更新用户界面的进程通常被称为浏览器UI线程  UI线程的工作机制可以理解为一个简单的队列系统,队列中的任务按顺序执行 ...

  3. Method Swizzling和AOP(面向切面编程)实践

    Method Swizzling和AOP(面向切面编程)实践 参考: http://www.cocoachina.com/ios/20150120/10959.html 上一篇介绍了 Objectiv ...

  4. 信息安全系统设计基础课程实践:简单TUI游戏设计

    简单TUI游戏设计                目       录               一                      Curses库简介与基本开发方法             ...

  5. 第二章 C语言编程实践

    上章回顾 宏定义特点和注意细节 条件编译特点和主要用处 文件包含的路径查询规则 C语言扩展宏定义的用法 第二章 第二章 C语言编程实践 C语言编程实践 预习检查 异或的运算符是什么 宏定义最主要的特点 ...

  6. Python GUI编程实践

    看完了<python编程实践>对Python的基本语法有了一定的了解,加上认识到python在图形用户界面和数据库支持方面快捷,遂决定动手实践一番. 因为是刚接触Python,对于基本的数 ...

  7. 《编写可维护的JavaScript》之编程实践

    最近读完<编写可维护的JavaScript>,让我受益匪浅,它指明了编码过程中,需要注意的方方面面,在团队协作中特别有用,可维护性是一个非常大的话题,这本书是一个不错的起点. 本书虽短,却 ...

  8. C#编程实践–产假方案优化版

    前言 既然作为一个踏踏实实学习技术的人,就要有一颗谦卑.虚心和追求卓越的心,我不能一次就写出很完美的代码,但我相信,踏踏实实一步一步的优化,代码就可以变得趋近完美,至少在某一个特定场景下相对完美,这和 ...

  9. C#编程实践–帮老婆计算产假方案

    摘要 今天中午午休时,和老婆聊天,老婆还过几天就要请产假了,她在网上问我让我帮她数一下该怎么请假最划算,老婆是个会过日子的人,面对此种要求我当然义不容辞,不过想到这个问题我的第一反应是:这个怎么可以用 ...

随机推荐

  1. 基于docker构建jenkins和svn服务(转)

    码农们很定都知道svn的重要性,机器坏掉丢代码的惨痛教训想必很多人都有. jenkins可能很多人都不了解.这是一个持续集成的工具,在敏捷开发领域很流行:跟svn结合可以实现定期build.check ...

  2. nefu117 素数个数的位数,素数定理

    素数个数的位数 Time Limit 1000ms Memory Limit 65536K description 小明是一个聪明的孩子,对数论有着非常浓烈的兴趣.他发现求1到正整数10n 之间有多少 ...

  3. linux(fedora) 下dvwa 建筑环境

    linux(fedora)下dvwa组态 1.下载httpd,dvwa,mysql,mysqlserver, php-mysql,php 除了dvwa 这是外界进入下一官方网站.该服务通过休息inst ...

  4. C语言功能 --C

    功能名称: cabs 动力 能够: 计算绝对复数值 使用 法国: double cabs(struct complex z); 程序示例: #include <stdio.h> #incl ...

  5. linux_vim_快捷键

    1.vim ~/.vimrc 进入配置文件 如果不知道vimrc文件在哪,可使用 :scriptnames 来查看 set nu #行号 set tabstop=4 #一个tab为4个空格长度 set ...

  6. Java有用的经验--Swing片

    Java有用经验总结--Swing篇 前言 本文前言部分为我的一些感想,假设你仅仅对本文介绍的Java有用技巧感兴趣,能够跳过前言直接看正文的内容. 本文的写作动机来源于近期接给人家帮忙写的一个小程序 ...

  7. obj-c编程15[Cocoa实例03]:MVC以及归档化演示样例

    前面的博文里介绍了归档和解档,这里我们把它实际应用到一个简单的代码中去,将它作为一个多文档应用程序的打开和保存的背后支持.另外这里介绍一下MVC思想,这个在不论什么语言里都会有,它是一种设计思想,主要 ...

  8. 突破IP限制动态替换代理ip。

    须要导入的两个jar包 实现的javabean <span style="font-size:18px;">package com.jx.po; public clas ...

  9. 关于Cassandra与Thrift在int/text/varint上的暧昧

    近期简单写了一个基于Cassandra/C++的日志缓存,虽然是Nosql,但是在实际应用中,还是期望能有部分的临时CQL统计 或+-*/可以支持 所以在针对部分字段入库时,选择了作为整形录入,于是麻 ...

  10. C# 获取磁盘容量

    原文:C# 获取磁盘容量 /// 获取指定驱动器的空间总大小(单位为B) /// </summary> /// <param name="str_HardDiskName& ...