重構一個繁瑣的資料結構

來源:互聯網
上載者:User

    在GIX4項目的開發過程中,遇到一個比較複雜的資料結構。複雜,是因為它有許多限制條件。我的工作是在現有系統中,添加新的功能,並在過程中重構部分舊代碼。

約束及需求

    以下約束是系統中已經存在的必要的約束,不可繞開這些約束而進行代碼的開發。

    1.項目中,有許多的實體類,都含有一種多叉樹的關係和邏輯。

    2.這些實體的樹型關係,在運行時,只有鍵的關係,而沒有對應的實體參考關聯性。
    由於GIX4是資料分析軟體,資料量比較大。建立關係需要的時間比較久,所以伺服器端只負責給資料。這樣用戶端得到的資料,只是一個簡單的對象集合。

    3.實體集合所有的更改對象位置只能使用一個特定的操作來實現排序:void Move(Object Item, Int32 index)。
    這個約束產生的主要是原因是:一:使用了CSLA作為實現分布式應用的架構,所有實體集合,都需要繼承BusinessListBase。而對這個集合中的實體進行操作,經常會引起該實體的狀態的改變;二:目前的OpenExpressApp架構中,要求實體直接綁定到展示層,而不能對它進行轉換,如使用“ViewModel”。當然,大量的資料處理,也要求了最好不要每次都對這些資料進行轉換。

    4.業務上要求,實體是可以進行排序操作的。(以下稱為“邏輯序號”和“邏輯排序”。)這些序號將會持久化到資料庫中。

    5.集合中的對象,應該按照“邏輯序號”進行排序。(以下稱為“物理序號”和“物理排序”)

    6.樹的每一個結點的孩子結點集合,也應該是按照“邏輯序號”進行物理排序。

    7.以上的操作,全部在OpenExpressApp架構中實現,而非應用程式層。

原有代碼

    一、樹的結構的定義,已經在老系統中定義並被廣泛使用。屬於固化因素,不可修改。定義如下:

/// <summary>/// 樹形節點/// </summary>public interface ITreeNode{    /// <summary>    /// 所有的孩子節點    /// </summary>    IList<ITreeNode> ChildrenNodes { get; }    /// <summary>    /// 這個節點對應的父級節點    /// </summary>    ITreeNode ParentNode { get; set; }    /// <summary>    /// 當前節點的鍵    /// </summary>    object Id { get; }    /// <summary>    /// 父節點的鍵    /// </summary>    object Pid { get; }}

    這個介面表示的資料結構是樹的結點,但是它受到上文中第2點約束的限制:當得到一個這個介面的執行個體時,它的Pid有值並指示出父節點的ID值,但是同時ParentNode卻可能因為沒有引用到實體,而為null。同樣的,雖然這個節點有許多孩子節點,但是ChildrenNodes得到的集合卻有可能是空集合。

    二、實體集合對象繼承自GBusinessListBase<T,C>泛型集合,對它的元素的操作只有一個:Move(C item,int index)。

 

介面定義

    在原有代碼的基礎上,我先增加了以下幾個介面,目的在於先把複雜的概念清晰化:

ISimpleMovableCollection.cs

/// <summary>/// 一個有簡單Move操作的集合/// </summary>internal interface ISimpleMovableCollection : ICollection{    /// <summary>    /// 把oldIndex上對應位置的元素移動到newIndex上去。    /// 目的位置上的和中間的所有元素,都向oldIndex的方向移動一個位置。    /// </summary>    /// <param name="oldIndex"></param>    /// <param name="newIndex"></param>    void Move(int oldIndex, int newIndex);    /// <summary>    /// 把item移動到newIndex上去。    /// 目的位置上的和中間的所有元素,都向oldIndex的方向移動一個位置。    /// </summary>    /// <param name="item">本集合中的某一個元素</param>    /// <param name="newIndex"></param>    void Move(object item, int newIndex);}internal interface ISimpleMovableCollection<T> : ISimpleMovableCollection{    /// <summary>    /// 把item移動到newIndex上去。    /// 目的位置上的和中間的所有元素,都向oldIndex的方向移動一個位置。    /// </summary>    /// <param name="item"></param>    /// <param name="newIndex"></param>    void Move(T item, int newIndex);}

    介面中的Move方法的定義,主要是來自原系統中的方法:GBusinessListBase<T,C>.Move()。 ISimpleMovableCollection 這個集合不同於下面列出的其它集合,它並不繼承自IList。這是因為它除了Move以外,沒有任何別的排序操作。而如果繼承自IList,則同時會擁有Insert,Remove等方法。這裡需要注意的是,雖然IList介面有IsReadOnly屬性來判斷是否是一個唯讀集合,但是如果這個值為false,而Move操作卻可以執行的話,邏輯上是不對的。所以該集合直接繼承自ICollection。

    泛型的ISimpleMovableCollection同樣也沒有實現ICollection<T>,原因也是因為泛型的ICollection<T>也有更改集合的操作。

    另外,我在這裡定義的這些集合,都是一個泛型和一個非泛型配合。這是因為代碼的實現是在OpenExpressApp架構中,而在架構中實體類的操作有時候是針對泛型實體,有時候卻針對非泛型實體。所以這裡只好也把非泛型版本也一起定義了。

 

