É uma boa prática criar um ClassCollection de outra classe?

35

Vamos dizer que eu tenho uma Caraula:

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

Digamos que estamos criando um sistema sobre um estacionamento, vou usar grande parte da Carclasse, então fazemos uma CarCollectionclasse, pode haver alguns métodos adicionais, como FindCarByModel:

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

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

Se estou fazendo uma aula ParkingLot, qual é a melhor prática?

Opção 1:

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

Opção 2:

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

É uma boa prática criar um ClassCollectionde outro Class?

Luis
fonte
Que benefício você percebe ao passar um CarCollectionpouco ao invés de um List<Car>redor? Especialmente porque o CarCollection não estende a classe Backing List nem implementa a interface Collection (tenho certeza que o C # tem coisas semelhantes).
A lista <T> já implementa IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> e IEnumerable ... Além disso, eu poderia usar Linq ...
Luis
Mas public class CarCollectionnão implementa IList ou ICollection, etc ... assim você não pode passá-lo para algo que esteja bem com uma lista. Alega como parte de seu nome que é uma coleção, mas não implementa nenhum desses métodos.
11
sendo uma pergunta de 6 anos, vejo que ninguém mencionou que essa é uma prática comum no DDD. Qualquer coleção deve ser abstraída para uma coleção personalizada. Por exemplo, digamos que você queira calcular o valor de um grupo de carros. Onde você colocaria essa lógica? em um serviço? Ou no DDD você teria um CarColectioncom uma TotalTradeValuepropriedade. O DDD não é a única maneira de projetar sistemas, apenas apontando como uma opção.
Storm Muller
11
Exatamente, essas coisas são na verdade raízes agregadas, como sugerido no DDD. A única coisa que o DDD provavelmente não recomendaria é chamar uma coleção de carros. Em vez disso, use alguns nomes como Estacionamento, Área de Trabalho etc.
Codinome Jack

Respostas:

40

Antes dos genéricos no .NET, era prática comum criar coleções 'digitadas' para que você tivesse class CarCollectionetc para todos os tipos necessários para agrupar. No .NET 2.0 com a introdução de Generics, uma nova classe List<T>foi introduzida, o que evita que você tenha que criar CarCollectionetc, como pode criar List<Car>.

Na maioria das vezes, você acha que isso List<T>é suficiente para seus propósitos; no entanto, pode haver momentos em que você queira ter um comportamento específico em sua coleção. Se você acredita que esse seja o caso, você tem algumas opções:

  • Crie uma classe que encapsule, List<T>por exemplopublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Crie uma coleção personalizada public class CarCollection : CollectionBase<Car> {}

Se você optar pela abordagem de encapsulamento, exponha no mínimo o enumerador para declará-lo da seguinte maneira:

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

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

Sem fazer isso, você não pode fazer um foreachover-the-collection.

Alguns motivos pelos quais você pode querer criar uma coleção personalizada são:

  • Você não deseja expor totalmente todos os métodos IList<T>ouICollection<T>
  • Você deseja executar ações adicionais ao adicionar ou remover um item da coleção

É uma boa prática? bem, isso depende de por que você está fazendo isso, se é por exemplo um dos motivos que eu listei acima, então sim.

A Microsoft faz isso regularmente, aqui estão alguns exemplos bastante recentes:

Quanto aos seus FindBymétodos, eu ficaria tentado a colocá-los em métodos de extensão para que eles possam ser usados ​​contra qualquer coleção que contenha carros:

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

    ...
}

Isso separa a preocupação de consultar a coleção da classe que armazena os carros.

Trevor Pilley
fonte
Seguindo esta abordagem, eu poderia até mesmo demitir o ClassCollectionmesmo para o adicionar, excluir métodos de atualização, adicionando um novo CarCRUDque irá encapsular todos esses métodos ...
Luis
@Luis Eu não recomendaria a CarCRUDextensão, pois exigir o uso seria difícil, a vantagem de colocar a lógica de crud personalizada na classe de coleção é que não há como contorná-la. Além disso, talvez você não se importe com a lógica Localizar no assembly principal em que Caretc são declarados, que pode ser uma atividade apenas da interface do usuário.
Trevor Pilley
Apenas como uma observação do MSDN: "Não recomendamos que você use a classe CollectionBase para novos desenvolvimentos. Em vez disso, recomendamos que você use a classe genérica Collection <T>." - docs.microsoft.com/en-us/dotnet/api/…
Ryan
9

