【轮子狂魔】轮子的魅力之扩展能力 – 寻找和谐 – 引力一族

  • A+
所属分类:架构设计
序言

如果你是第一次看本文,建议先看下前面两篇,否则你可能会一头雾水

看过上一篇【轮子狂魔】打造简易无配置的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     }

View Code

 

DispatchBeforeActiveHandler的由来

我们要想清楚这个接口需要在什么时候执行?

就以当前的业务来说,肯定是在执行微信指令之前填充。

所以执行顺序当然是在普通的调度处理器执行前,为了统一执行方式,我们也需要把这个看作是一个调度处理器,只是执行顺序特殊一些。

因为调度器建立关系网是根据特性(Attribute)来的,我们就让这个特殊的调度器继承自DispatchHandlerAttribute的类

  

【轮子狂魔】轮子的魅力之扩展能力 - 寻找和谐 - 引力一族【轮子狂魔】轮子的魅力之扩展能力 - 寻找和谐 - 引力一族

1     [AttributeUsage(AttributeTargets.Method)]
2     public class DispatchBeforeActiveHandlerAttribute : DispatchHandlerAttribute
3     {
4         public DispatchBeforeActiveHandlerAttribute()
5             : base(typeof(DispatchHandlerAttribute))
6         {
7 
8         }
9     }

View Code

 

如何使用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     }

View Code

 

如何使用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     }

View Code

我们看到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     }

View Code

 

激活Event的代码变成什么样子?

 

1             var getAccessTokenEvent = new GetAccessTokenEvent();
2             Dispatcher.ActiveEvent(getAccessTokenEvent);
3             var accessTokenInfo = getAccessTokenEvent.AccessTokenInfo; 

是的,你没看错,没有变化!

同时,在真正的创建菜单事件中也不需要处理任何有关AccessToken的业务逻辑。

因为创建菜单的指令也很简单,我就不贴代码了,跟GetAccessToken差不多,只是处理微信指令时逻辑上不太一样而已。

 

为什么我喜欢造轮子?

 在满足技术条件的基础上,轮子的可控性更高,可扩展性也更高。

 轮子的发展方向会根据我们的业务进行调整,同时去掉了我们并不需要的一些附带功能。

 绝大多数第三方是做一个通用的类库,而我造的轮子不是,我造的是更贴近系统业务的,更贴近团队技术水平的。

 不论是从封装也好,调用也好,各个角度来看,我们都能够控制轮子的形态,让轮子成为我们非常熟悉的调用方式。学习成本会也自然就会降低。

 轮子有好有坏,看你从哪个方面来看,这个不需要去争论,我们只需要保持着可用性、可扩展性、性能达标的标准去走即可。

 最后,我并不鼓励盲目的造轮子,这是我从始至终的观点,但我仍然坚持造最适合自己的轮子,两者并不冲突。 ^_^

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: