2009-03-05 16 views
5

Me encontré con esto un par de veces, y me pregunté cuál es la manera OO de resolver referencias circulares. Con eso quiero decir que la clase A tiene la clase B como miembro, y B a su vez tiene la clase A como miembro.¿Cómo resolver los cruces de referencia en OOP?

Un ejemplo de esto sería la clase Persona que tiene cónyuge Persona como miembro.

Person jack = new Person("Jack"); 
Person jill = new Person("Jill"); 
jack.setSpouse(jill); 
jill.setSpouse(jack); 

Otro ejemplo serían las clases de productos que tienen alguna colección de otros productos como miembro. Esa colección podría ser, por ejemplo, productos en los que las personas interesadas en este producto también podrían estar interesadas, y queremos mantener esa lista por base de producto, no en los mismos atributos compartidos (por ejemplo, no queremos simplemente mostrar todos los demás productos en la misma categoría).

Product pc = new Product("pc"); 
Product monitor = new Product("monitor"); 
Product tv = new Product("tv"); 
pc.setSeeAlso({monitor, tv}); 
monitor.setSeeAlso({pc}); 
tv.setSeeAlso(null); 

(estos productos son sólo para hacer un punto, el asunto no se trata de tiempo o no ciertos productos podría relacionarse entre sí)

¿Sería esto un mal diseño de programación orientada a objetos en general? ¿Deberían/​​deberían todos los lenguajes OOP permitir esto, o es solo una mala práctica? Si es una mala práctica, ¿cuál sería la mejor manera de resolver esto?

+0

@Jon Limjap: Gracias por corregir el error tipográfico. – tehvan

Respuesta

1

No es un problema fundamental en el diseño de OO. Un ejemplo de un momento en que podría convertirse en un problema es en el recorrido de un gráfico, por ejemplo, encontrar la ruta más corta entre dos objetos: podría entrar potencialmente en un ciclo infinito. Sin embargo, eso es algo que tendría que considerar caso por caso. Si sabe que podría haber referencias cruzadas en un caso como ese, codifique algunos controles para evitar bucles infinitos (por ejemplo, mantener un conjunto de nodos visitados para evitar volver a visitarlos). Pero si no hay ninguna razón por la cual podría ser un problema (como en los ejemplos que dio en su pregunta), entonces no está mal tener tales referencias cruzadas. Y en muchos casos, como ha descrito, es una buena solución para el problema en cuestión.

2

Los ejemplos que ofrece son (para mí, de todos modos) ejemplos de diseño OO razonable.

El problema de las referencias cruzadas que describes no es un artefacto de ningún proceso de diseño, sino una característica real de las cosas que estás representando como objetos, por lo que no veo que haya un problema.

¿Qué ha encontrado que le ha dado la impresión de que este enfoque es de mal diseño?

Actualización 11 de marzo:

En los sistemas que carecen de recolección de basura, donde la gestión de la memoria se gestiona de forma explícita, un enfoque común es exigir a todos los objetos que tienen una propietario - algún otro objeto responsable de la gestión la vida de ese objeto.

Un ejemplo es la clase TComponent de Delphi, que proporciona compatibilidad en cascada: destruye el componente principal y todos los componentes de propiedad también se destruyen.

Si está trabajando en un sistema de este tipo, los tipos de bucle referencial descritos en esta pregunta pueden considerarse como un diseño deficiente porque no hay un propietario claro, ni un solo objeto responsable de la gestión de vidas útiles.

La forma en que he visto esta manejado en algunos sistemas es retener las referencias (porque captan adecuadamente las preocupaciones de negocios), y añadir de forma explícita TransactionContext objeto que es dueño de todo cargado en el dominio de negocio de la base de datos. Este objeto de contexto se ocupa de saber qué objetos se deben guardar y lo limpia todo cuando se completa el procesamiento.

+0

También me parece real, solo pensaba que los VMs no se confundirían persiguiendo objetos ... cargando a una Persona, cargando al cónyuge de esa persona, otra vez a la esposa de esa persona, ... es un ciclo eterno. Solo me pregunto si todas las máquinas virtuales pueden manejarlo correctamente. – tehvan

