¿Es una buena práctica crear una ClassCollection de otra clase?

35

Vamos a decir que tengo una Carclase:

public class Car
{
    public string Engine { get; set; }
    public string Seat { get; set; }
    public string Tires { get; set; }
}

Digamos que estamos haciendo un sistema sobre un estacionamiento, voy a usar una gran parte de la Carclase, por lo que hacemos una CarCollectionclase, puede tener algunos métodos adicionales como FindCarByModel:

public class CarCollection
{
    public List<Car> Cars { get; set; }

    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

Si estoy haciendo una clase ParkingLot, ¿cuál es la mejor práctica?

Opción 1:

public class ParkingLot
{
    public List<Car> Cars { get; set; }
    //some other properties
}

Opcion 2:

public class ParkingLot
{
    public CarCollection Cars { get; set; }
    //some other properties
}

¿Es incluso una buena práctica crear uno ClassCollectionde otro Class?

Luis
fuente
¿Qué beneficio percibe al pasar un en CarCollectionlugar de un List<Car>alrededor? Especialmente dado que CarCollection no extiende la clase de la Lista de respaldo, ni siquiera implementa la interfaz de la Colección (estoy seguro de que C # tiene cosas similares).
List <T> ya implementa IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> e IEnumerable ... Además, podría usar Linq ...
Luis
Pero public class CarCollectionno implementa IList o ICollection, etc., por lo tanto, no puede pasarlo a algo que esté bien con una lista. Afirma como parte de su nombre que es una colección, pero no implementa ninguno de esos métodos.
1
siendo una pregunta de 6 años, veo que nadie ha mencionado que esta es una práctica común en DDD. Cualquier colección debe resumirse en una colección personalizada. Por ejemplo, supongamos que desea calcular el valor de un grupo de automóviles. ¿Dónde pondrías esa lógica? en un servicio? O en DDD tendrías un CarColectioncon una TotalTradeValuepropiedad en él. DDD no es la única forma de diseñar sistemas, solo lo señala como una opción.
Storm Muller
1
Exactamente, estas cosas son en realidad raíces agregadas como se sugiere en DDD, lo único es que DDD probablemente no recomendaría que lo llames una colección de automóviles. Más bien use algunos nombres como estacionamiento, o área de trabajo, etc.
Nombre

Respuestas:

40

Antes de los genéricos en .NET, era una práctica común crear colecciones 'escritas' para que tuviera class CarCollectionetc. para cada tipo que necesitara agrupar. En .NET 2.0 con la introducción de Generics, List<T>se introdujo una nueva clase que le ahorra tener que crear, CarCollectionetc., como puede crear List<Car>.

La mayoría de las veces, encontrará que List<T>es suficiente para sus propósitos, sin embargo, puede haber ocasiones en que desee tener un comportamiento específico en su colección, si cree que este es el caso, tiene un par de opciones:

  • Crear una clase que encapsule List<T>por ejemplopublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Crea una colección personalizada public class CarCollection : CollectionBase<Car> {}

Si opta por el enfoque de encapsulación, como mínimo debe exponer el enumerador para que lo declare de la siguiente manera:

public class CarCollection : IEnumerable<Car>
{
    private List<Car> cars = new List<Car>();

    public IEnumerator<Car> GetEnumerator() { return this.cars.GetEnumerator(); }
}

Sin hacer eso, no puedes hacer una foreachsobre la colección.

Algunas razones por las que puede querer crear una colección personalizada son:

  • No desea exponer completamente todos los métodos en IList<T>oICollection<T>
  • Desea realizar acciones adicionales al agregar o eliminar un elemento de la colección

¿Es una buena práctica? bueno, eso depende de por qué lo está haciendo, si es, por ejemplo, una de las razones que he enumerado anteriormente, entonces sí.

Microsoft lo hace con bastante regularidad, aquí hay algunos ejemplos bastante recientes:

En cuanto a sus FindBymétodos, estaría tentado de ponerlos en métodos de extensión para que puedan usarse contra cualquier colección que contenga automóviles:

public static class CarLookupQueries
{
    public static Car FindByLicencePlate(this IEnumerable<Car> source, string licencePlate)
    {
        return source.SingleOrDefault(c => c.LicencePlate == licencePlate);
    }

    ...
}

Esto separa la preocupación de consultar la colección de la clase que almacena los autos.

Trevor Pilley
fuente
Siguiendo este enfoque, incluso podría descartar ClassCollectionincluso los métodos de agregar, eliminar y actualizar, agregando un nuevo CarCRUDque encapsulará todos estos métodos ...
Luis
@Luis No recomendaría la CarCRUDextensión, ya que forzar su uso sería difícil, la ventaja de poner la lógica cruda personalizada en la clase de colección es que no hay forma de evitarla. Además, es posible que no le importe la lógica de búsqueda en el ensamblado central donde Carse declaran, etc., que podría ser una actividad exclusiva de la IU.
Trevor Pilley
Solo como una nota de MSDN: "No recomendamos que use la clase CollectionBase para un nuevo desarrollo. En cambio, le recomendamos que use la clase genérica Collection <T>". - docs.microsoft.com/en-us/dotnet/api/…
Ryan
9

No. La creación de XXXCollectionclases pasó de moda con la llegada de los genéricos en .NET 2.0. De hecho, existe la ingeniosa Cast<T>()extensión LINQ que la gente usa actualmente para sacar cosas de esos formatos personalizados.

Jesse C. Slicer
fuente
1
¿Qué pasa con los métodos personalizados que podríamos tener dentro de eso ClassCollection? ¿Es una buena práctica ponerlos en la página principal Class?
Luis
3
Creo que cae bajo el mantra de desarrollo de software de "depende". Si está hablando, por ejemplo, de su FindCarByModelmétodo, eso tiene sentido como un método en su repositorio, que es un poco más complejo que solo una Carcolección.
Jesse C. Slicer
2

A menudo es útil tener métodos orientados al dominio para buscar / dividir colecciones, como en el ejemplo de FindByCarModel anterior, pero no hay necesidad de recurrir a la creación de clases de colección de contenedor. En esta situación, normalmente crearé un conjunto de métodos de extensión.

public static class CarExtensions
{
    public static IEnumerable<Car> ByModel(this IEnumerable<Car> cars, string model)
    {
        return cars.Where(car => car.Model == model);
    }
}

Agrega tantos filtros o métodos de utilidad a esa clase como desee, y puede usarlos en cualquier lugar que tenga IEnumerable<Car>, lo que incluye cualquier cosa ICollection<Car>, matrices Car, IList<Car>etc.

Dado que nuestra solución de persistencia tiene un proveedor LINQ, con frecuencia también crearé métodos de filtro similares que operan y regresan IQueryable<T>, para que también podamos aplicar estas operaciones al repositorio.

El idioma de .NET (bueno, C #) ha cambiado mucho desde 1.1. Mantener clases de colección personalizadas es una molestia, y gana poco al heredar de CollectionBase<T>lo que no obtiene con la solución del método de extensión si todo lo que necesita es un filtro específico de dominio y métodos de selección.

Neil Hewitt
fuente
1

Creo que la única razón para crear una clase especial para mantener una colección de otros elementos debería ser cuando se le agrega algo de valor, algo más que simplemente encapsular / heredar de una instancia IListu otro tipo de colección.

Por ejemplo, en su caso, agregue una función que devolvería sublistas de automóviles estacionados en espacios de lotes pares / desiguales ... E incluso entonces ... tal vez solo si se reutiliza a menudo, porque si solo toma una línea con un bonito LinQ funciona y se usa solo una vez, ¿cuál es el punto? BESO !

Ahora, si planea ofrecer muchos métodos de clasificación / búsqueda, entonces sí, creo que esto podría ser útil porque aquí es donde deberían pertenecer, en esa clase de colección especial. Esta también es una buena manera de "ocultar" las complejidades de algunas consultas de "búsqueda" o cualquier cosa que pueda hacer en un método de clasificación / búsqueda.

Jalayn
fuente
Aun así, creo que podría incluir esos métodos en generalClass
Luis
Sí, de hecho ... puedes
Jalayn
-1

Prefiero ir con la siguiente opción, para que pueda agregar su método a la colección y usar la ventaja de la lista.

public class CarCollection:List<Car>
{
    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

y luego puedes usarlo como C # 7.0

public class ParkingLot
{
    public CarCollection Cars { get; set; }=new CarCollection();
    //some other properties
}

O puedes usarlo como

public class ParkingLot
{
   public ParkingLot()
   {
      //initial set
      Cars =new CarCollection();
   }
    public CarCollection Cars { get; set; }
    //some other properties
}

- Versión genérica gracias al comentario de @Bryan

   public class MyCollection<T>:List<T> where T:class,new()
    {
        public T FindOrNew(Predicate<T> predicate)
        {
            // code here
            return Find(predicate)?? new T();
        }
       //Other Common methods
     }

y luego puedes usarlo

public class ParkingLot
{
    public MyCollection<Car> Cars { get; set; }=new MyCollection<Car>();
    public MyCollection<Motor> Motors{ get; set; }=new MyCollection<Motor>();
    public MyCollection<Bike> Bikes{ get; set; }=new MyCollection<Bike>();
    //some other properties
}
Waleed AK
fuente
no heredar de la lista <T>
Bryan Boettcher
@Bryan, tienes razón, la pregunta no es para la colección genérica. Modificaré mi respuesta para la colección genérica.
Waleed AK
1
@WaleedAK todavía lo hiciste, no heredes de la Lista <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Bryan Boettcher el
@Bryan: si lees tu enlace ¿ Cuándo es aceptable? Cuando está creando un mecanismo que extiende el mecanismo Lista <T>. , Así que estará bien siempre que no haya propiedades adicionales
Waleed AK