2012-11-29 50 views
4

我有可展開樹(在HTML頁):尋找更好結構爲樹狀數據

+ Category 1 
- Category 2 
    + Subcategory 1 
    - Subcategory 2 
    |- Foo 
    |- Bar 
    |- Link 42 

其由結構表示(在後端定義):

class Demo { 
    static ImmutableList<Item> sample() { 
    return ImmutableList.of(
     new Item("Category 1", ImmutableList.of(
      new Item("Some link title", "resource_id_1"), 
      new Item("Another link title", "resource_id_2"))), 
     new Item("Category 2", ImmutableList.of(
      new Item("Subategory 1", ImmutableList.of(
       new Item("Another link title", "resource_id_3"))), 
      new Item("Subcategory 2", ImmutableList.of(
       new Item("Foo", "resource_id_1"), 
       new Item("Bar", "resource_id_2"), 
       new Item("Link 42", "resource_id_42")))))); 
    } 
} 

Item定義如下:

public class Item { 
    private String readableName; 
    private String resourceId; 
    private ImmutableList<Item> children; 

    Item(String name, String resourceId) { 
    this.readableName = name; 
    this.resourceId = resourceId; 
    } 

    Item(String name, ImmutableList<Item> children) { 
    this.readableName = name; 
    this.children = children; 
    } 

    public String getReadableName() { 
    return readableName; 
    } 

    public String getResourceId() { 
    return resourceId; 
    } 

    public ImmutableList<Item> getChildren() { 
    return children; 
    } 
} 

resourceId可以有不同的可讀的名稱,並且可以是PLA在整個結構中不止一次,但在當前類別/子類別中只有一次。

目前當用戶點擊一個鏈接或資源被加載(例如,鏈接映射到/showResource?id=resource_id_1:uniqe_magic_id)和樹木展開URL。它僅僅是因爲一個黑客 - 前端創建了自己的結構副本,並在每個資源ID(每個葉)上附加了一些:uniqe_magic_id字符串,並且在發送請求到後端時,它會劃分魔術部分。 :uniqe_magic_id僅被前端用於擴展上面顯示的樹中的正確項目。對我來說,這似乎是一個很好的解決方案(我重構了這段代碼,並刪除了cleanId方法,我認爲這不是必須的,但是在發送請求到後端之前就已經剝離了魔法......),我正在尋找更好的方法。

我可以修改前端和後端。我想到了一些類似節點的樹:

class Node { 
    Node next; 
    Node child; 
    String readableName; 
    String resourceId; 
    String someUniqueHash; 
} 

和使用someUniqueHash

有沒有更好的方法來實現相同的結果,而不需要在前端複製整個結構?

+0

我不認爲這會解決你的問題,但由於你需要在任何級別強制執行任何重複值,你有沒有想過使用LinkedHashSet(或不可變的)而不是List? –

+0

'List'最初在這裏用來表示它以某種方式被排序,'Item's到目前爲止沒有hashCode/equals,但它是一個小的(而且有用的)建議,它並不能真正解決我的問題。 – Xaerxess

+1

如何僅維護後端結構?在前端顯示樹並始終要求在服務器的點擊節點處開始一個子樹?另外,使用weakhashmap可能會有用。 –

回答

3

只要斷言Item的id對於您正在創建的菜單是本地唯一的,並且將子項追加到每個項目時更新對父項的引用?

這將允許您展開任何焦點在url中的子樹,前端(假設爲html)將動態地導航向用戶呈現各種類別的菜單。

本地項目的獨特性可以通過工廠模式通過添加一個名爲菜單新的類,使兒童在菜單可變斷言。

class Menu { 
    final HashMap<String, Item> items = new HashMap<String, Item>(); 
    final List<Item> root = new ArrayList<Item>(); 

    public Item createItem(String title, String id, Item parent) { 
     if (items.containsKey(id)) { 
      raise SomeRuntimeException(); 
     } 

     final Item item = new Item(title, id, parent, this); 

     if (parent == null) { 
      root.add(item); 
     } 
     else { 
      parent.addChild(item); 
     } 

     items.put(id, item); 
    } 

    /* default to have no parent, these are root items. */ 
    public Item createItem(String title, String id, Item parent) { 
     return addItem(title, id, null); 
    } 
} 

對Item類的一些修改。

class Item { 
    private final Menu menu; 
    private final Item parent; 
    private final List<Item> children = new ArrayList<Item>(); 

    public Item(String name, String resourceId, Menu menu, Item parent) { 
     ... 
     this.menu = menu; 
     this.parent = parent; 
    } 