Não. A criação de XXXCollectionclasses praticamente ficou fora de moda com o advento dos genéricos no .NET 2.0. De fato, existe a Cast<T>()extensão LINQ bacana que as pessoas usam hoje em dia para extrair coisas desses formatos personalizados.

Jesse C. Slicer
fonte
11
E os métodos personalizados que poderíamos ter dentro disso ClassCollection? é uma boa prática colocá-los no lugar principal Class?
Luis
3
Eu acredito que isso se enquadra no mantra de desenvolvimento de software de "depende". Se você está falando, por exemplo, do seu FindCarByModelmétodo, isso faz sentido como um método no seu repositório, que é um pouco mais complexo do que apenas uma Carcoleção.
Jesse C. Slicer
2

Muitas vezes, é útil ter métodos orientados a domínio para localizar / cortar coleções, como no exemplo FindByCarModel acima, mas não há necessidade de recorrer à criação de classes de coleção de wrapper. Nesta situação, agora criarei normalmente um conjunto de métodos de extensão.

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

Você adicionar tantos filtro ou utilidade métodos para essa classe como você gosta, e você pode usá-los em qualquer lugar que você tem IEnumerable<Car>, o que inclui qualquer coisa ICollection<Car>, matrizes de Car, IList<Car>etc.

Como nossa solução de persistência possui um provedor LINQ, frequentemente criarei métodos de filtro semelhantes que operam e retornam IQueryable<T>, para que possamos aplicar essas operações ao repositório também.

O idioma do .NET (bem, C #) mudou muito desde a 1.1. Manter as classes de coleção personalizadas é um problema, e você ganha muito pouco com a herança CollectionBase<T>que não obtém com a solução do método de extensão, se tudo que você precisa são métodos de filtro e seletor específicos do domínio.

Neil Hewitt
fonte
1

Eu acho que o único motivo para criar uma classe especial para armazenar uma coleção de outros itens deve ser quando você adiciona algo de valor a ela, algo mais do que apenas encapsular / herdar de uma instância de IListou outro tipo de coleção.

Por exemplo, no seu caso, adicionar uma função que retorne sublistas de carros estacionados em espaços de lotes pares / irregulares ... E mesmo assim ... talvez apenas se for reutilizado com frequência, porque se for preciso apenas uma linha com um LinQ agradável função e é usado apenas uma vez, qual é o objetivo? BEIJO !

Agora, se você planeja oferecer muitos métodos de classificação / localização, sim, acho que isso pode ser útil porque é aqui que eles devem pertencer, nessa classe de coleção especial. Essa também é uma boa maneira de "ocultar" as complexidades de algumas consultas "encontrar" ou o que você poderia fazer em um método de classificação / localização.

Jalayn
fonte
Mesmo tho, eu acho que eu poderia incluir esses métodos no principalClass
Luis
Sim, é verdade ... você pode
Jalayn
-1

Eu prefiro ir com a opção a seguir, para que você possa adicionar seu método à coleção e usar a vantagem da lista.

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

e então você pode usá-lo como C # 7.0

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

Ou você pode usá-lo como

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

- Versão genérica graças ao comentário @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
     }

e então você pode usá-lo

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
fonte
não herda da lista <T>
Bryan Boettcher
@ Bryan, você está certo a pergunta não é para coleta genérica. Modificarei minha resposta para coleta genérica.
Waleed AK
11
@WaleedAK você ainda fez isso - não herdar de List <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Bryan Boettcher
@ Bryan: se você ler o seu link Quando é aceitável? Quando você está criando um mecanismo que estende o mecanismo List <T>. , Por isso, será muito bem contanto que não há propriedades adicionais
Waleed AK