扩展能力
序言 |
如果你是第一次看本文,建议先看下前面两篇,否则你可能会一头雾水
看过上一篇【轮子狂魔】打造简易无配置的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三个层中,所以最适合的地方就是在事件里,而接口也是由各个事件自己去实现。

/// <summary>
/// 微信接口交互凭据授权接口
/// </summary>
public interface IAccessTokenAuth
{
string AccessToken { get; set; }
}
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 }
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类中

/// <summary>
/// 微信交互接口凭证命令
/// </summary>
public class AccessTokenCommand
{
#region 静态构造函数
static AccessTokenCommand()
{
AutoUpdateCache.Add(CacheKeySet.AccessToken.ToString(), new AutoUpdateItem()
{
UpdateValue = (AutoUpdateItem autoUpdateItem) =>
{
var accessTokenInfo = CommandHelper.GetWeChatResponseObject<AccessTokenInfo>(new AccessTokenCommandRequest());
autoUpdateItem.ExpiredSeconds = accessTokenInfo.ExpiresIn - 30;//预留过期时效,防止提前过期
autoUpdateItem.Value = accessTokenInfo;
}
});
}
/// <summary>
/// 填充微信接口交互凭据
/// </summary>
/// <param name="e"></param>
[DispatchBeforeActiveHandler()]
public void FillAccessToken(DispatchEvent e)
{
IAccessTokenAuth accessTokenAuth = e as IAccessTokenAuth;
if (accessTokenAuth == null)
{
return;
}
var getAccessTokenEvent = new GetAccessTokenEvent();
Dispatcher.ActiveEvent(getAccessTokenEvent);
accessTokenAuth.AccessToken = getAccessTokenEvent.AccessTokenInfo.AccessToken;
}
#endregion
/// <summary>
/// 获取微信交互接口凭证
/// </summary>
/// <param name="e"></param>
[DispatchHandler(typeof(GetAccessTokenEvent))]
public void GetAccessToken(GetAccessTokenEvent e)
{
e.AccessTokenInfo = AutoUpdateCache.GetValue<AccessTokenInfo>(CacheKeySet.AccessToken.ToString());
}
}
我们看到FillAccessToken有一个参数DispatchEvent,一个原因是我们需要为这个事件内的AccessToken赋值,还有一个原因是它是一个基类,这样我们就可以处理任何的事件。
另外FillAccessToken判断DispatchEvent是不是IAccessTokenAuth接口,来判断是否需要填充AccessToken。
为什么是做在这里而不是在调度器?因为调度器尽量保持中立,不要接触过于细节的业务逻辑,所以放在这里来校验是否需要填充AccessToken更合适。
如何让调度器支持DispatchBeforeActiveHandler? |
1.添加一个激活前处理器列表
2.建立关系网时,分开处理DispatchBeforeActiveHandler和DispatchHandler
3.在ActiveEvent方法中,执行DispatchHandler之前,先循环遍历DispatchBeforeActiveHandler

