【轮子狂魔】轮子的魅力之扩展能力
序言 |
如果你是第一次看本文,建议先看下前面两篇,否则你可能会一头雾水
看过上一篇【轮子狂魔】打造简易无配置的IoC的人,可能会有几个疑问,我统一回答一下吧。
1.你这说是IoC,但感觉不像啊。
首先,我在百度百科里把IoC的概念Copy过来看看。
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
那么,IoC一定是AutoFac、Unity之类的吗?它什么时候被标签上一定要与这些第三方功能一致的标签?
在我上篇文中,我使用的是参数注入,缓存反射关系的依赖查找方式。所以我说是简易的IoC应该没什么问题。
2.有人在上一篇看到了CQRS或者DDD的影子。
实话说我最近确实看过这方面的东西,受到了一些启发,所以命名上有点贴近这方面技术名词,但这仅仅是名字相近而已。
3.为什么要出这样一个系列?
我希望我可以从因果关系上解释出遇到什么样的问题,可以有什么样的方法应对,或者以什么样的顺序来搭建我们的系统架构,这并不是一个标准,而是个人的经验,仅供参考而已。
另外我也希望大家可以扩展思维,发现架构是可以“造”出来的,而不是“拷贝”出来的,不要落入三层、MVC、MVVM、SOA等里面。
改造,让架构更智能 |
开发微信时发现一个可以提炼出来的共同点:交互接口参数中需携带AccessToken
而这个AccessToken有多么烦人,在上一篇已经提过我就不再赘述了。
IAccessTokenAuth的由来
我们要做的是让架构可以帮助我们自动填充这个AccessToken,那我们就先给他定义为一个接口,方便后面去针对接口操作,
同时,Event是穿梭在Event、Dispatch、Command三个层中,所以最适合的地方就是在事件里,而接口也是由各个事件自己去实现。
1 /// <summary> 2 /// 微信接口交互凭据授权接口 3 /// </summary> 4 public interface IAccessTokenAuth 5 { 6 string AccessToken { get; set; } 7 }
DispatchBeforeActiveHandler的由来
我们要想清楚这个接口需要在什么时候执行?
就以当前的业务来说,肯定是在执行微信指令之前填充。
所以执行顺序当然是在普通的调度处理器执行前,为了统一执行方式,我们也需要把这个看作是一个调度处理器,只是执行顺序特殊一些。
因为调度器建立关系网是根据特性(Attribute)来的,我们就让这个特殊的调度器继承自DispatchHandlerAttribute的类。
1 [AttributeUsage(AttributeTargets.Method)]
2 public class DispatchBeforeActiveHandlerAttribute : DispatchHandlerAttribute
3 {
4 public DispatchBeforeActiveHandlerAttribute()
5 : base(typeof(DispatchHandlerAttribute))
6 {
7
8 }
9 }
如何使用IAccessTokenAuth? |
以创建菜单为例
1 /// <summary> 2 /// 创建菜单事件 3 /// </summary> 4 public class CreateMenuEvent : DispatchEvent, IAccessTokenAuth 5 { 6 public string AccessToken { get; set; } 7 8 public MenuList MenuList { get; set; } 9 }
如何使用DispatchBeforeActiveHandler? |
根据业务分类,我们把FillAccessToken方法加到AccessTokenCommand类中
1 /// <summary>
2 /// 微信交互接口凭证命令
3 /// </summary>
4 public class AccessTokenCommand
5 {
6 #region 静态构造函数
7
8 static AccessTokenCommand()
9 {
10 AutoUpdateCache.Add(CacheKeySet.AccessToken.ToString(), new AutoUpdateItem()
11 {
12 UpdateValue = (AutoUpdateItem autoUpdateItem) =>
13 {
14 var accessTokenInfo = CommandHelper.GetWeChatResponseObject<AccessTokenInfo>(new AccessTokenCommandRequest());
15
16 autoUpdateItem.ExpiredSeconds = accessTokenInfo.ExpiresIn - 30;//预留过期时效,防止提前过期
17 autoUpdateItem.Value = accessTokenInfo;
18 }
19 });
20 }
21
22 /// <summary>
23 /// 填充微信接口交互凭据
24 /// </summary>
25 /// <param name="e"></param>
26 [DispatchBeforeActiveHandler()]
27 public void FillAccessToken(DispatchEvent e)
28 {
29 IAccessTokenAuth accessTokenAuth = e as IAccessTokenAuth;
30 if (accessTokenAuth == null)
31 {
32 return;
33 }
34
35 var getAccessTokenEvent = new GetAccessTokenEvent();
36 Dispatcher.ActiveEvent(getAccessTokenEvent);
37
38 accessTokenAuth.AccessToken = getAccessTokenEvent.AccessTokenInfo.AccessToken;
39 }
40
41 #endregion
42
43 /// <summary>
44 /// 获取微信交互接口凭证
45 /// </summary>
46 /// <param name="e"></param>
47 [DispatchHandler(typeof(GetAccessTokenEvent))]
48 public void GetAccessToken(GetAccessTokenEvent e)
49 {
50 e.AccessTokenInfo = AutoUpdateCache.GetValue<AccessTokenInfo>(CacheKeySet.AccessToken.ToString());
51 }
52 }
我们看到FillAccessToken有一个参数DispatchEvent,一个原因是我们需要为这个事件内的AccessToken赋值,还有一个原因是它是一个基类,这样我们就可以处理任何的事件。
另外FillAccessToken判断DispatchEvent是不是IAccessTokenAuth接口,来判断是否需要填充AccessToken。
为什么是做在这里而不是在调度器?因为调度器尽量保持中立,不要接触过于细节的业务逻辑,所以放在这里来校验是否需要填充AccessToken更合适。
如何让调度器支持DispatchBeforeActiveHandler? |
1.添加一个激活前处理器列表
2.建立关系网时,分开处理DispatchBeforeActiveHandler和DispatchHandler
3.在ActiveEvent方法中,执行DispatchHandler之前,先循环遍历DispatchBeforeActiveHandler
1 /// <summary> 2 /// 调度器 3 /// </summary> 4 public class Dispatcher 5 { 6 /// <summary> 7 /// 调度关系网 8 /// </summary> 9 private static Dictionary<Type, List<DispatchHandlerAttribute>> _dicDispatchRelativeNetwork = new Dictionary<Type, List<DispatchHandlerAttribute>>(); 10 11 /// <summary> 12 /// 激活前处理器列表 13 /// </summary> 14 private static List<DispatchHandlerAttribute> _lstBeforeActiveHandler = new List<DispatchHandlerAttribute>(); 15 16 /// <summary> 17 /// 建立调度关系网 18 /// </summary> 19 /// <param name="assembly"></param> 20 public static void BuildDispatchRelationship(Assembly assembly) 21 { 22 Logger.Info("调度器:开始建立调度关系网..."); 23 24 var types = assembly.GetTypes(); 25 26 foreach (var type in types) 27 { 28 var methods = type.GetMethods(); 29 30 foreach (var method in methods) 31 { 32 var attribute = method.GetCustomAttributes(typeof(DispatchHandlerAttribute), true).FirstOrDefault(); 33 if (attribute != null) 34 { 35 CheckParameterRule(method); 36 37 var handler = attribute as DispatchHandlerAttribute; 38 handler.DispatchInstance = Activator.CreateInstance(type); 39 handler.ActionMethodInfo = method; 40 41 if (attribute is DispatchBeforeActiveHandlerAttribute) 42 { 43 AddBeforeActiveHandler(handler); 44 } 45 else 46 { 47 AddDispatchRelation(handler); 48 } 49 } 50 } 51 } 52 } 53 54 /// <summary> 55 /// 添加激活前处理器 56 /// </summary> 57 /// <param name="handler">调度处理器</param> 58 private static void AddBeforeActiveHandler(DispatchHandlerAttribute handler) 59 { 60 _lstBeforeActiveHandler.Add(handler); 61 62 Logger.Info(string.Format("调度器:增加激活前处理器 [{0}]-[{1}.{2}]", handler.ObservedDispatchEventType.Name, handler.DispatchInstance.GetType().Name, handler.ActionMethodInfo.Name)); 63 } 64 65 /// <summary> 66 /// 添加调度关系 67 /// </summary> 68 /// <param name="handler">调度处理器</param> 69 private static void AddDispatchRelation(DispatchHandlerAttribute handler) 70 { 71 var eventType = handler.ObservedDispatchEventType; 72 73 if (!_dicDispatchRelativeNetwork.ContainsKey(eventType)) 74 { 75 _dicDispatchRelativeNetwork.Add(eventType, new List<DispatchHandlerAttribute>()); 76 } 77 78 _dicDispatchRelativeNetwork[eventType].Add(handler); 79 80 Logger.Info(string.Format("调度器:建立新的关系网 [{0}]-[{1}.{2}]", eventType.Name, handler.DispatchInstance.GetType().Name, handler.ActionMethodInfo.Name)); 81 } 82 83 /// <summary> 84 /// 检查参数规则 85 /// </summary> 86 /// <param name="method"></param> 87 private static void CheckParameterRule(MethodInfo method) 88 { 89 var parameters = method.GetParameters(); 90 if (parameters == null || 91 parameters.Length != 1 || 92 (!parameters.FirstOrDefault().ParameterType.Equals(typeof(DispatchEvent)) && 93 !parameters.FirstOrDefault().ParameterType.BaseType.Equals(typeof(DispatchEvent)) 94 )) 95 { 96 throw new Exception(string.Format("DispatchHandler - [{0}]的参数必须是只有一个且继承自DispatchEvent", method.Name)); 97 } 98 } 99 100 /// <summary> 101 /// 激活事件 102 /// </summary> 103 /// <param name="dispatchEvent">调度事件</param> 104 public static void ActiveEvent(DispatchEvent dispatchEvent) 105 { 106 var type = dispatchEvent.GetType(); 107 108 if (!_dicDispatchRelativeNetwork.ContainsKey(type)) 109 { 110 Logger.Error(string.Format("调度器:当前事件[{0}]没有找到绑定的Handler", type.FullName)); 111 return; 112 } 113 114 _lstBeforeActiveHandler.ForEach(action => 115 { 116 ActiveAction(action, dispatchEvent); 117 }); 118 119 _dicDispatchRelativeNetwork[type].ForEach(action => 120 { 121 ActiveAction(action, dispatchEvent); 122 }); 123 } 124 125 private static void ActiveAction(DispatchHandlerAttribute action, DispatchEvent dispatchEvent) 126 { 127 try 128 { 129 action.ActionMethodInfo.Invoke(action.DispatchInstance, new object[] { dispatchEvent }); 130 } 131 catch (Exception ex) 132 { 133 if (ex.InnerException != null && ex.InnerException.GetType().Equals(typeof(WCFLib.ExceptionExtension.GException))) 134 { 135 throw ex.InnerException; 136 } 137 else 138 { 139 throw; 140 } 141 } 142 } 143 }
激活Event的代码变成什么样子? |
1 var getAccessTokenEvent = new GetAccessTokenEvent(); 2 Dispatcher.ActiveEvent(getAccessTokenEvent); 3 var accessTokenInfo = getAccessTokenEvent.AccessTokenInfo;
是的,你没看错,没有变化!
同时,在真正的创建菜单事件中也不需要处理任何有关AccessToken的业务逻辑。
因为创建菜单的指令也很简单,我就不贴代码了,跟GetAccessToken差不多,只是处理微信指令时逻辑上不太一样而已。
为什么我喜欢造轮子? |
在满足技术条件的基础上,轮子的可控性更高,可扩展性也更高。
轮子的发展方向会根据我们的业务进行调整,同时去掉了我们并不需要的一些附带功能。
绝大多数第三方是做一个通用的类库,而我造的轮子不是,我造的是更贴近系统业务的,更贴近团队技术水平的。
不论是从封装也好,调用也好,各个角度来看,我们都能够控制轮子的形态,让轮子成为我们非常熟悉的调用方式。学习成本会也自然就会降低。
轮子有好有坏,看你从哪个方面来看,这个不需要去争论,我们只需要保持着可用性、可扩展性、性能达标的标准去走即可。
最后,我并不鼓励盲目的造轮子,这是我从始至终的观点,但我仍然坚持造最适合自己的轮子,两者并不冲突。 ^_^
自动签名