2010-09-18 9 views
6

Estoy creando una biblioteca Java para un cliente, y una de las cosas que ellos quieren es una representación de datos de un conjunto particular de estándares con los que trabajan. No quiero revelar los intereses de mi cliente, pero si fuera un alquimista, él puede ser que desee el siguiente:¿Cómo construir una estructura de datos compleja, jerárquica e inmutable 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 

tengo que ser capaz de acceder a varios elementos de datos por su nombre, como por ejemplo:

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

que también tienen que ser capaces de recorrer atributos, como:

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

hecho, me han sido capaces de crear esta estructura de datos, y puedo hacer todo lo que he pedido anteriormente - aunque tuve que crear matrices separadas para la iteración, así que realmente lo que hago se parece más a:

for (MagicalType attrib : Elements.Fire.MagicalAuxArray) 

Lamentablemente no he podido cumplir mi último requisito: toda la estructura de datos debe ser inmutable. Lo he intentado repetidamente, y busqué en la web buscando ejemplos, pero hasta ahora no he podido lograr esto de manera razonable. Tenga en cuenta que la estructura de datos final será bastante grande; Realmente espero evitar una solución demasiado repetitiva o crear demasiados símbolos públicos.

Soy un programador muy experimentado, con menos experiencia con Java. ¿Alguien puede sugerirme cómo puedo representar los datos anteriores para cumplir todos mis requisitos?

+0

+1 Para el ejemplo de la alquimia. Bienvenido a SO! – NullUserException

Respuesta

6

Algunas maneras que vienen a la mente de inmediato:

  • No proporcione métodos setter para su objeto. Los usuarios solo pueden crear el objeto a través de un constructor y una vez creado, no se puede modificar. Esto también se aplica a otros métodos de modificación de estados. Si desea evitar una lista de parámetros muy grande en su constructor, puede usar el Builder pattern (descrito en Java efectivo por Joshua Bloch (2da Ed.))
  • Al devolver colecciones, haga copias defensivas. En este caso, use un List en lugar de una matriz. De esa manera puede hacer return new ArrayList<MagicalType>(MagicalAuxList) en lugar de return MagicalAuxList. De esta forma, las personas que usan la clase no podrán modificar la colección. Una advertencia aquí. Si su matriz contiene objetos complejos, también deben ser inmutables.
  • Para colecciones inmutables, también puede intentar usar el método estático unmodifiableCollection (hay métodos estáticos similares para listas, conjuntos, etc., use el que sea apropiado para usted) para convertir su colección cuando la devuelva. Esta es una alternativa a la copia defensiva.
+0

+1 para el patrón de generador –

+0

Gracias, estas fueron todas sugerencias muy útiles. Una vez que tuve algunos nombres de símbolos para buscar, comencé a descubrir muchas cosas. Hay un artículo aquí en Stack Overflow que está relacionado: "¿Cómo crear una colección profunda no modificable?" –

+0

@ vw-register sí, esa es una muy buena pregunta: debería darte muchas más buenas ideas. En general, si tiene una lista de tipos complejos, puede hacer una copia superficial de la lista si los tipos complejos son * inmutables *. ¡Buena suerte! :) –

1

¿Por qué usa matrices? ¿Las colecciones inmutables (por ejemplo, desde Google Guava) no harían un mejor trabajo?

+0

Usé matrices porque quería poder inicializarlas. Desde entonces, descubrí (como parte del seguimiento de las respuestas a esta pregunta, muchas gracias) que puedo inicializar una lista usando Arrays.asList() para lograr casi lo mismo. Como un glosario: estaba inicializando los elementos de la lista a los valores de los diversos atributos físicos y mágicos. Eso significaba tener que declarar los valores y luego tener que ingresar sus nombres una segunda vez como elementos de mi lista. Así que ahora estoy usando la reflexión y construyendo dinámicamente las listas en los constructores. –

0

Puede usar Iterable en su API pública. Más limpio que las colecciones con todos los mutadores que tienes que suprimir. (Por desgracia Iterator tiene un método remove() (?!), pero eso es sólo una)

public final Iterable<MagicalType> magics; 

for(MagicalType magic : magics) ... 
0

puede probar con el siguiente código que utiliza final, enumeraciones y mapas no modificables. pero eso no le permite acceder por su nombre ya que necesita obtener un obtener del mapa. probablemente puedas hacer eso en 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

Intenté algo así al principio de mi experimentación. No estaba mal, y me dio una buena búsqueda de cadenas ... pero no fue lo que especificó mi cliente. Creo que ahora lo tengo a mano, ¡gracias a todos por sus comentarios! –

Cuestiones relacionadas