Fork me on GitHub
扩展能力

扩展能力

序言

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

看过上一篇【轮子狂魔】打造简易无配置的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差不多,只是处理微信指令时逻辑上不太一样而已。

 

为什么我喜欢造轮子?

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

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

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

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

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

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

posted on 2014-12-22 21:33  HackerVirus  阅读(251)  评论(0编辑  收藏  举报