2010-09-18 6 views
6

Je construis une bibliothèque Java pour un client, et l'une des choses qu'ils veulent est une représentation de données d'un ensemble particulier de normes avec lesquelles ils travaillent. Je ne veux pas révéler les intérêts de mon client, mais s'il était un alchimiste, il pourrait vouloir les éléments suivants:Comment construire une structure de données immuable complexe et hiérarchique en Java?

Elements 
    Fire 
    Name="Fire" 
    Physical 
     Temperature=451 
     Color="Orange" 
    Magical 
     Domain="Strength" 
    Water 
    Name="Water" 
    Physical 
     Color="Blue" 
    Earth 
    Name="Earth" 
    Magical 
     Domain="Stability" 
     Ordinality=1 

Je dois être en mesure d'accéder à divers éléments de données par nom, tels que:

Elements.Earth.Name 
    Elements.Water.Physical.Color 

Je dois aussi pouvoir itérer attributs, comme:

for (MagicalType attrib : Elements.Fire.Magical) 
    { 
    ... 
    } 

J'ai effectivement été en mesure de créer cette structure de données, et je peux faire tout ce que j'ai demandé ci-dessus - si je devait créer des tableaux séparés pour l'itération, donc vraiment ce que je fais ressemble plus:

for (MagicalType attrib : Elements.Fire.MagicalAuxArray) 

Malheureusement, je ne l'ai pas été en mesure de répondre à ma dernière exigence: l'ensemble de la structure de données doit être immuable. J'ai essayé à plusieurs reprises, et parcouru le web à la recherche d'exemples, mais jusqu'à présent, je n'ai pas été en mesure d'accomplir cela de manière raisonnable. Notez que la structure de données finale sera assez grande; J'espère vraiment éviter une solution trop répétitive ou créer trop de symboles publics. Je suis un programmeur très expérimenté, moins expérimenté avec Java. Quelqu'un peut-il suggérer comment je pourrais représenter les données ci-dessus pour répondre à toutes mes exigences?

+0

+1 Pour l'exemple d'alchimie. Bienvenue à SO! – NullUserException

Répondre

6

Quelques façons qui me viennent à l'esprit immédiatement:

  • Ne fournit pas des méthodes de réglage de votre objet. Les utilisateurs ne peuvent créer l'objet que via un constructeur et une fois créé, il ne peut pas être modifié. Cela vaut également pour d'autres méthodes de modification d'état. Si vous voulez éviter une très grande liste de paramètres dans votre constructeur, vous pouvez utiliser le Builder pattern (décrit dans Java efficace par Joshua Bloch (2ème édition))
  • Lorsque vous renvoyez des collections, effectuez des copies défensives. Dans ce cas, utilisez un List au lieu d'un tableau. De cette façon, vous pouvez faire return new ArrayList<MagicalType>(MagicalAuxList) au lieu de return MagicalAuxList. De cette façon, les personnes qui utilisent la classe ne seront pas en mesure de modifier la collection. Une mise en garde ici. Si votre tableau contient des objets complexes, ils doivent également être immuables.
  • Pour les collections immuables, vous pouvez également essayer d'utiliser la méthode statique unmodifiableCollection (il existe des méthodes statiques similaires pour les listes, les ensembles, etc. - utilisez celle qui vous convient) pour convertir votre collection lorsque vous la renvoyez. C'est une alternative à la copie défensive.
+0

+1 pour le modèle de constructeur –

+0

Merci, ce sont toutes des suggestions très utiles. Une fois que j'ai eu quelques noms de symboles à rechercher, j'ai commencé à trouver beaucoup de choses. Il y a un article ici à Stack Overflow qui est lié: "Comment créer une collection profonde non modifiable?" –

+0

@ vw-register oui, c'est une très bonne question - elle devrait vous donner beaucoup plus de bonnes idées. En général, si vous avez une liste de types complexes, vous pouvez obtenir une copie superficielle de la liste si les types complexes sont * immuables *. Bonne chance! :) –

1

Pourquoi utilisez-vous des tableaux? Les collections immuables (p. Ex. De Google Guava) ne feraient-elles pas un meilleur travail?

+0

J'ai utilisé des tableaux car je voulais pouvoir les initialiser. J'ai depuis découvert (dans le cadre du suivi des réponses à cette question - merci beaucoup) que je peux initialiser une liste en utilisant Arrays.asList() pour accomplir à peu près la même chose. En tant que gloss - j'initialisais les éléments de la liste aux valeurs des différents attributs physiques et magiques. Cela signifiait d'avoir à déclarer les valeurs, puis d'entrer leurs noms une deuxième fois en tant qu'éléments de ma liste. Alors maintenant, j'utilise la réflexion, et la construction dynamique des listes dans les constructeurs. –

0

Vous pouvez utiliser Iterable dans votre API publique. Plus propre que les collections avec tous les mutateurs que vous devez supprimer. (Malheureusement Iterator a une méthode remove() (?!) mais c'est juste un)

public final Iterable<MagicalType> magics; 

for(MagicalType magic : magics) ... 
0

vous pouvez essayer le code ci-dessous qui utilise finale, énumérations et cartes non modifiables. mais cela ne vous laisse pas accéder à votre nom puisque vous devez faire un tour depuis la carte. vous pourriez probablement le faire dans groovy.

import java.util.*; 

enum Color { 
    red, green, blue; 
} 

class Physical { 
    Physical(final Double temperature, final Color color) { 
     this.temperature = temperature; 
     this.color = color; 
     final Map<String, Object> map=new LinkedHashMap<String, Object>(); 
     map.put("temperature", temperature); 
     map.put("color", color); 
     this.map=Collections.unmodifiableMap(map); 
    } 

    final Double temperature; 
    final Color color; 
    final Map<String, Object> map; 
} 

class Magical { 
    Magical(final String domain, final Integer ordinality) { 
     this.domain = domain; 
     this.ordinality = ordinality; 
     final Map<String, Object> map=new LinkedHashMap<String, Object>(); 
     map.put("domain", domain); 
     map.put("ordinality", ordinality); 
     this.map=Collections.unmodifiableMap(map); 
    } 

    final String domain; 
    final Integer ordinality; 
    final Map<String, Object> map; 
} 

public enum Elements { 
    earth("Earth", new Magical("Stability", 1), null), air("Air", null, null), fire("Fire", new Magical("Strength", null), new Physical(451., Color.red)), water(
      "Water", null, new Physical(null, Color.blue)); 
    Elements(final String name, final Magical magical, final Physical physical) { 
     this.name = name; 
     this.magical = magical; 
     this.physical = physical; 
    } 

    public static void main(String[] arguments) { 
     System.out.println(Elements.earth.name); 
     System.out.println(Elements.water.physical.color); 
     for (Map.Entry<String, Object> entry : Elements.water.physical.map.entrySet()) 
      System.out.println(entry.getKey() + '=' + entry.getValue()); 
     for (Map.Entry<String, Object> entry : Elements.earth.magical.map.entrySet()) 
      System.out.println(entry.getKey() + '=' + entry.getValue()); 
    } 

    final String name; 
    final Magical magical; 
    final Physical physical; 
} 
+0

J'ai essayé quelque chose comme ça au début de mon expérimentation. Ce n'était pas mal, et ça donnait une belle recherche de cordes ... mais ce n'était pas ce que mon client avait spécifié. Je pense que j'ai la main maintenant, cependant ... merci à tous pour vos commentaires! –

Questions connexes