IOrderedObject.cs
/// <summary>/// 有邏輯排序號的對象/// </summary>public interface IOrderedObject{    /// <summary>    /// gs 邏輯排序號     /// LogicIndex    /// </summary>    int OrderNo { get; set; }    //event EventHandler OrderNoChanged;}

    IOrderedObject 表示一個可以被邏輯排序的實體,可以直接設定它的邏輯排序號。這個序號也是最後會被持久化到資料庫的值。這樣在下次從資料庫中取出時,可簡單的根據邏輯號進行物理排序,減少了時間消耗。

 
 
IOrderedObjectCollection.cs
/// <summary>/// 按排序號OrderNo進行邏輯排序的對象集合/// </summary>/// <typeparam name="T"></typeparam>public interface IOrderedObjectCollection : IList{    /// <summary>    /// g 下一個新的對象,可以使用這個排序號    /// </summary>    int NextOrderNo { get; }    /// <summary>    /// 把指定的節點上/下移    /// </summary>    /// <param name="node"></param>    /// <param name="isUp"></param>    void MoveNode(IOrderedObject node, bool isUp);    /// <summary>    /// 按照OrderNo進行排序    /// </summary>    void SortByOrderNo();    /// <summary>    /// 按照物理位置把所有的OrderNo設定好。    /// </summary>    void SetOrderNoByIndex();}public interface IOrderedObjectCollection<T> : IOrderedObjectCollection, IList<T>    where T : IOrderedObject{ }

    IOrderedObjectCollection<T>表示一IOrderedObject的集合。它不但負責維護邏輯序號,也負責維護物理位置。需要注意的是,這個集合的定義,與樹的操作是沒有任何關係的。

 
 
ITreeNodeCollection.cs

/// <summary>/// 一般的遍曆類型/// </summary>public enum TreeNodeTravelType{    /// <summary>    /// 深度優先    /// </summary>    DepthFirst,    /// <summary>    /// 廣度優先    /// </summary>    ScopeFirst}