/// <summary>
/// 调度器
/// </summary>
public class Dispatcher
{
/// <summary>
/// 调度关系网
/// </summary>
private static Dictionary<Type, List<DispatchHandlerAttribute>> _dicDispatchRelativeNetwork = new Dictionary<Type, List<DispatchHandlerAttribute>>();
/// <summary>
/// 激活前处理器列表
/// </summary>
private static List<DispatchHandlerAttribute> _lstBeforeActiveHandler = new List<DispatchHandlerAttribute>();
/// <summary>
/// 建立调度关系网
/// </summary>
/// <param name="assembly"></param>
public static void BuildDispatchRelationship(Assembly assembly)
{
Logger.Info("调度器:开始建立调度关系网...");
var types = assembly.GetTypes();
foreach (var type in types)
{
var methods = type.GetMethods();
foreach (var method in methods)
{
var attribute = method.GetCustomAttributes(typeof(DispatchHandlerAttribute), true).FirstOrDefault();
if (attribute != null)
{
CheckParameterRule(method);
var handler = attribute as DispatchHandlerAttribute;
handler.DispatchInstance = Activator.CreateInstance(type);
handler.ActionMethodInfo = method;
if (attribute is DispatchBeforeActiveHandlerAttribute)
{
AddBeforeActiveHandler(handler);
}
else
{
AddDispatchRelation(handler);
}
}
}
}
}
/// <summary>
/// 添加激活前处理器
/// </summary>
/// <param name="handler">调度处理器</param>
private static void AddBeforeActiveHandler(DispatchHandlerAttribute handler)
{
_lstBeforeActiveHandler.Add(handler);
Logger.Info(string.Format("调度器:增加激活前处理器 [{0}]-[{1}.{2}]", handler.ObservedDispatchEventType.Name, handler.DispatchInstance.GetType().Name, handler.ActionMethodInfo.Name));
}
/// <summary>
/// 添加调度关系
/// </summary>
/// <param name="handler">调度处理器</param>
private static void AddDispatchRelation(DispatchHandlerAttribute handler)
{
var eventType = handler.ObservedDispatchEventType;
if (!_dicDispatchRelativeNetwork.ContainsKey(eventType))
{
_dicDispatchRelativeNetwork.Add(eventType, new List<DispatchHandlerAttribute>());
}
_dicDispatchRelativeNetwork[eventType].Add(handler);
Logger.Info(string.Format("调度器:建立新的关系网 [{0}]-[{1}.{2}]", eventType.Name, handler.DispatchInstance.GetType().Name, handler.ActionMethodInfo.Name));
}
/// <summary>
/// 检查参数规则
/// </summary>
/// <param name="method"></param>
private static void CheckParameterRule(MethodInfo method)
{
var parameters = method.GetParameters();
if (parameters == null ||
parameters.Length != 1 ||
(!parameters.FirstOrDefault().ParameterType.Equals(typeof(DispatchEvent)) &&
!parameters.FirstOrDefault().ParameterType.BaseType.Equals(typeof(DispatchEvent))
))
{
throw new Exception(string.Format("DispatchHandler - [{0}]的参数必须是只有一个且继承自DispatchEvent", method.Name));
}
}
/// <summary>
/// 激活事件
/// </summary>
/// <param name="dispatchEvent">调度事件</param>
public static void ActiveEvent(DispatchEvent dispatchEvent)
{
var type = dispatchEvent.GetType();
if (!_dicDispatchRelativeNetwork.ContainsKey(type))
{
Logger.Error(string.Format("调度器:当前事件[{0}]没有找到绑定的Handler", type.FullName));
return;
}
_lstBeforeActiveHandler.ForEach(action =>
{
ActiveAction(action, dispatchEvent);
});
_dicDispatchRelativeNetwork[type].ForEach(action =>
{
ActiveAction(action, dispatchEvent);
});
}
private static void ActiveAction(DispatchHandlerAttribute action, DispatchEvent dispatchEvent)
{
try
{
action.ActionMethodInfo.Invoke(action.DispatchInstance, new object[] { dispatchEvent });
}
catch (Exception ex)
{
if (ex.InnerException != null && ex.InnerException.GetType().Equals(typeof(WCFLib.ExceptionExtension.GException)))
{
throw ex.InnerException;
}
else
{
throw;
}
}
}
}
激活Event的代码变成什么样子? |
1 var getAccessTokenEvent = new GetAccessTokenEvent(); 2 Dispatcher.ActiveEvent(getAccessTokenEvent); 3 var accessTokenInfo = getAccessTokenEvent.AccessTokenInfo;
是的,你没看错,没有变化!
同时,在真正的创建菜单事件中也不需要处理任何有关AccessToken的业务逻辑。
因为创建菜单的指令也很简单,我就不贴代码了,跟GetAccessToken差不多,只是处理微信指令时逻辑上不太一样而已。
为什么我喜欢造轮子? |
在满足技术条件的基础上,轮子的可控性更高,可扩展性也更高。
轮子的发展方向会根据我们的业务进行调整,同时去掉了我们并不需要的一些附带功能。
绝大多数第三方是做一个通用的类库,而我造的轮子不是,我造的是更贴近系统业务的,更贴近团队技术水平的。
不论是从封装也好,调用也好,各个角度来看,我们都能够控制轮子的形态,让轮子成为我们非常熟悉的调用方式。学习成本会也自然就会降低。
轮子有好有坏,看你从哪个方面来看,这个不需要去争论,我们只需要保持着可用性、可扩展性、性能达标的标准去走即可。
最后,我并不鼓励盲目的造轮子,这是我从始至终的观点,但我仍然坚持造最适合自己的轮子,两者并不冲突。 ^_^