灵巧多叉树 IAgileMultiTree -- ESBasic 可复用的.NET类库(23)
1.缘起:
我们还是以多叉树IMultiTree章节介绍的那个例子来继续讲解。假设,在系统运行的过程中,集团又成立了分公司D及其下属的一些单位,这些资料已经被存入了数据库中,但是这些信息在我们当前正在运行的MultiTree实例中并不存在,如果此时向MultiTree实例请求与D分公司相关的信息,那么将一无所获。除非,你手动地将D分公司及其下属单位的节点值添加到MultiTree实例中。这是一个比较麻烦的动作。
试着设想一下这样一种情况,当我们要请求的节点在当前MultiTree实例中并不存在时,多叉树能自动地加载目标节点到MultiTree实例中,而且,如果需要的话,它会自动加载当前MultiTree实例中不存在的而目标节点又需要的其所有直接和间接的上级节点。比如,我们要向当前MultiTree实例请求D分公司下的部门M下的K小组,那么MultiTree会首先自动加载D分公司节点,再自动加载M部门节点,再自动加载K小组节点,然后返回K小组节点。
我设计了ESBasic.ObjectManagement.Trees. IAgileMultiTree来对IMultiTree进行扩展,以实现节点的自动加载。
为了完成自动加载,需要借助一个类似我们前面介绍的IObjectRetriever对象获取器的接口,这里叫做“节点获取器”IAgileNodePicker,正是它完成从存储设备提取节点值的任务的。
灵巧多叉树的形象示意图如下:
相比于IMultiTree,IAgileMultiTree最大的特色在于能够自动加载存储设备中已经存在但在当前多叉树实例中还不存在的节点或体系。这样,我们每次向多叉树实例请求特定ID的节点时,只要该ID在存储设备中存在,多叉树就一定不会返回null。
2.适用场合:
在需要使用IMultiTree的基础上,如果还满足以下条件,则可以转而使用更高级的IAgileMultiTree来进一步减轻你要做的工作。
(1)在运行的过程中,节点只会不断地增加,但从来不需要被移除。
(2)每个节点值都必须有一个上节介绍的节点路径Path属性(也称为SequenceCode)。
(3)允许节点延迟加载到多叉树中。对于一些节点来说,只有其第一次被请求时,IAgileMultiTree才会去加载它。
3.设计思想与实现
灵巧多叉树是对普通多叉树IMultiTree的扩展,所以IAgileMultiTree接口继承了IMultiTree接口,IAgileMultiTree源码如下所示:
{
char SequenceCodeSplitter { get;set; }
IAgileNodePicker<TVal> AgileNodePicker { set;}
/// <summary>
/// Initialize 加载和初始化整个AgileTree。该方法用于取代基类的IMultiTree.Initialize方法。
/// </summary>
void Initialize() ;
/// <summary>
/// EnsureNodeExist 用于确保目标节点及其所有上级节点都存在树中,如果都存在,则直接返回目标节点。
/// 否则,通过IAgileMultiTreeHelper来加载所需要的所有上级节点和目标节点,然后返回目标节点。
/// 如果即使通过IAgileMultiTreeHelper也无法提取某个上级节点或目标节点,则返回null。
/// </summary>
MNode<TVal> EnsureNodeExist(string nodeSequenceCode);
}
首先,我们看到IAgileMultiTree接口增加了SequenceCodeSplitter属性,用于指示节点路径SequenceCode采用的分隔符号。
AgileNodePicker属性用于从存储设备加载节点值,它是IAgileNodePicker类型,这个接口从我们前面介绍的对象获取器IObjectRetriever继承而来,其类图如下所示:
相对IObjectRetriever来说,IAgileNodePicker只增加了一个获取根节点值的PickupRoot方法。
我们再来看IAgileMultiTree提供了一个自己的不带参数的Initialize方法,这表明IAgileMultiTree不再需要使用基类IMultiTree提供的初始化方法,而是可以在不需要任何参数的情况下,自我进行初始化。这正是通过IAgileNodePicker接口来做到了,因为通过IAgileNodePicker接口,IAgileMultiTree可以从存储设备中提取根节点值和所有其它已存在的节点值。
EnsureNodeExist方法用于取代基类的GetNodeByPath方法。正式这个方法体现出了IAgileMultiTree的最主要特色。当使用GetNodeByPath方法访问一个在当前IAgileMultiTree实例中不存在的节点时,IAgileMultiTree首先会逐级加载该节点的直接或节间上级节点以及它目标节点到内存中,然后再返回该节点。
AgileMultiTree从MultiTree继承,关于其实现要注意以下几点:
(1)Initialize方法实现时借助IAgileNodePicker从存储设备获取根节点和其它节点,然后再调用基类的Initialize方法进行初始化。即AgileMultiTree的Initialize方法重用了基类的Initialize方法来构建多叉树。
(2)EnsureNodeExist方法的实现非常关键。首先,追加节点时必须要保证同步性,即不能由两个线程同时在追加节点,否则可能引发冲突。其次,采用迭代的方式依据索引深度值的递增顺序逐级追加未加载的节点。
如果在追加节点的过程中,SequenceCode中的某个ID对应的节点值在存储设备中不存在,那么,追加过程将被迫中断,并且EnsureNodeExist方法也将返回null。
4. 使用时的注意事项
(1)如果说在IMultiTree中,节点路径SequenceCode还是一个可有可无的特性,那么在IAgileMultiTree中,这个特性就是必须的。IAgileMultiTree对IMultiTree的扩展的主要功能都是基于SequenceCode来完成的。
(2)IAgileMultiTree采用了延迟加载(Lazy Load)的策略,该策略可以最大限度地节省内存空间的使用。更进一步,如果不需要预加载一些经常使用的节点,或者在程序启动时,无法预知哪些节点会被经常使用,那么可以在IAgileMultiTree初始化时只加载根节点root。这可以通过在实现IAgileNodePicker接口的RetrieveAll方法时,只返回一个包含了根节点值的字典来做到。
(3)由于IAgileMultiTree延迟加载节点的特性,所以那些从来不被直接和间接请求的节点可能永远都不会被加载,除了那些在IAgileMultiTree的初始化时加载进来的节点外。所谓被间接请求的意思是,我们请求的是某个子节点A,但是其父节点B在IAgileMultiTree实例中也不存在,所以IAgileMultiTree会先加载B,然后再加载并返回A,这种情况下,B就是被间接请求的节点。
(4)基类的GetNodeByPath方法依旧可用,只是通过该方法请求未被加载的节点,它将会返回null。也就是说,它不会自动加载所需要的节点,这并不是IAgileMultiTree。所以,在应用IAgileMultiTree的时候,不要调用其GetNodeByPath方法,而是要调用EnsureNodeExist方法来取代它。
5.扩展
灵巧多叉树IAgileMultiTree暂时没有任何扩展。
注: ESBasic已经开源,点击这里下载源码。
ESBasic讨论QQ群:37677395
ESBasic开源前言