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異なる可読な名前を持つことができ、構造全体に複数回配置できますが、現在のカテゴリ/サブカテゴリには 1 回だけ配置できます。

現在、ユーザーがリンクをクリックするURL を書き込むと、リソースがロードされ (例: リンクFooがマップされ/showResource?id=resource_id_1:uniqe_magic_idます)、ツリーが展開されます。ハックのためにのみ機能します-フロントエンドは構造の独自のコピーを作成し、:uniqe_magic_id各リソースID(各リーフ)に文字列を追加し、リクエストをバックエンドに送信するときにマジック部分をストライプします。:uniqe_magic_id上記のツリーで適切なアイテムを展開するためにフロントエンドでのみ使用されます。それは私には巧妙な解決策のようです(私はこのコードをリファクタリングし、cleanId必要ではないと思ったメソッドを削除しましたが、バックエンドにリクエストを送信する前に魔法をストライピングしていました...)、より良い解決策を探しています。

フロントエンドとバックエンドの両方を変更できます。次のようなノードを持つある種のツリーについて考えました。

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

と使用しsomeUniqueHashます。

フロントエンドで構造全体をコピーせずに同じ結果を達成するより良い方法はありますか?

4

3 に答える 3

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
}

IDの一意性を確保する1つの方法は、クラスを使用するUUIDことです。これは、一意の識別子を提供する非常に便利randomUUID()なメソッドを提供します。

于 2012-12-13T15:47:46.967 に答える
3

Item の ID が作成中のメニューに対してローカルで一意であることをアサートし、子が各項目に追加されたときに親への参照を更新するのはどうですか?

これにより、URL でフォーカスされているもののサブツリーを展開できます。フロントエンド (html を想定) は、さまざまなカテゴリをユーザーに提示するメニューを動的にナビゲートします。

Menuという名前の新しいクラスを追加し、Menuのを変更可能にすることで、ファクトリ パターンを通じて項目のローカルな一意性をアサートできます。

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;
    }
}

このパターンは、ネストされたリストのセットを提供するよりも、エラーを処理するときにはるかに表現力があると信じているためです。

不変メニューの生成

不変性が重要な場合は、いつでも Menu と Item をインターフェイスに変更し、元の Menu と Item をコピーする不変のバリアントを実装してから、要求された構造を構築する Menu クラスにcopyImmutableメソッドを追加できます。

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

これは、すべてのアイテムに対して同じことを再帰的に行うことを意味します。

メニュー生成アルゴリズム

  1. Menuクラスから項目をルックアップします(潜在的なエラーを処理します)
  2. そのメニューへのすべての親を反復し、pathTakenを記録します。
  3. ルート ノードに到達したら、それをactiveRootとして保存します。
  4. Menuから順番にすべてのルート ノードを繰り返してレンダリングします。activeRootをヒットすると、すべての子を再帰的にレンダリングしますが、 pathTakenに登録されているもののみを入力します。

これが、問題を解決するためのインスピレーションを与える解決策を説明していることを願っています!

于 2012-12-06T20:06:48.483 に答える
1

私の理解が正しければ、一意でない ID のみを指定して、ツリー構造内のアイテムの位置を確実に特定しようとしています。

生成された URL は「パーマリンク」ですか?つまり、ユーザーはそれらを安全にブックマークして、ツリー構造が変更された n 年後に戻ってくることができますか? ツリー構造は変更されますか? 特に、リソースを別のカテゴリに移動したときに、その古いリンクがユーザーを新しい場所に移動させることを期待したことがありますか?

後者の場合、リソース ID ごとに一意の識別子を生成または指定する以外にほとんど方法はありません。UUID を生成/使用するか、ユーザーに ID を指定させ、コードで一意性を強制することができます。

それ以外の場合は、ツリー内のアイテムの位置を使用して一意の ID を与えることができます (ツリー構造が変更されないか、リソースが劇的に移動した場合に将来機能するリソースへの保存済みリンクにユーザーが依存できないため)ツリー内)。

後者の例として、ツリーが与えられた場合:

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

そのため、各アイテムには、ツリー内の位置と一意でないリソース ID に基づいて、サーバー側で複合リソース ID を割り当てることができます。たとえば、「A/Resource1」と「B/Z/Resource1」です。

または、表示名に依存したり、ID を割り当てたりしたくない場合は、親内の各カテゴリの序数を使用します。たとえば、「1/Resource1」(最初のカテゴリ、次に Resource1) は「2/ 3/Resource1" (2 番目のカテゴリ、次にその 3 番目の子、次に Resource1)。

これはまさにバニラ ファイルシステムが行うことです: アイテムへの一意のパスを指定して、一意の名前を持たないリソース (ファイル/フォルダー) を識別します。

(サーバー側でこの割り当てを実行できるため、Parent フィールドを Item に追加し、単純な getUniqueResourceId() メソッドを追加します。このメソッドは、Parent リンクを介してツリーを繰り返し、一意のパス + getResourceId() を構成し、それをクライアントに渡します。 getResourceId() ではなく - HTML フロントエンドに影響を与える必要はありません。)

于 2012-12-13T13:20:23.453 に答える