2011-11-02 8 views
12

Tome estas dos clases de Java:¿Alguna buena manera de hacer que dos objetos inmutables se refieran entre sí?

class User { 
    final Inventory inventory; 
    User (Inventory inv) { 
     inventory = inv; 
    } 
} 

class Inventory { 
    final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 

¿Hay alguna manera without using reflection* de sacar esto adelante? Realmente no espero que lo sea, pero no está de más preguntarlo.

Actualización: Dado que en la construcción de código de bytes tiene dos pasos (1. Asignar objeto, 2. llamada al constructor **) puede ser esto (ab) que se utilizan para ello, con el código de bytes a mano o un compilador de encargo? Estoy hablando de realizar primero el paso 1 para ambos objetos, luego el paso 2 para ambos, usando las referencias del paso 1. Por supuesto, algo así sería bastante engorroso, y esta parte de la pregunta es académica.

(* Debido a la reflexión puede dar problemas con un controlador de seguridad)

(** indica que mi conocimiento limitado)

+1

las dependencias circulares no son a menudo una buena idea – Bozho

+0

Lo siento, ¿podría explicar un poco sobre el problema. No veo cuál es el problema, ya que ya tiene una referencia al otro objeto? – Anders

+0

@Antes, el problema es que ambos requieren una referencia a la otra en el momento de la construcción, y dicha referencia solo está disponible después de la construcción. –

Respuesta

13

Esto sólo puede funcionar limpiamente si uno de los objetos es creado por el otro. Por ejemplo, usted puede cambiar su clase User a algo como esto (mientras se mantiene la clase Inventory sin cambios):

class User { 
    private final Inventory inventory; 
    User() { 
     inventory = new Inventory(this); 
    } 
} 

Usted necesita tener cuidado sobre cómo acceder al objeto User en el Inventory constructor, sin embargo: no se ha inicializado completamente todavía . Por ejemplo, su campo inventory seguirá siendo null!

actualización de anuncio: ahora he verificado que el enfoque de código de bytes-manipulación no no funciona. Lo intenté usando Jasmin y siempre falló al cargar con un VerifyError.

Profundizando en el tema, encontré § 4.10.2.4 Instance Initialization Methods and Newly Created Objects. Esta sección explica cómo la JVM garantiza que solo se pasen las instancias de objetos inicializados.

+0

¿Alguna idea sobre los trucos bytecode (ver pregunta actualizada)? Lo que describo allí probablemente sea imposible. –

+0

@BartvanHeukelom: teóricamente podría funcionar. No sé si a la JVM realmente le gusta un 'nuevo' sin el' invokespecial' correcto para invocar al constructor. Déjame intentarlo ... –

+0

Hubo cambios en 1.4 (después de JVSpec 2nd Ed.) Que permitieron cierto acceso para inicializar campos finales fuera de la clase. No puedo recordar los cambios exactos. –

7

Puede hacerlo si no necesita inyectar uno de los objetos.

class User { 
    private final Inventory inventory; 
    User() { 
     inventory = new Inventory(this); 
    } 
} 
3
class User { 
    private final Inventory inventory; 
    User (/*whatever additional args are needed to construct the inventory*/) { 
     //populate user fields 
     inventory = new Inventory(this); 
    } 
} 

class Inventory { 
    private final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 

Eso es lo mejor que puedo imaginar. Tal vez hay un mejor patrón.

0

Esta es una "solución", aunque la pérdida de uno final es inconveniente.

class User { 
    Inventory inventory; 
    User() { } 
    // make sure this setter is only callable from where it should be, 
    // and is called only once at construction time 
    setInventory(inv) { 
     if (inventory != null) throw new IllegalStateException(); 
     inventory = inv; 
    } 
} 

class Inventory { 
    final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 
+1

Derecha. Ya no tiene dos objetos inmutables, que el título de su pregunta ordena como premisa;) – aioobe

+0

@aioobe De hecho, lo edité y puse "solución" entre comillas. Sin embargo, todavía es inmutable para el mundo exterior, porque no existe un setter que puedan usar. –

+0

Correcto, a menos que considere que otras clases en el mismo paquete son el mundo exterior: P – aioobe

0

Si sólo está interesado en la máquina virtual Java de código de bytes y no se preocupan por la codificación en Java específicamente, tal vez usando Scala o Clojure podría ayudar. Necesitará algún tipo de maquinaria letrec.

1

Ligeramente pedante, pero no es estrictamente necesario crear uno dentro del otro, si no te importa un poco de indirección. Ambos podrían ser clases internas.

public class BadlyNamedClass { 
    private final User owner; 
    private final Inventory inventory; 

    public BadlyNamedClass() { 
     this.owner = new User() { 
      ... has access to BadlyNamedClass.this.inventory; 
     }; 
     this.inventory = new Inventory() { 
      ... has access to BadlyNamedClass.this.owner; 
     }; 
    } 
    ... 
} 

O incluso:

public class BadlyNamedClass { 
    private final User owner; 
    private final Inventory inventory; 

    public BadlyNamedClass() { 
     this.owner = new User(this); 
     this.inventory = new Inventory(this); 
    } 
    public User getOwner() { return owner; } 
    public Inventory getInventory() { return inventory; } 
    ... 
} 
+0

Me parece una buena solución. La relación entre el usuario y el inventario es algo que tiene su propio propósito y, por lo tanto, merece su propia clase. Referirse a otro objeto también puede significar que necesita ir más allá para preguntarle a alguien a quien se refiere una instancia. – SpaceTrucker

+0

Bien pensado, sin embargo, todavía hay un problema ya que su tercer objeto inmutable se escapa en su constructor. –

+0

@AlexandreDupriez ¿Cuál? la semántica de campo 'final', etc., de' owner' y 'inventory' son irrelevantes. Están cubiertos por los de 'BadlyNamedClass'. ('String' que se implementa típicamente usando' char [] 'es el ejemplo canónico de esto.) –

0

B: "Inventario creado por el usuario es nuestra última esperanza".
Y: "No, hay otra".

Si abstrae las referencias a un tercero, puede controlar la relación allí.

Por ejemplo.

public class User 
{ 
    private final String identifier; // uniquely identifies this User instance. 

    public User(final String myIdentifier) 
    { 
     identifier = myIdentifier; 

     InventoryReferencer.registerBlammoUser(identifier); // Register the user with the Inventory referencer. 
    } 

    public Inventory getInventory() 
    { 
     return InventoryReferencer.getInventoryForUser(identifier); 
    } 
} 

public interface Inventory // Bam! 
{ 
    ... nothing special. 
} 

// Assuming that the Inventory only makes sence in the context of a User (i.e. User must own Inventory). 
public class InventoryReferencer 
{ 
    private static final Map<String, Inventory> referenceMap = new HashMap<String, Inventory>(); 

    private InventoryReferencer() 
    { 
     throw ... some exception - helps limit instantiation. 
    } 

    public static void registerBlammoUser(final String identifier) 
    { 
     InventoryBlammo blammo = new InventoryBlammo(); 
     referenceMap.add(indentifier, blammo); 
    } 

    public static void registerKapowUser(final String identifier) 
    { 
     InventoryBlammo kapow = new InventoryKapow(); 
     referenceMap.add(indentifier, kapow); 
    } 

    public static Inentory getInfentoryForUser(final String identifier) 
    { 
     return referenceMap.get(identifier); 
    } 
} 

// Maybe package access constructors. 
public class InventoryBlammo implements Inventory 
{ 
    // a Blammo style inventory. 
} 

public class InventoryKapow implements Inventory 
{ 
    // a Kapow style inventory. 
} 
Cuestiones relacionadas