¿Cuál es un buen patrón de diseño en C# para clases que necesitan hacer referencia a otras clases?

StackOverflow https://stackoverflow.com/questions/249171

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!

¿Fue útil?

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top