2008-10-30 10 views
6

Estoy trabajando en un problema de negocios en C# .NET. Tengo dos clases, llamadas C y W, que se crearán de manera independiente en diferentes momentos.¿Cuál es un buen patrón de diseño en C# para las clases que necesitan hacer referencia a otras clases?

Un objeto de clase C necesita contener referencias a 0 ... n objetos de la clase W, es decir, un objeto C puede contener hasta n W objetos.

Cada objeto W debe contener una referencia a exactamente 1 objeto de la clase C, es decir, un objeto W está contenido en un objeto C.

Un objeto de la clase C generalmente se crea primero. En un punto posterior, se descubren sus contenidos W y se crean instancias. En este punto posterior, necesito cruzar los objetos C y W entre sí.

¿Cuál es un buen patrón de diseño para esto? De hecho, tengo casos en los que tengo tres o cuatro clases involucradas, pero podemos hablar de dos clases para hacerlo simple.

Estaba pensando en algo tan simple como:

class C 
{ 
    public List<W> contentsW; 

} 

class W 
{ 
    public C containerC; 

} 

Esto funciona por el momento pero puede prever tener que escribir una buena cantidad de código para realizar un seguimiento de todas las referencias y su validez. Me gustaría implementar el código en el camino para realizar actualizaciones superficiales solo del contenedor y actualizaciones profundas de todas las clases a las que se hace referencia. ¿Hay otros enfoques y cuáles son sus ventajas?

Editar en 11/3: Gracias a todos por las buenas respuestas y buena discusión. Finalmente elegí la respuesta de jop porque estaba más cerca de lo que quería hacer, pero las otras respuestas también me ayudaron. ¡Gracias de nuevo!

+0

¿cómo se hace referencia cruzada entre C y W? ¿Quién tiene "W" antes de que se haga referencia cruzada? –

+0

Como Keith señaló en mi respuesta, tenemos que preguntarnos: ¿cuál es la vida relativa de estos objetos cara a cara? –

Respuesta

6

Si tiene el libro de refabricación de Martin Fowler, simplemente siga la refactorización "Cambiar Asociación unidireccional a bidireccional".

En caso de que no lo tiene, así es como sus clases se verá como después de la refactorización:

class C 
{ 
    // Don't to expose this publicly so that 
    // no one can get behind your back and change 
    // anything 
    private List<W> contentsW; 

    public void Add(W theW) 
    { 
    theW.Container = this; 
    } 

    public void Remove(W theW) 
    { 
    theW.Container = null; 
    } 

    #region Only to be used by W 
    internal void RemoveW(W theW) 
    { 
    // do nothing if C does not contain W 
    if (!contentsW.Contains(theW)) 
     return; // or throw an exception if you consider this illegal 
    contentsW.Remove(theW); 
    } 

    internal void AddW(W theW) 
    { 
    if (!contentW.Contains(theW)) 
     contentsW.Add(theW); 
    } 
    #endregion 
} 

class W 
{ 
    private C containerC; 

    public Container Container 
    { 
    get { return containerC; } 
    set 
    { 
     if (containerC != null) 
     containerC.RemoveW(this); 
     containerC = value; 
     if (containerC != null) 
     containerC.AddW(this); 
    } 
    } 
} 

toman nota de que he tomado la List<W> privado. Exponga la lista de W a través de un enumerador en lugar de exponer la lista directamente.

p. Ej. public List GetWs() {return this.ContentW.ToList(); }

El código anterior maneja la transferencia de propiedad correctamente. Digamos que tiene dos instancias de C - C1 y C2 - y las instancias de W - W1 y W2.

W1.Container = C1; 
W2.Container = C2; 

En el código anterior, C1 contiene W1 y C2 contiene W2. Si reasigna a W2 C1

W2.Container = C1; 

Entonces C2 tendrá cero artículos y C1 tendrá dos elementos - W1 y W2. Puede tener un flotante W

W2.Container = null; 

En este caso, W2 se eliminará de la lista de C1 y no tendrá contenedor. También puede usar los métodos Agregar y Eliminar de C para manipular los contenedores de W, por lo que C1.Add (W2) eliminará automáticamente W2 de su contenedor original y lo agregará al nuevo.

2

Hmmm, parece que casi lo consiguió, con un pequeño problema - tienes que ser capaz de controlar la adición a la lista dentro de C.

por ejemplo,

class C 
{ 
    private List<W> _contentsW; 

    public List<W> Contents 
    { 
     get { return _contentsw; } 
    } 

    public void AddToContents(W content); 
    { 
     content.Container = this; 
     _contentsW.Add(content); 
    } 
} 

Para el control, se solo tengo que repetir en su lista, creo:

foreach (var w in _contentsW) 
{ 
    if (w.Container != this) 
    { 
     w.Container = this; 
    } 
} 

No estoy seguro si eso es lo que necesita.

Tenga en cuenta que puede haber varias instancias de W que tengan los mismos valores, pero pueden tener diferentes contenedores C.

+0

que puede funcionar o no dependiendo de las vidas de C y W. si se supone que C muere antes que W, entonces W realmente necesita una referencia débil a C –

+0

Supongo que es por eso que pregunté cómo debería manejarse W. El requisito parece lo suficientemente simple como para tener que preocuparse por la existencia de W. Tal vez un concepto más concreto ayudará a aclarar el problema. –

0

Una opción para esto sería implementar las interfaces IContainer y IComponent que se encuentran en System.ComponentModel. C sería el contenedor y W el componente. La clase ComponentCollection serviría entonces como el almacenamiento para sus casos W, y IComponent.Site proporcionaría la copia de enlace a C.

3

generalmente lo hago algo como esto:

class C 
{ 
    private List<W> _contents = new List<W>(); 
    public IEnumerable<W> Contents 
    { 
     get { return _contents; } 
    } 

    public void Add(W item) 
    { 
     item.C = this; 
     _contents.Add(item); 
    } 
} 

lo tanto, su contenido la propiedad es de solo lectura y agrega elementos solo a través del método de su agregado.

1

Ampliando Jons respuesta ....

Es posible que tenga referencias débiles si isnt W supone mantener viva C.

también ... el complemento debe ser más complicada si desea transferir la propiedad ...

public void AddToContents(W content); 
{ 
    if(content.Container!=null) content.Container.RemoveFromContents(content); 
    content.Container = this; 
    _contentsW.Add(content); 
} 
0

Este es el patrón de uso.

public class Parent { 
    public string Name { get; set; } 
    public IList<Child> Children { get { return ChildrenBidi; } set { ChildrenBidi.Set(value); } } 
    private BidiChildList<Child, Parent> ChildrenBidi { get { 
     return BidiChildList.Create(this, p => p._Children, c => c._Parent, (c, p) => c._Parent = p); 
    } } 
    internal IList<Child> _Children = new List<Child>(); 
} 

public class Child { 
    public string Name { get; set; } 
    public Parent Parent { get { return ParentBidi.Get(); } set { ParentBidi.Set(value); } } 
    private BidiParent<Child, Parent> ParentBidi { get { 
     return BidiParent.Create(this, p => p._Children,() => _Parent, p => _Parent = p); 
    } } 
    internal Parent _Parent = null; 
} 

Obviamente, tengo clases BidiParent<C, P> y BidiChildList<C, P>, el último de los cuales implementa IList<C>, etc. Detrás de las escenas de las actualizaciones se realizan a través de los campos internos, mientras que las actualizaciones de código que utiliza este modelo de dominio se realiza a través de las propiedades públicas.

Cuestiones relacionadas