2008-09-24 13 views
5

Esto es un poco difícil de explicar, espero que mi Inglés es suficiente:El control del acceso a una colección interna en C# - patrón requerido

tengo una clase "A", que debe mantener una lista de objetos de la clase " B "(como una lista privada). Un consumidor de clase "A" debería poder agregar elementos a la lista. Después de que los artículos se agregan a la lista, el consumidor no debería poder modificarlos de nuevo, dejando solo que no debería ser capaz de atemperar con la lista en sí (agregar o quitar elementos). Pero debería ser capaz de enumerar los elementos en la lista y obtener sus valores. ¿Hay un patrón para eso? ¿Cómo lo harías tú?

Si la pregunta no es lo suficientemente clara, házmelo saber.

Respuesta

3

Para evitar editar la lista o sus elementos, debe hacerlos immutable, lo que significa que debe devolver una nueva instancia de un elemento en cada solicitud. excelente serie

Véase Eric Lippert de "Inmutabilidad en C#": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (usted tiene que desplazarse un poco)

+0

Excelente enlace - gracias por eso –

+0

La inmutabilidad de la que habla Eric no significa devolver una nueva instancia cada vez. Él te muestra cómo escribir objetos que, una vez que están construidos, nunca cambian. Eso significa que puede compartir la misma instancia en todas partes y tener la seguridad de que nadie más cambiará esa instancia. –

0

EDIT: Se agregó soporte para contextos de edición. La persona que llama solo puede agregar elementos dentro de un contexto de edición. Además, puede hacer cumplir que solo se puede crear un contexto de edición para la duración de la instancia.


Usando el encapsulado puede definir cualquier conjunto de políticas para acceder al miembro privado interno. El siguiente ejemplo es una aplicación básica de sus requerimientos:

namespace ConsoleApplication2 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Collections; 

    class B 
    { 
    } 

    interface IEditable 
    { 
     void StartEdit(); 
     void StopEdit(); 
    } 

    class EditContext<T> : IDisposable where T : IEditable 
    { 
     private T parent; 

     public EditContext(T parent) 
     { 
      parent.StartEdit(); 
      this.parent = parent; 
     } 

     public void Dispose() 
     { 
      this.parent.StopEdit(); 
     } 
    } 

    class A : IEnumerable<B>, IEditable 
    { 
     private List<B> _myList = new List<B>(); 
     private bool editable; 

     public void Add(B o) 
     { 
      if (!editable) 
      { 
       throw new NotSupportedException(); 
      } 
      _myList.Add(o); 
     } 

     public EditContext<A> ForEdition() 
     { 
      return new EditContext<A>(this); 
     } 

     public IEnumerator<B> GetEnumerator() 
     { 
      return _myList.GetEnumerator(); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return this.GetEnumerator(); 
     } 

     public void StartEdit() 
     { 
      this.editable = true; 
     } 

     public void StopEdit() 
     { 
      this.editable = false; 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      A a = new A(); 
      using (EditContext<A> edit = a.ForEdition()) 
      { 
       a.Add(new B()); 
       a.Add(new B()); 
      } 

      foreach (B o in a) 
      { 
       Console.WriteLine(o.GetType().ToString()); 
      } 

      a.Add(new B()); 

      Console.ReadLine(); 
     } 
    } 
} 
+0

Esta es una buena implementación, pero no creo que el acceso a los mutadores en o esté controlado en su ejemplo para el ciclo. –

+0

Me gusta el concepto de EditContext, pero no hay nada aquí para evitar que A se edite por segunda vez, tal vez durante el foreach en el ejemplo anterior. Quizás si configura editable = true en el constructor de A, y luego elimina esa línea de StartEdit? –

0

Es, básicamente, quiere evitar que regalar referencias a los objetos de la clase B. Es por eso que debes hacer una copia de los artículos.

Creo que esto se puede resolver con el método ToArray() de un objeto List. Debe crear una copia profunda de la lista si desea evitar cambios.

En general, la mayoría de las veces no vale la pena hacer una copia para forzar el buen comportamiento, especialmente cuando también se escribe al consumidor.

0
public class MyList<T> : IEnumerable<T>{ 

    public MyList(IEnumerable<T> source){ 
     data.AddRange(source); 
    } 

    public IEnumerator<T> GetEnumerator(){ 
     return data.Enumerator(); 
    } 

    private List<T> data = new List<T>(); 
} 

