灵巧多叉树 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源码如下所示: 

 

    public interface IAgileMultiTree<TVal> : IMultiTree<TVal> where TVal : IMTreeVal        
    {
        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开源前言

 

时间: 2024-09-30 05:40:00

灵巧多叉树 IAgileMultiTree -- ESBasic 可复用的.NET类库(23)的相关文章

对象获取器IObjectRetriever -- ESBasic 可复用的.NET类库(17)

1.缘起: ESBasic中许多管理对象的容器都用到了这个ESBasic.ObjectManagement.IObjectRetriever接口,所以单独将其提出来介绍一下. 当我们向对象容器(Container)请求某个对象时,也许目标对象还未加载到容器中,这可能是因为容器在初始化的时候就没有加载这个对象,也有可能是因为这个对象是容器初始化以后新增到数据库(当然也有可能是其它的持久化存储)的.在这种情况下,对象容器就可以借助IObjectRetriever来将目标对象从数据库等持久化存储中加载

多叉树 IMultiTree -- ESBasic 可复用的.NET类库(22)

1.缘起:     假设我们要描述一个集团公司的组织结构,这个集团公司的体系分为如下几层:集团.公司.子公司.部门.小组.即一个集团由多个公司构成,每个公司又有几个子公司构成,每个子公司拥有多个部门,每个部门又内分为几个小组.     很明显,这种体系结构就是一个多叉树.我设计了ESBasic.ObjectManagement.Trees.Multiple.IMultiTree来抽象多叉树,其提供了很多简便的方法让我们对多叉树进行节点查询和操作. 多叉树的形象示意图如下: 从上图中我们看到,集团

层级结构缓存IHiberarchyCache -- ESBasic 可复用的.NET类库(24)

1.缘起:     从IMultiTree到IAgileMultiTree,一切进展得都不错.但是,还有改进的地方.多叉树的一个优点在于,根据指定的节点能够非常迅速地找到其所有的子节点.但是缺点在于,根据节点值的ID定位到目标节点不够快,因为需要对所有的节点进行遍历操作.当节点非常多.层次非常深时,这种定位操作可能会严重的影响效率.     我设计了层级结构缓存ESBasic.ObjectManagement.Cache.IHiberarchyCache来加速这种根据节点值ID定位节点的访问.所

ESBasic 可复用的.NET类库(00) -- 开源前言(附下载)

自从03年正式使用.NET开发以来,已经走过了6个年头,这期间我积累了几套类库和框架,ESBasic便是其中最基础的一个类库.ESBasic是Enterprise Service Basic的缩写,虽然也简写为ESB,但是它和Enterprise Service Bus(企业服务总线)没有任何关系.ESBasic是我能够快速和高效开发应用程序的利器之一,开这个专门的blog是想将它介绍给大家,希望能对大家有所启发. ESBasic覆盖的内容包括:对象管理.插件.网络(Socket).多线程.Em

优先级管理器 IPriorityManager -- ESBasic 可复用的.NET类库(14)

1.缘起:     假设我们的订单处理系统所要处理的订单是有优先级的,也就是说,不同的订单类型所要求被处理的紧迫程度不同,对那些优先级高的注单要先处理,对于优先级低的注单可稍后处理.对于处于同一优先级的订单了,就按照其到达的先后顺序进行处理.     这是一个典型的管理具有优先级的对象的需求,注单就是具有优先级(With Priority)的对象.我设计了ESBasic.ObjectManagement.Managers.IPriorityManager优先级管理器(确切地说,应该称之为"具有优

Round缓存管理器RoundCacheManager--ESBasic 可复用的.NET类库(26)

1.缘起:     在增量自动获取器章节的缘起部分,我们曾提到增量缓存,本节我们将深入探讨它以及用于管理增量缓存的管理器.我们还是以增量自动获取器章节提到的例子作为基础,并做更进一步的讨论.       OK,现在让我们开始这有趣的旅程. 首先,基于前面例子给出的上下文,我们知道IIncreaseAutoRetriever获取的增量是用于累积当天的已成交订单报表的."当天已成交报表"就是一个典型的增量缓存,每当有新的增量到来,都会累加到上面. 我们假设今天是2009.07.08,那么我

循环任务切换器 CircleTaskSwitcher -- ESBasic 可复用的.NET类库(06)

 1.缘起:     假设我的订单处理系统有这样的需求:将一天24小时分为4个时段,凌晨2:15到8:30采用A类型的处理器处理接收到的订单,8:30到14:00采用B类型的处理器,14:00到20:00采用C类型的处理器,20:00到第二天凌晨2:15采用D类型的处理器.     即我们的订单处理器需要在任一天的2:15.8:30.14:00.20:00这四个时刻发生切换,这就是一个循环切换器所要做的工作.     我设计了ESBasic.Threading.Application. ICir

工作者引擎 IWorkerEngine -- ESBasic 可复用的.NET类库(05)

1.缘起:     假设我们的系统在运行的过程中,源源不断的有新的任务需要处理(比如订单处理),而且这些任务的处理是相互独立的,没有前后顺序依赖性(顺序依赖性是指,必须在任务A处理结束后才可开始B任务),那么我们就可以使用多个线程来同时处理多个任务.每个处理任务的线程称为"工作者(线程)".      我设计了ESBasic.Threading.Engines.IWorkerEngine工作者引擎,其目的就是使用多个线程来并行处理任务,提高系统的吞吐能力.       工作者引擎的形象

回调定时器ICallbackTimer --ESBasic 可复用的.NET类库(07)

 1.缘起:     举个例子也许就能够说清楚回调定时器的用途.假设我的订单系统接收各种不同类型的订单,当订单A进来时,系统根据订单的类型和其它特征进行综合判断后,决定A订单要在2秒之后被方法M1处理:接下来收到的B订单经过同样的判断后,决定要在10秒后被方法M2处理,--.这时候就可以用回调定时器来管理这些将要被延迟一定时间再执行的任务.     当然,我们可以使用定时器或前面介绍的循环引擎来实现这样的功能,只不过我们自己需要手动管理注册的定时回调任务,并且定时检查每一个未处理订单是否已经到了