面向接口可扩展框架之类型转化基础服务
新框架正在逐步完善,可喜可贺的是基础服务部分初具模样了,给大家分享一下
由于基础服务涉及面太广,也没开发完,这篇只介绍其中的类型转化部分,命名为类型转化基础服务,其实就是基础服务模块的类型转化子模块
说到类型转化必须要弄清楚.net的类型,类型都不清楚何来类型转化
1、Primitive类型
1.1 这个概念估计很多人都没听说过,Primitive不是一个新类型,而是.net类型中最基本的一种分类,是基元类型的意思
MS将类型分为三类:Primitive(基元类型)、Complex(复合类型) 和 Collection(集合类型),Type 提供了IsPrimitive 属性
1.2 哪些类型属于Primitive呢?
参考链接:http://msdn.microsoft.com/en-us/library/system.type.isprimitive(v=vs.110).aspx
”The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.“
1.3 看到这里还是挺失望的,我们常用的类型Decimal和String并不含在内
有的同学可能说,别说那两个了,里面就除了Char和Double其他都没用过;老实说MS真的把.net程序员彻底惯坏了
其实Boolean就是我们常用的bool,Int32就是int,Int64就是long,Single就是float
1.4 Primitive类型有什么用呢?
Primitive是基元类型,可以这样理解,其他类型都是由Primitive类型构成的或者衍生的,.net对Primitive支持最好,"有些地方"对于非Primitive类型支持不好,可能造成"灵异"事件(对于一般的项目是很难碰到的,如果你能碰到,说明你对.net钻研的比较深了,普通的工程师就更不要嚷嚷说.net类型有缺陷不敢用之说,最多底层框架开发者或者架构师小心一点)
这里就不展开了
2、IConvertible类型
IConvertible并不是新维度的类型类别,IConvertible是MS定义的一个重要接口,我这里讲的IConvertible类型特指Ms定义的基本类型中继承了该接口的类型
我来稍作解析一下,继承了IConvertible的类型非常强大,可以方便的转化为常用的这15种类型,也正是这15种类型继承了IConvertible接口
也就是这15种类型可以方便的相互转化(其实不一定的,有些转化是会出错的)
IConvertible类型转化具有高度的扩展性,可以传一个格式化的参数(IFormatProvider)
注:在我看来IFormatProvider主要是作用是字符串(string)类型和其他14种类型相互转化之用,string被Ms定义成了"通用类型",我认为是数据形态之一(我把数据分成几个形态,强类型,弱类型(object),string,二进制(byte[]),数据流(Stream),这个会在序列化基础服务中再详细讲解)
3、IFormattable类型
IFormattable和前面IConvertible类似,也是Ms定义的一个重要接口,继承这个接口的类型非常多,这个接口主要是把对象转化为特殊(格式化)的字符串。
基本类型中的byte\DateTime\Decimal\Double\short\int\long\sbyte\float\ushort\uint\ulong等都继承,除此之外还有很多系统类型也都继承IConvertible(这里不再枚举),我们自定义类型也可以方便的实现IConvertible接口
鉴于字符串(string)的重要性,该接口相关的服务对应用开发非常有用(但是很多人都不清楚)
4、"基本"类型
这里是我定义的”基本类型“,我认为有这些类型就足够开发使用,框架对此之外的类型不予"支持",或者这些类型优先支持
包含类型列表:bool、byte、char、decimal、double、float、int、long、string、DateTime、byte[]
注1:在现在内存这么便宜的时代,我认为短数据类型的作用不大,短数据类型都使用int就好了
注2:我也不喜欢使用无符号类型,所以又省略了一大批类型,对于数字类型Ms使用==0表示"逻辑空",我使用<=0表示"逻辑空",后面还有相关例子
注3:这样做也是情非得已,类型太多了,其排列组合更多,好在本框架是可扩展框架,使用本框架的时候可以自己扩展支持的类型,当然也可以开发一个类型扩展的组件(类库),需要的时候自己注册进去
现在开始演示类型转化基础服务
一、基本类型转化
1、类型太多,先测试字符串转int
2、不同数字类型转化为Int
这里我要多唠叨一句,我前面说过对”基本类型“以外的不予支持,其实是不"支持"转化为这些类型,这些类型转化为”基本类型“是没有问题的
上面例子里面有小数(浮点)转整数(int)的例子,Ms有自己的一套“四舍五入”的规则,如果不满意可以自己扩展注册进去(一般情况下调用的地方并不需要修改)
另外,会尝试IConvertible的类型相互转化,如果Ms支持的很好的IConvertible转化.这里也没道理不支持的
3、object转化为int
以上例子可以看出,基本类型转化完全没有问题,其实在主框架,我也不打算默认实现复杂自定义类型的转化,这个留给扩展实现
有人可能会说你上面的这些功能So easy,实现过类似功能的多如牛毛,不过是多造个轮子而已。确实,基本类型转化实现简单,但是这里要强调,我做的是可扩展框架,可扩展是亮点,而且非常容易扩展
二、自定义类型转化为基本类型
1、先看自定义类型代码
非常简单还是解读一下,定义了两个类型,customObj明显是个模型(Model)类,customConverter有一个方法传入一个customObj对象返回一个int值
2、再看对象转化代码
首先我们看结果,是我们预期的效果,和customConverter的Get方法的效果一致,但是我们没有直接调用customConverter.Get,甚至好像和customConverter类型都没什么关系
这里有几个关键因素,其一是GlobalServices.Convert方法是怎么运行的;其二是GlobalServices.CreateContainer()是什么鬼
三、源码解析
1、GlobalServices.Convert方法解析
这个类型转化还是挺复杂的,挑主要的来解读
1.1 TryConvert是尝试类型转化,如果转化失败可以换一种方法再做
这种方式在本框架中有大量应用,其实就就形成一个”策略链“,每个”策略“判断一个这个问题自己是否是自己可以处理的类型,不能处理,下一个策略继续,这样非常便于扩展
在TryConvert中先调用个接口IEntityConvert,转化失败再调用IEntityAccess用来转化
这里有一个特别重要的事情,就是这个接口的对象从哪里来,这里是来自一个容器对象,这里很清楚的看到本框架的一个扩展点,只要往容器里面添加"策略"就可以增强本框架的类型转化功能,这是我为什么说主框架不打算实现复杂的类型转化,真的非常容易扩展
1.2 再看Convert主方法
A:先按当前类型尝试转化
如果策略库(容器注册)里面有当前两种类型转化的策略,性能是最好的,优先执行
B:转化失败判断当前对象是否为空
对象为null,放弃转化,直接返回默认值
C:Transform.TryConvertByType
这个简单就是强制类型转化,如果T是S的基类,这个时候就可以直接转化过去,也是非常安全的
D:再尝试IConvertible转化
前面说到系统的IConvertible定义了15种基本类型的相互排列组合转化,而且还可以使用IFormatProvider自定义转化,这就是一个强大的转化机器,不能不试
E:再检测返回类型是否为string,如果是string直接调用对象的ToString()
F:尝试通用类型转化(IConvert)
这个地方可以把第三方的Mapper工具封装为IConvert接口注册进来(后面还有讲解)
G:最后尝试把s对象转化为字符串,然后把字符串转化为目标类型(T)对象
其实这里把字符串作为基本数据格式,相当于与对s对象序列化为字符串,然后把字符串反序列化为T类型对象,也不怪我怎么用,.net所有类型都有一个ToString()方法,所以string是个不错的中间类型
功能是不是非常强大,也非常有别于很多类型转化工具,上来先反射,获取类型元数据,然后调用属性和字段,现在还没做测试,我这种方式可能会有明显的性能优势,但是性能现在不是我最想考虑的问题,我现在考虑的是怎么可以非常简单的扩展
2、GlobalServices.CreateContainer()是什么鬼
CreateContainer定义
A:从上面可以看出GlobalServices.CreateContainer就是一个再普通不过的容器,默认实例的容器名是GlobalServices
B:前面有提到容器是可以扩展的,只要我们使用的容器支持(比如Unity、Spring.net等),我们完全可以使用配置文件来扩展类型转化,还可以使用非常炫的IOC和AOP等特性
C:说到这里,现在"业界"有几个类型转化工作可以做到?我稍微看了一个现在非常流行的Automapper,自定义映射确实没问题,但是映射过程还可以通过配置文件扩展或者IOC和AOP的我没见识过,如果有人看到过请告知,我要好好学习学习
D:早期看过这篇文章的可能发现,变化很大,我把GlobalServices由静态类修改可以实例化的形式,这样就允许我们创建多套服务配置管理对象(使用子容器技术,继承默认服务并覆盖少数服务),进一步提高可扩展性
3、其实GlobalServices容器和其他容器还是有点区别的,继续深挖源码
这里有一个不起眼的地方加了一行代码,给GlobalServices容器”吃小灶“
不挖不知道,一挖吓一跳,有一种”柳暗花明又一村“的感觉
哈哈,我就说我要对每个容器做包装,”居心不良“吧;哈哈,这样说太难听了,一句话"还是为了更好的扩展"。
4、没办法了,还得继续挖GlobalServices.CheckServices
这里可以看到框架支持的"基本类型",把这些转化策略都注册到容器中,主要是担心容器注册的服务不够用,把不足的基本类型转化服务都注册上
当然如果容器中已经存在同类型的"服务",这里的注册是会忽略的
同样,就算是这里注册的服务,如果觉得不好用,也可以在外面重新注册并覆盖
还有一点,前面截图可以明显看出"基础服务模块"的基本样子,但是除了”类型转化子模块“外,其他子模块都还没开发出来
四、拆分使用
1、字符串转时间测试
以上两个日期字符串我们用来转化为时间,一个成功一个失败
这个例子我想说明两个问题
其一、每个对象转化服务都可以单独使用而且性能最好,省去了复杂的的策略逻辑、容器操作,而且执行逻辑明确
其二、使用特定的服务不便于扩展,如果这样的代码有bug需要替换,每个地方去改,很容易导致遗留问题(没改全)
其三、通用服务都是默认实现,总有局限性,有些特殊问题无法解决(以上调用逻辑和使用全局服务的逻辑一致,也会出现异常),总有特事特办的,只要控制数量还是没有问题的
2、对特殊字符串转转时间测试
以上顺利转化成功,可喜可贺,事实上有的时候特事特办能做到更高的性能或者更好的效果
这里还有一个我要说的就是“面向对象编程”,在我看来很多人都是在做"面向类"编程,因为他们的类型都只有一个实例或者都只能简单的new()来构造,我特别强调类和对象是一对多的关系,一个类可以初始化多个对象,通过不同的字段和属性能达到不同的效果。这样才可以把类和对象用到极致。不是有句名言“程序等于算法加数据结构”。对我而言,每个对象就是一个独立的程序,字段(属性)就是数据结构,定义的方法(函数)就是算法。好的对象是可以独立迁移的。如果使用容器配置管理,我要迁移程序(组件对象)就只要复制对应的配置节点和dll即可。
当然不是说一定非要硬编码,这样的“特事特办”也可以在容器中配置特殊的节点来实现
3、从容器中获取特殊服务对特殊字符串转转时间测试
效果还不错吧,而且也是面向接口的,要扩展也是非常简单
以下是容器配置:
以上是Unity容器配置,能实现类似配置的容器很多,选择也很多,再强调一次逻辑代码没有必要强依赖任何固定的容器
五、IFormattable转化为字符串服务
1、时间格式化的例子
效果不错吧
有人说使用DateTime的ToString()方法传一个字符串参数吗?确实,底层调用的是同一个逻辑。
我们知道DateTime的ToString()可以传一个格式字符串,那这个格式字符串硬编码写死不太好吧,如果哪天我们要的字符串格式使用ToString()无法实现怎么办。ToString(format)是一种转化字符串的方式,但不是唯一的方式。 这就是面向接口的好处,我们依赖的是把一个把DateTime转化为string的服务(接口),甚至是一个把object转化为string的服务。这样我们的服务就更强大,更容易扩展了。
2、使用容器再做一个上面的例子
效果和前面一样
以下是容器配置:
当然还可以注册为全局服务(使用GlobalServices.Convert就可以调用到,覆盖默认的ToString()),这样就更方便了(但是要慎重,全局格式一定定义的足够通用)
六、扩展性演示
1、逻辑空(int转bool)
前面有提到,我是把>0为true的,<=0为false,我一直用的很方便,我叫做"逻辑空",但是ms并不是这样处理的,如果有的团队还是希望按ms的规则怎么办,扩展啊
2、把上例中的注释打开就切换为ms模式(==0为false)的扩展
上面的结果就是ms模式了
3、现在来看一下是怎么扩展的
以上代码很清楚,扩展很简单,获取服务配置的容器,注入服务并覆盖原服务,就完成了扩展
当然如果自己想扩展基础服务中的任何功能都不是问题,只需要实现对应服务接口,如后注册到服务容器中去就可以了
七、IConvertible转化功能
1、IConvertible转化int的例子
A:这里演示的就是IConvertible(15种类型)转化为int的部分例子,完全没有问题,调用也很方便
B:有的人可能会说,Ms提供了IConvertible搞定15中类型相互转化,你写那些类型转化完全是重复造轮子,这是没有理解这个框架的思想
注:在这里使用Convert<IConvertible, int>转化short类型的值(123)是没有使用Convert<short, int>性能好的,不仅仅是类型转化的问题,完全不是一套逻辑
C:这个框架(可扩展)不只是提供某种功能,他要提供高度可扩展性,以便对任意的细分"领域"都可以扩展优化效果和性能
D:特别是性能这块,框架优先获取最细分领域的服务来处理问题,我有一个开发思想,通用工具解决大部分(60%到90%)的问题,有特殊性能或业务需求的单独写代码来支持,这个框架就是对这种思想的强有力支持
注:这个框架和其他框架有个明显的区别,一般框架是越扩展(打补丁)性能越差,这个框架是相反的,一般情况下是越扩展性能越好(除非扩展进来的服务太差,那还叫扩展吗)
八、自定义类型转化
1、继承类型转化试试
木有问题,转化成功
2、取消继承再测
“哦,NO!框架出bug了,这么简单的事情也出错”
不是的,大家好好看这个错误信息的内容,可扩展框架提醒你,需要扩展
3、按提示的扩展IEntityConvert再测
又转化成功了,说明可扩展此言不虚
有人说你这不就是硬编码吗?类型转化这等小事,还要一个个硬编码,还让不让人活了。我还要重复一遍,对于特别性能需要的地方,硬编码是有必要的
而诶硬编码类型转化代码不需要你直接调用,是注入到框架中的,调用的地方没有区别(不知道是调用硬编码实现还是默认实现)
有人说你要我每个类型都硬编码就是不对,现在没有这么干的!确实没有必要,90%以上的类型转化都不需要硬编码,再按提示做另一种扩展
4、扩展IConvert再测
再次转化成功,可以使用这种方式扩展自定义类型的默认转化
有人要挑错了,你以上代码哪是任意类型嘛,明明就只能A转B,其他都转成null了。以上只是个示例,现在开源项目中能实现这样转化的工具很多,我真没必要开发到主框架中,拿来用就可以了
我完全可以拿Automapper(或者EmitMapper,LiteMapper等)封装一下,继承IConvert接口,然后注册到框架中来,这才是本框架的精神(集成和整合),还是那句话,如果特定两个类型转化需要高性能,直接硬编码注册进来,所有调用的地方不用改就达到优化的目的
我也不用再纠结哪个Mapper更好,所有Mapper都封装,爱用哪个就注册哪个(当然不建议同一个应用程序使用两个以上的Mapper)