¿Cuál es un buen patrón de diseño en C# para clases que necesitan hacer referencia a otras clases?
-
05-07-2019 - |
Pregunta
Estoy trabajando en un problema empresarial en C#.NET.Tengo dos clases, denominadas C y W, de las que se crearán instancias de forma independiente en diferentes momentos.
Un objeto de clase C necesita contener referencias a 0...n objetos de clase W, es decirun objeto C puede contener hasta n objetos W.
Cada objeto W debe contener una referencia a exactamente 1 objeto de clase C, es decirun objeto W está contenido en un objeto C.
Generalmente se crea una instancia de un objeto de clase C primero.En un momento posterior, se descubre su contenido W y se crea una instancia.En este punto posterior, necesito hacer una referencia cruzada de 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 simplificar las cosas.
Estaba pensando en algo simple como:
class C
{
public List<W> contentsW;
}
class W
{
public C containerC;
}
Esto funcionará por el momento, pero preveo que tendré que escribir una buena cantidad de código para realizar un seguimiento de todas las referencias y su validez.Me gustaría implementar código en el futuro para realizar actualizaciones superficiales solo del contenedor y actualizaciones profundas de todas las clases a las que se hace referencia.¿Existen otros enfoques y cuáles son sus ventajas?
Editar el 3/11:Gracias a todos por las buenas respuestas y la buena discusión.Finalmente elegí la respuesta de jop porque se acercaba más a lo que quería hacer, pero las otras respuestas también ayudaron.¡Gracias de nuevo!
Solución
Si tiene el libro de Refactorización de Martin Fowler, simplemente siga el " Cambiar Asociación Unidireccional a Bidireccional " refactorización.
En caso de que no lo tenga, así es como se verán sus clases 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);
}
}
}
Tenga en cuenta que he hecho que el List<W>
sea privado. Exponga la lista de Ws a través de un enumerador en lugar de exponer la lista directamente.
p. Lista pública 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 W2 a C1
W2.Container = C1;
Entonces C2 tendrá cero elementos y C1 tendrá dos elementos: W1 y W2. Puedes tener una W flotante
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.
Otros consejos
Generalmente lo hago más o menos así:
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);
}
}
Por lo tanto, su propiedad Contenido es de solo lectura y agrega elementos solo a través del método de su agregado.
Hmmm, parece que casi lo tienes, con un pequeño error: debes poder 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 verificar, solo tienes que recorrer tu 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 tendrían los mismos valores pero que podrían tener diferentes contenedores de C.
Expandiendo sobre Jons Answer ...
Puede que necesite referencias débiles si no se supone que W mantenga a C con vida.
Además ... el complemento debería ser más complicado si desea transferir la propiedad ...
public void AddToContents(W content);
{
if(content.Container!=null) content.Container.RemoveFromContents(content);
content.Container = this;
_contentsW.Add(content);
}
Una opción para esto sería implementar el IContainer y IComponent interfaces encontradas en Sistema. Componente Modelo. C sería el contenedor y W el componente. La clase ComponentCollection serviría como almacenamiento para sus instancias W e IComponent.Site proporcionarían el vínculo de retroceso a C.
Este es el patrón que 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.Las actualizaciones detrás de escena se realizan a través de los campos internos, mientras que las actualizaciones del código que utiliza este modelo de dominio se realizan a través de las propiedades públicas.