2010-07-01 7 views
6

Estoy refabricando un poco del código de acceso a datos C# de un desarrollador anterior y tengo curiosidad acerca de un patrón que usó.Cualquier ventaja para los objetos. ¿Obtiene Objeto (i) sobre los objetos [i]?

El código inicialmente expuso colecciones (arrays) de una variedad de objetos comerciales de estilo ActiveRecord, esencialmente objetos envolviendo campos de base de datos. Estoy cambiando las matrices a listas genéricas, pero el aspecto del código Tengo curiosidad acerca es que el desarrollador anterior tenía conseguir métodos para cada tipo de objeto que estaba envolviendo, de esta manera:

public Thing GetThing(int i) { 
    return things[i]; 
} 

hay varios de estos métodos, y no puedo pensar en ninguna ventaja posible de usar ese mecanismo simplemente refiriéndome a las cosas [i] directamente. Supongamos, por el argumento, que las cosas son una propiedad pública, no un campo público (en este caso, en realidad es una propiedad implementada automáticamente, por lo que esa suposición es cierta en realidad).

¿Me falta algo obvio? O incluso algo esotérico?

ACTUALIZACIÓN probablemente debería aclarar que estas colecciones se accede actualmente dentro de los bucles:

for (int i = 0; i < thingsCount; i==) { 
    dosomthing(GetThing(i)); 
    dosomethingelse(GetThing(i)); 
} 

que estoy refactorización a:

for (int i = 0; i < thingsCount; i==) { 
    Thing thing = things[i]; 
    dosomthing(thing); 
    dosomethingelse(thing); 
} 

y tal vez incluso a usar las cosas. para cada().

Respuesta

6

No sé si es obvio, pero sí creo que se está perdiendo algo.

Digamos things es un IList<Thing>. Luego exponerlo directamente (como Things) permitiría que el código de llamada llame al Add, Insert, RemoveAt, etc. Tal vez el desarrollador anterior no quiso permitir esto (y estoy seguro de que hay muchas buenas razones para eso).

Aun suponiendo que es un Thing[] (por lo Add, etc., no estaría disponible), exponiéndolo como tal todavía permitiría código de llamada a hacer algo como obj.Things[0] = new Thing(); que puede ser una operación que no se debe permitir en función de la clase de implementación.

Usted podría exponer Things como ReadOnlyCollection<Thing> la que se haría cargo de la mayoría de estos problemas. Pero lo que se reduce a esto es esto: si el desarrollador solo quiere permitir que el código de llamada acceda a los elementos por índice, nada más, entonces proporcionar un único método GetThing como medio para hacerlo, honestamente, hace que sea muy la mayoria de los sentidos

Ahora, concedida, también existe esta opción: implementar una propiedad this[int] con solo un acceso get. Pero eso solo tiene sentido si la clase en cuestión es esencialmente una colección de objetos Thing exclusivamente (es decir, no hay también una colección de otro tipo de objeto al que desea proporcionar acceso dentro de la clase).

En total, creo que el enfoque GetThing es bastante bueno.

Dicho esto, desde la forma en que has redactada así su pregunta, que suena como el desarrollador anterior hecho algunas otras decisiones bastante pobre: ​​

  1. Si él/ella también expone la colección things directamente como una propiedad pública, bueno, entonces ... que vence el propósito del método GetThing, ¿no es así? El resultado es simplemente una interfaz inflada (en general, creo que no es una gran señal cuando tienes múltiples métodos para lograr exactamente lo mismo, a menos que estén claramente documentados como alias por algún motivo justificable). [Actualización: Parece que el desarrollador anterior hizo no hacer esto. Bueno.]
  2. Parece que el desarrollador anterior también fue internamente accediendo a elementos de things utilizando el método GetThing, que es simplemente una tontería (en mi opinión). ¿Por qué introducir la sobrecarga inútil de las llamadas a métodos adicionales utilizando la interfaz pública de una clase desde dentro de la misma clase? Si está en la clase, ya está en dentro de la implementación y puede acceder a los datos privados/protegidos todo lo que quiera, sin necesidad de pretender lo contrario.