La desventaja es que un consumidor puede modificar los elementos que recibe del enumerador, una La solución es hacer una copia profunda de la lista privada < T>.

0

No estaba claro si también necesitaba que las instancias B fueran inmutables una vez agregadas a la lista. Puede jugar un truco usando una interfaz de solo lectura para B, y solo exponiéndolos a través de la lista.

internal class B : IB 
{ 
    private string someData; 

    public string SomeData 
    { 
     get { return someData; } 
     set { someData = value; } 
    } 
} 

public interface IB 
{ 
    string SomeData { get; } 
} 
+0

Esto está bien hasta donde llegue, pero la persona que llama puede enviar cualquier interfaz IB recuperada a voluntad, lo que le permite usar el setter. –

+0

Depende de la configuración. A menudo puede evitar esto al cambiar la visibilidad de la clase, como en el ejemplo anterior. De todos modos, al menos esta implementación te da una pista de que no debes actualizar los objetos. –

0

El más simple que puedo pensar es devolver un readonly version de la colección subyacente si la edición ya no es permitido.

public IList ListOfB 
{ 
    get 
    { 
     if (_readOnlyMode) 
      return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB); 
     else 
      return listOfB; 
    } 
} 

Personalmente, sin embargo, no lo exponga la lista subyacente al cliente y sólo proporcionan métodos para añadir, eliminar y enumerar los casos B.

+0

Esto no cumple el requisito de que "Después de que los artículos se agreguen a la lista, el consumidor no debería poder modificarlos nuevamente". La nueva lista contiene * referencias * a los elementos en la lista original y los usuarios pueden llamar mutators a voluntad. –

+0

Mejor uso su clase ProxyB entonces. :) – jop

1

Guau, aquí hay algunas respuestas demasiado complejas por un problema simple.

Tener una privada List<T>

tener un método public void AddItem(T item) - cada vez que decide hacer que deje de funcionar, hacer que deje de funcionar. Podría lanzar una excepción o podría hacer que falle silenciosamente. Depende de lo que estés pasando allí.

tener un método que hace public T[] GetItems()return _theList.ToArray()

+0

¡LOL! Estoy de acuerdo, hice lo mismo con una lista privada y una cadena pública [] en un trabajo que estaba haciendo anoche por esta misma razón :) –

+0

Esto no cumple el requisito de que "Después de que los elementos se agreguen a la lista, el consumidor no debería poder modificarlos de nuevo ". La matriz contiene * referencias * a los elementos en la lista y los usuarios pueden llamar mutadores a voluntad. –

+0

Estoy de acuerdo con Seb Rose: si no desea que los elementos de la lista sean editables, debe evitar devolver la referencia original -> inmutabilidad. – VVS

1

Como muchas de estas respuestas mostrar, hay muchas maneras de hacer la propia colección inmutable.

Requiere más esfuerzo mantener inmutables a los miembros de la colección. Una posibilidad es utilizar una fachada/proxy (lo siento por la falta de brevedad):

class B 
{ 
    public B(int data) 
    { 
     this.data = data; 
    } 

    public int data 
    { 
     get { return privateData; } 
     set { privateData = value; } 
    } 

    private int privateData; 
} 

class ProxyB 
{ 
    public ProxyB(B b) 
    { 
     actual = b; 
    } 

    public int data 
    { 
     get { return actual.data; } 
    } 

    private B actual; 
} 

class A : IEnumerable<ProxyB> 
{ 
    private List<B> bList = new List<B>(); 

    class ProxyEnumerator : IEnumerator<ProxyB> 
    { 
     private IEnumerator<B> b_enum; 

     public ProxyEnumerator(IEnumerator<B> benum) 
     { 
      b_enum = benum; 
     } 

     public bool MoveNext() 
     { 
      return b_enum.MoveNext(); 
     } 

     public ProxyB Current 
     { 
      get { return new ProxyB(b_enum.Current); } 
     } 

     Object IEnumerator.Current 
     { 
      get { return this.Current; } 
     } 

     public void Reset() 
     { 
      b_enum.Reset(); 
     } 

     public void Dispose() 
     { 
      b_enum.Dispose(); 
     } 
    } 

    public void AddB(B b) { bList.Add(b); } 

    public IEnumerator<ProxyB> GetEnumerator() 
    { 
     return new ProxyEnumerator(bList.GetEnumerator()); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 

La desventaja de esta solución es que la persona que llama será iterar sobre una colección de objetos ProxyB, en lugar de los objetos B se adicional.

Cuestiones relacionadas