+0

VM? ¿Te refieres a tiempos de ejecución? Los sistemas contados solo por ref. (COM o roll-your own punteros inteligentes, por ejemplo), cualquier sistema de GC real no lo hará –

+0

Los ciclos de referencia son manejados muy bien por los entornos CLR (.NET) y JVM. También fueron manejados muy bien por los primeros entornos Lisp y Smalltalk, dentro de los cuales gran parte de esta tecnología fue probada originalmente. – Bevan

1

El momento principal en que esto es un problema es si se vuelve demasiado confuso de manejar o mantener, ya que puede convertirse en una forma de código de spaghetti.

Sin embargo, para ver sus ejemplos;

Vea también es perfectamente válido si esto es una característica que necesita en su código - se trata de una simple lista de punteros (o referencias) a otros elementos de un usuario puede estar interesado en

Similarmente es. perfectamente válido para agregar cónyuge, ya que esta es una simple relación del mundo real que no sería confusa para alguien que mantenga su código.

Siempre lo he visto como un potencial olor a código, o tal vez una advertencia para dar un paso atrás y racionalizar lo que estoy haciendo.

En cuanto a algunos sistemas que encuentran relaciones recursivas en su código (mencionados en un comentario anterior), estos pueden aparecer independientemente de este tipo de diseño. Recientemente trabajé en un sistema de captura de metadatos que tenía 'tipos' de relaciones recursivas, es decir, columnas que estaban lógicamente relacionadas con otras columnas. Necesita ser manejado por el código que intenta analizar su sistema.

-2

Una forma de solucionar esto es hacer referencia a otro objeto mediante una identificación.

p. Ej.

Person jack = new Person(new PersonId("Jack")); 
Person jill = new Person(new PersonId("Jill")); 
jack.setSpouse(jill.getId()); 
jill.setSpouse(jack.getId()); 

No estoy diciendo que sea una solución perfecta, pero evitará referencias circulares. Está utilizando un objeto en lugar de una referencia de objeto para modelar la relación.

+0

Esto es pensar demasiado como una base de datos. Establecer una referencia a un objeto logra lo mismo con menos trabajo. – Boden

+0

Pero si desea persistir el gráfico de objetos en una base de datos, este enfoque permitirá la carga parcial del gráfico de objetos sin proxying y evitará las referencias circulares. Como dije, no es la solución perfecta, pero la he visto usar con éxito en una línea multimillonaria de aplicaciones de código. – parkr

0

No creo que las referencias circulares como tales sean un problema.

Sin embargo, poner todas esas relaciones dentro de los objetos puede generar demasiado desorden, por lo que puede querer representarlos en el exterior. P.ej. puede usar una tabla hash para almacenar relaciones entre productos.

1

No creo que este sea un ejemplo de referencias cruzadas.

Referencias cruzadas por lo general se refiere a este caso:

class A 
{ 
    public void MethodA(B objectB) 
    { 
     objectB.SomeMethodInB(); 
    } 
} 

class B 
{ 
    public void MethodB(A objectA) 
    { 
     objectA.SomeMethodInA(); 
    } 
} 

En este caso, cada tipo de objeto "llega en" el uno al otro; A llama a B, B llama a A, y se unen estrechamente. Esto se hace aún peor si A y B están en paquetes/espacios de nombres/conjuntos diferentes; en muchos casos, estos crearían errores de tiempo de compilación ya que los ensamblados se compilan linealmente.

La forma de resolver eso es hacer que cualquiera de los objetos implemente una interfaz con el método deseado.

En su caso sólo tiene un nivel de "llegar a":

public Class Person 
{ 
    public void setSpouse(Person person) 
    { ... } 
} 

no creo que esto no sea razonable, ni siquiera un caso de referencias cruzadas/referencias circulares.

0

Hacer referencia a otros objetos no es realmente un mal diseño de OO. Es la forma en que se gestiona el estado dentro de cada objeto.

Una buena regla de oro es la Ley de Demeter. Mire este papel perfecto de LoD (Paperboy y la billetera): click here

Cuestiones relacionadas