    public Item addChild(String name, String resourceId) { 
     final Item item = this.menu.createItem(name, resourceId, this); 
     this.children.add(item); 
     return item; 
    } 
} 

現在我犧牲了一些不變性,因爲我相信,處理不是提供一組嵌套表的錯誤時,這種模式是更富於表現力。

生成一個不變的菜單

如果不變性是大不了你可以隨時更改菜單和項目進入接口和實現不可變變體複製原始菜單和項目,然後添加一個copyImmutable方法到將構建請求的結構的Menu類。

class MenuBuilder { 
    /* ... contains all things previously declared in Menu ... */ 
    Menu copyImmutable() { 
     ImmutableList<Item> root = ... 
     ImmutableMap<String, Item> items = ... 
     return new ImmutableMenu(root, items) 
    } 
} 

這意味着您遞歸地對所有項目執行相同操作。

算法生成菜單

  1. 查找從菜單類(處理潛在的錯誤)
  2. 迭代每一個家長的項目是菜單和記錄pathTaken
  3. 當到達根節點時,將其存儲爲activeRoot
  4. 菜單開始遍歷所有根節點並渲染它們。當打activeRoot,遞歸渲染所有的孩子,但只有進入註冊pathTaken的人。

我希望這裏介紹一個解決方案,可能會給你如何解決你的問題的靈感!

1

如果我理解正確,那麼您就試圖在只給出非唯一ID的樹結構中可靠地標識項目的位置。

生成的URL是否爲「永久鏈接」,即用戶可以安全地將它們加入書籤,並在樹結構可能發生更改後的n年內回來?樹結構會不會改變?特別是,您是否曾經將資源移動到不同的類別,但希望其舊的鏈接能夠將用戶帶到新的位置?

如果後者爲,那麼除了爲每個資源ID生成或指定一個唯一標識符外,別無選擇。您可以生成/使用UUID,或讓用戶指定一個ID並使代碼執行唯一性。

否則,您可以使用樹中項目的位置爲您提供唯一的ID(因爲樹結構不會更改,或者用戶無法依賴保存的工作鏈接未來如果資源在樹內急劇移動)。

例如後者的,給定樹:

- A 
    |- Resource1 
- B 
    + X 
    + Y 
    - Z 
    |- Resource1 
    |- Resource24 

所以每個項目可以被分配根據它在樹中的位置和它的非唯一資源標識符的化合物資源ID服務器端。例如,「A/Resource1」和「B/Z/Resource1」。

或者,如果您不想依賴顯示名稱或整個指定ID,請使用其父級中每個類別的序號位置,例如「1/Resource1」(第1個類別,然後是Resource1)可以區分從「2/3/Resource1」(第二類,然後是其第三個孩子,然後是Resource1)。

這正是香草文件系統所做的:識別沒有唯一名稱的資源(文件/文件夾),給定項目的唯一路徑。 (因爲服務器端可以完成這個任務 - 爲Item添加一個Parent字段,然後添加一個簡單的getUniqueResourceId()方法,它通過父鏈接遍歷樹來構成一個唯一路徑+ getResourceId(),並傳遞而不是getResourceId() - 不需要影響HTML前端)

3

在前端製作樹的副本是必要的,因爲它代表了不同的概念:雖然後面的樹結束代表模型,前端樹代表模型的視圖。這是一個非常重要的區別:後端樹顯示顯示內容,而前端樹顯示如何顯示它。具體而言,前端知道樹的哪些部分被展開:後端不應該知道它,因爲樹節點的打開/關閉狀態對於不同的用戶將是不同的。

也許你應該做的是明確責任分離。而不是複製樹併爲其資源ID添加唯一標識符,爲VisualItem創建一個單獨的類,封裝實際項目的ID並擁有自己的唯一ID(唯一ID永遠不會回到後面年底):

class VisualItem { 
    String resourceId; 
    ImmutableList<VisualItem> children; 
    String uniqueId; // Used only in the front end 
    boolean isExpanded; // Tells you if the subtree is expanded or not 
    // You can add more attributes here to avoid requesting the Item. 
    // For example, you could add a readableName here 
} 

的一種方式,以保證編號的唯一性是use the UUID class:它提供了一個非常方便的方法randomUUID(),爲您提供一個唯一的標識符。

+0

基本上我得出的結論就是你在這裏寫的 - 我不能在沒有在前端創建類似於我的結構的副本的情況下使用(優雅的)解決方案。在這裏添加一些唯一的id比較容易(在這個選項中其他人提出的前端結構hashCode和equals等功能),並且支持後面將只有一個只包含普通數據的簡單模型 - 不多不多。我稍後會發布最終解決方案。 – Xaerxess