2009-05-28 24 views
7

este debe ser un escenario tan común que ya se ha escrito mucho sobre él, con suerte incluso un patrón realmente bueno. Tengo un modelo de dominio en el que un contenedor personalizado contiene entidades. Por ejemplo (propiedades e interfaces excluidos por razones de brevedad):Evite la referencia circular en el modelo de dominio

class Entity 
{ 
    public int Id; 
    public EntityContainer ParentContainer; 
} 


class EntityContainer 
{ 
    public int Id; 
    public IList<Entity> Entities = new List<Entity>(); 

    public void AddEntity(Entity entity) 
    { 
     entity.ParentContainer = this; 
     Entities.Add(entity); 
    } 
} 


class Main 
{ 
    public Main() 
    { 
     Entity entity1 = new Entity(); 
     Entity entity2 = new Entity(); 
     EntityContainer entityContainer = new EntityContainer(); 
     entityContainer.AddEntity(entity1); 
     entityContainer.AddEntity(entity2); 

     // Can now traverse graph easily, e.g. 
     Console.WriteLine("entity1's parent container ID = " + entity1.ParentContainer.Id); 
     Console.WriteLine("Container contains at least this entity ID: " + entityContainer.Entities[0].Id); 

    } 
} 

ahora puedo atravesar fácilmente mi gráfico de objetos en ambos sentidos, pero han creado una referencia circular. ¿Crearías un tercer tipo para divorciar las dependencias?

Gracias de antemano

+2

El modelo que tiene allí no permite una relación inversa para más de un contenedor principal, por lo que lo más probable es que no funcione de la misma manera si tiene una entidad en varios contenedores. – workmad3

+0

¿Puedes aclarar cómo es eso circular? está creando una estructura de árbol por lo que yo entiendo, así que no veo de dónde viene la circularidad – RobV

+0

Entidad tiene una referencia a EntityContainer y EntityContainer tiene una referencia a Entidad. – ng5000

Respuesta

4

No hay nada malo con referencias circulares, per se, y se utilizan ampliamente en la .NET Framework, por ejemplo XmlNode.OwnerDocument, Control.Parent.

Si necesita atravesar el árbol, puede utilizar una referencia anterior.

En COM, las referencias circulares son complicadas porque si fuera el conjunto del contenedor y todos sus elementos secundarios a nada, entonces los objetos no se limpiarán correctamente ya que los niños todavía tienen referencias al elemento primario. Sin embargo, la recolección de basura .NET no tiene ningún problema con la forma en que se implementa.

+0

Un problema que encuentro con las referencias circulares es con la serialización. Generalmente, si el objeto A tiene B y el objeto B tiene A, no se serializará. Esto es particularmente problemático al colocar estos objetos en un estado de sesión fuera de proceso. –

2

¿El contenedor necesitan saber sobre el tipo de los contenidos? De lo contrario, los genéricos pueden evitar esto, es decir, Container<T>, donde ocurre para usar Container<Entity>. Aparte de eso; introducir los detalles necesarios en una interfaz (o clase base) en un conjunto al que ambos pueden hacer referencia es un enfoque común.

Personalmente, trataría simplemente de evitar la necesidad de que el niño sepa sobre el padre.

También; tenga en cuenta que si hace baje por la ruta de abstracción (interfaz, etc.); esto puede tener una gran implicación si está utilizando (por ejemplo) serialización xml.


(re) editar comentarios de OK; primero: qué problema está causando la referencia circular (dentro de un ensamblaje); si ninguno, déjalo en paz. Si hay un problema, necesitarás un tipo adicional; presumiblemente algunas interfaces para representar los tipos de hormigón - es decir, cuando Entity : IEntity y EntityContainer sólo conoce IEntity (o vv con IEntityContainer, o ambos),

+1

El niño necesita saber acerca de su padre por motivos de rendimiento. – ng5000

+0

Generica no sería adecuado para el dominio del problema real que estoy modelando. Estos son tipos y relaciones específicos. – ng5000

+0

Gracias por la actualización: voy a agregar interfaces. Creo que el consenso general de que no hay nada de malo con las referencias circulares es la principal conclusión de esta pregunta. – ng5000

1

Como tal, no veo un problema con su modelo de clase pero podría hacer un corte limpio. Haga que Entity implemente una interfaz IEntity y haga que EntityContainer contenga un IList y, a menos que tenga un motivo muy específico para usar IList, debe considerar IEnumerable, lo haría más fácil para el consumidor de la EntityClass que utiliza. Como pasar cualquier matriz de IEntity, o la expresión linq seleccionando IEntities sería posible

+0

Llamar a Enumerable.ToList() en una lista definitivamente no es gratis, ya que siempre produce una nueva lista, por lo que tendrá que hacer una copia, que es O (N). –

+0

@Pavel nope Está optimizado para simplemente devolver el objeto de la lista si se llama en una lista –

+0

Está completamente equivocado. Abre crack System.Core.dll con ILSpy y mira la implementación de 'Enumerable.ToList'. Es literalmente tan simple como 'return new List()' más un argumento null check. Esto es bastante intencional: los diseñadores de LINQ no querían que los clientes de la biblioteca bajaran los objetos a algo que el autor de la biblioteca no esperaba, y potencialmente mutar los datos internos. Entonces todos los métodos 'To ...()' crearán una copia incluso si el original ya es de ese tipo. Por el contrario, el 'As ...()' devuelve el objeto original. –

1

El uso de objetos de referencia circular está bien en mi libro siempre que esté utilizando algún tipo de patrón de carga diferida al diseñar esos objetos.

p. Ej.

que desea acceder: Company.Employee Y en otro escenario: Empleado.Company

Lo que hace una referencia circular es decir Company.Employee.Company.Employee etc.

Si estas propiedades no son-lazy cargado, por ejemplo, Un objeto de la Compañía siempre carga su propiedad de Empleado y el objeto Empleado siempre carga su propiedad de Compañía, entonces no va a funcionar demasiado bien cuando introduce una fuente de datos.

Cuestiones relacionadas