/// <summary>/// TreeNode的集合/// /// 裡面的元素是貧血的樹節點(ITreeNode:雖然有pid,卻不一定有ParentNode對象。也就是說不一定有實體參考關聯性……)/// </summary>public interface ITreeNodeCollection : IList{    /// <summary>    /// 是深度排序還是廣度排序    /// </summary>    TreeNodeTravelType SortType { get; set; }    /// <summary>    /// 是否集合中的TreeNode已經按照關係進行排序    /// </summary>    bool Sorted { get; }    /// <summary>    /// 是否集合中的Node都已經按照Pid關聯了ParenNode和ChildrenNodes    /// </summary>    bool ObjectRelationsRuilt { get; }    /// <summary>    /// 重建立立ParenNode和ChildrenNodes關聯    /// </summary>    void RebuildObjectRelations();    /// <summary>    /// 保證建立了ParenNode和ChildrenNodes關聯    /// </summary>    void EnsureObjectRelations();    /// <summary>    /// 按照SortType進行排序    /// </summary>    void SortByTree();    /// <summary>    /// 保證已經排序    /// </summary>    void EnsureSorted();    /// <summary>    /// 把指定的節點升/降級    /// </summary>    /// <param name="node"></param>    /// <param name="isUp"></param>    void ChangeNodeLevel(ITreeNode node, bool isUp);    /// <summary>    /// 以指定的節點為位置標準,添加一個新的節點。    /// </summary>    /// <param name="targetNode">作為位置標準的節點</param>    /// <param name="insertAsChild">新的節點是否是targetNode的孩子節點。(如果不是,就算兄弟/同級節點。)</param>    /// <param name="createAsChild">    /// 如果是添加兄弟節點,true表示放在targetNode的前面,false則表示放在後面。    /// 如果是添加孩子節點,true表示新的節點作為第一個孩子,false表示作為第後一個績點    /// </param>    /// <returns></returns>    ITreeNode CreateNode(ITreeNode targetNode, bool createAsChild, bool beforeFirst);    /// <summary>    /// 把指定的節點和它所有的子節點都加入集合中。    /// </summary>    /// <param name="node"></param>    void AddNode(ITreeNode node, bool recursivlyAddChildren);    /// <summary>    /// 在列表中尋找前一個兄弟節點    /// </summary>    /// <param name="node"></param>    /// <returns></returns>    ITreeNode FindPrevSibling(ITreeNode node);    /// <summary>    /// 在列表中尋找後一個兄弟節點    /// </summary>    /// <param name="node"></param>    /// <returns></returns>    ITreeNode FindNextSibling(ITreeNode node);    /// <summary>    /// 尋找所有根節點(沒有父親的節點)    /// </summary>    /// <returns></returns>    ITreeNode[] FindRoots();
}public interface ITreeNodeCollection<T> : ITreeNodeCollection, IList<T>    where T : ITreeNode{}

    ITreeNodeCollection<T>集合是ITreeNode的集合。類似IOrderedObjectCollection<T>,這裡也與IOrderedObject沒任何關係。

    這個集合中的每一個ITreeNode,都可以在這個集合中找到它的所有的關聯節點。在集合裡面尋找,是因為ITreeNode.ParentNode和ITreeNode.ChildrenNodes並不一定會有實體引用。 看到方法FindRoots,大家應該想到,這個集合中並不一定只包含一棵樹。

 
 
IOrderedTreeNodeCollection.cs
/// <summary>/// 一個元素集合/// /// 整合兩套不同的模式:ITreeNodeCollection和IOrderedObjectCollection/// </summary>public interface IOrderedTreeNodeCollection :    ITreeNodeCollection,    IOrderedObjectCollection,    IList{    /// <summary>    /// 按照樹的順序重新給OrderNo賦值    /// 這裡對每個根節點使用深度遍曆設定OrderNo。    /// </summary>    void SetOrderNoByTree();    /// <summary>    /// 保證 邏輯Index、物理Index 相等。    /// 並且和樹的遞迴順序是一樣的    /// </summary>    void EnsureIndices();}public interface IOrderedTreeNodeCollection<T> :    IOrderedTreeNodeCollection,    ITreeNodeCollection<T>,    IOrderedObjectCollection<T>,    IList<T>    where T : IOrderedObject, ITreeNode{}

    IOrderedTreeNodeCollection<T>才是系統中要應用到的集合。集合中的元素一定是同時實現了IOrderedObject和ITreeNode介面。所以我們可以根據樹來設計邏輯序號:SetOrderNoByTree。也有了操作:EnsureIndices。

部分實現代碼

    首先,有樹的遍曆操作,自然先實現這個。這裡使用兩個靜態方法對已經建立關係的樹進行遍曆,一個深度遍曆使用棧,一個廣度遍曆使用隊列。代碼就不貼了,太占空間。

    下面這個GBusinessTreeListBase<T,T>類,繼承自原來的GBusinessListBase<T, C>類。考慮到繼承層次過深對效能的影響,所以在這個類中直接全部實現了前面的所有介面。但是卻是邏輯分離的。先看代碼:

    注意到,前面的介面,繼承層次是這樣的:

    但是GBusinessTreeListBase<T,T>類在實現全部介面時,邏輯上,可以簡單理解為以下形式:

    其中,箭頭方向,即是邏輯中的繼承方向,也是實現中的依賴方向。在GBusinessTreeListBase<T,T>中每一個介面實現的Region中,都可以看成是一個簡單的類。只不過是把它們整合到了一個類裡。如下:

ISimpleMovableCollection<C> 實現:

IOrderedObjectCollection<C> 實現:

ITreeNodeCollection<C> 實現:

IOrderedTreeNodeCollection<C> 實現:

 

後話

    細心的讀者可能會發現:IOrderedObjectCollection<T>實現中的的MoveNode方法調用了IOrderedTreeNodeCollection<T>實現中的MoveNode方法;本應該出來在ITreeNodeCollection<T>實現中CreateNode方法,卻被放在了IOrderedObjectCollection<T>實現。

    誰能說出這是為什麼嗎?這還算是單向依賴的嗎?:)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.