+0

Pensando en la respuesta de @Elpezmuerto, estaba llegando exactamente a esta conclusión, a pesar de que estoy exponiendo las * colecciones * como propiedades con {get; conjunto privado;} una vez que obtengo la colección, puedo modificarla en el código de llamada de forma que no tenga ningún sentido. El desarrollador anterior los ha expuesto como protegidos, por lo que su punto 1 no es relevante en mi caso, pero estaba llamando a GetThing (i) varias veces en cada ciclo, lo cual, estoy de acuerdo, no tiene sentido. Parece que el mejor refactor es dejar los métodos GetThing() tal como están, pero solo llamarlos una vez por ciclo. Gracias! – cori

+0

@cori: Sí, un error muy común que los desarrolladores hacen al diseñar sus clases expone colecciones mutables directamente, sin tener en cuenta lo que puede ocurrir si/cuando esas colecciones se modifican. Si solo desea habilitar el código de llamada para enumerar y no modificar una colección, exponer un 'IEnumerable ' y no, por ejemplo, 'List '! –

+0

@cori: admitiré que es bastante frustrante, sin embargo, cuando se quiere permitir el acceso aleatorio por índice, pero no todas las demás características de la interfaz 'IList ' o incluso un 'T []' (que le permite establecer elementos , no solo obtenerlos). Esto es realmente algo sobre lo que pregunté en [una vieja pregunta personal] (http://stackoverflow.com/questions/2110440/why-is-there-no-iarrayt-interface-in-net) hace un tiempo. –

2

Hay dos cosas a tener en cuenta aquí. Lo primero es que desea mantener las variables de objeto privadas y usar getters y setters para acceder a ellas. Esto evita que el usuario cambie o modifique accidentalmente las variables del objeto.

En segundo lugar, se considera una buena convención de nomenclatura donde los términos get/set se deben usar cuando se accede a un atributo directamente. Esto ayuda a mejorar la legibilidad.

Aunque es una propiedad pública en este caso, el uso de getters y setters mejora la legibilidad y ayuda a prevenir comportamientos inesperados. No importa si está accediendo a las variables del objeto dentro de un bucle, debe continuar utilizando la convención GetThing.

Finalmente, si está accediendo a las variables desde dentro del objeto, no necesita usar el captador o instalador.

NOTA: Su buen estilo generalmente se considera para mantener las variables de objeto privado y usar getters/setters para todos los idiomas

C++ style guidelines

C# style guidelines

Usted también puede estar interesado en la pregunta "getter and setter for class in class c#"

+0

Puedo entender esa lógica (aunque no veo nada que haga referencia a ella en el ejemplo de C# al que apunta). ¡Gracias! – cori

+0

@Cori Enlace actualizado – Elpezmuerto

2

Probablemente quiera exponer el objeto como IList<Thing>. Esto le dará las capacidades de indexación que está buscando, pero también puede usar el rango de funciones LINQ, como crear una nueva lista de elementos según las condiciones. Además, IList<T> implementa IEnumerable<T>, por lo que podrá usar foreach para recorrer los objetos.

p. Ej.en su clase:

public IList<Thing> Things { get; private set; } 

p. Uso:

Thing x = business.Things[3]; 

o

var x = business.Things.Where(t => t.Name == "cori"); 
+0

Ahí es donde me dirigía con la refactorización a List (y sí, IList probablemente sería una mejor opción). – cori

1

Si tuviera que adivinar, diría que este desarrollador se utilizó para otro idioma como Java, y no estaba completamente al tanto de la práctica estándar en C#. La nomenclatura "get [Property]" se usa mucho en Java, javascript, etc. C# reemplaza esto con propiedades e indexadores. Las propiedades son tan potentes como getters y setters, pero son más fáciles de escribir y usar. El único momento en que normalmente se ve "Get [algo]" en C# es si:

  • La operación es probable que sea lo suficientemente caro que realmente quiere llevar a casa el hecho de que esto no es sencillo el acceso de miembros (por ejemplo GetPrimeNumbers()) o
  • Su colección realmente incluye varias colecciones indexadas. (Por ejemplo GetRow(int i) y . Incluso en este caso, es más común que simplemente exponer a cada una de estas colecciones indexadas como una propiedad en sí mismo, que es de tipo indexado ("table.Rows[2]").

Si está solo accediendo a estos valores en los bucles for, la colección debe implementar IEnumerable<Thing>, lo que le daría acceso a los métodos LINQ y al constructo foreach. Si aún necesita tener buscadores basados ​​en índices, debería considerar usar su propia interfaz que extienda IEnumerable<T>, pero también proporciona:

T this[int i] { get; } 

De esta manera, no le da a los consumidores la impresión de que pueden Add y Remove objetos en esta colección.

actualización

Sé que esto es todo una cuestión de estilo, que es objeto de debate, pero realmente creo que la solución GetThings no es la forma correcta de hacer las cosas. La siguiente estrategia, mientras que se necesita un poco más de trabajo, es mucho más de acuerdo con la forma en que las clases y NET marcos estándar están diseñados:

public class ThingHolderDataAccess 
{ 
    public ThingHolder GetThingHolderForSomeArgs(int arg1, int arg2) 
    { 
     var oneThings = GetOneThings(arg1); 
     var otherThings = GetOtherThings(arg2); 
     return new ThingHolder(oneThings, otherThings); 
    } 
    private IEnumerable<OneThing> GetOneThings(int arg) 
    { 
     //... 
     return new List<OneThing>(); 
    } 
    private IEnumerable<AnotherThing> GetOtherThings(int arg2) 
    { 
     //... 
     return new List<AnotherThing>(); 
    } 
} 

public class ThingHolder 
{ 
    public IIndexedReadonlyCollection<OneThing> OneThings 
    { 
     get; 
     private set; 
    } 

    public IIndexedReadonlyCollection<AnotherThing> OtherThings 
    { 
     get; 
     private set; 
    } 

    public ThingHolder(IEnumerable<OneThing> oneThings, 
         IEnumerable<AnotherThing> otherThings) 
    { 
     OneThings = oneThings.ToIndexedReadOnlyCollection(); 
     OtherThings = otherThings.ToIndexedReadOnlyCollection(); 
    } 
} 

#region These classes can be written once, and used everywhere 
public class IndexedCollection<T> 
    : List<T>, IIndexedReadonlyCollection<T> 
{ 
    public IndexedCollection(IEnumerable<T> items) 
     : base(items) 
    { 
    } 
} 

public static class EnumerableExtensions 
{ 
    public static IIndexedReadonlyCollection<T> ToIndexedReadOnlyCollection<T>(
     this IEnumerable<T> items) 
    { 
     return new IndexedCollection<T>(items); 
    } 
} 

public interface IIndexedReadonlyCollection<out T> : IEnumerable<T> 
{ 
    T this[int i] { get; } 
} 
#endregion 

Usando el código anterior podría ser algo como esto:

var things = _thingHolderDataAccess.GetThingHolderForSomeArgs(a, b); 
foreach (var oneThing in things.OneThings) 
{ 
    // do something 
} 
foreach (var anotherThing in things.OtherThings) 
{ 
    // do something else 
} 

var specialThing = things.OneThings[c]; 
// do something to special thing 
+0

gracias por la respuesta bien documentada. Como mi código trata solo con colecciones homogéneas (y está completamente bajo mi control y solo será consumido por código bajo mi control) esto es un poco excesivo para mis necesidades más bien simples. – cori

+0

@cori: Entiendo totalmente. Cuando su código solo se consume localmente, no vale la pena el esfuerzo adicional. – StriplingWarrior

+0

... y en ese caso sugeriría exponer el IList como dice Warren. No veo el sentido de tener un método GetThing. – StriplingWarrior

0

Al igual que las otras respuestas han indicado, es una cuestión de restringir el acceso a la matriz (o lista).

En general, no desea que los clientes de la clase puedan modificar directamente la matriz. Ocultar la implementación de la lista subyacente también le permite cambiar la implementación en el futuro sin afectar ningún código de cliente que use la clase.

Cuestiones relacionadas