È una buona pratica creare un ClassCollection di un'altra classe?


35

Diciamo che ho una Carlezione:

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

Diciamo che stiamo creando un sistema per un parcheggio, userò molta della Carclasse, quindi creiamo una CarCollectionclasse, potrebbe avere alcuni metodi di aditionals come FindCarByModel:

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

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

Se sto facendo una lezione ParkingLot, qual è la migliore pratica?

Opzione 1:

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

Opzione 2:

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

È anche una buona pratica creare un ClassCollectionaltro Class?


Quale beneficio percepisci nel passare un CarCollectionpiuttosto che un List<Car>giro? Soprattutto dato che CarCollection non estende la classe Backing List, né implementa nemmeno l'interfaccia Collection (sono sicuro che C # abbia cose simili).

Elenco <T> implementa già IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> e IEnumerable ... Inoltre, potrei usare Linq ...
Luis

Ma public class CarCollectionnon implementa IList o ICollection, ecc ... quindi non puoi passarlo a qualcosa che va bene con un elenco. Afferma come parte del suo nome che è una raccolta, ma non implementa nessuno di questi metodi.

1
essendo una domanda di 6 anni, vedo che nessuno ha menzionato che questa è una pratica comune nel DDD. Qualsiasi raccolta deve essere astratta in una raccolta personalizzata. Ad esempio, supponiamo che tu voglia calcolare il valore di un gruppo di automobili. Dove metteresti questa logica? in un servizio? O in DDD avresti un CarColectioncon una TotalTradeValueproprietà su di esso. DDD non è l'unico modo per progettare sistemi, ma solo indicandolo come un'opzione.
Storm Muller,

1
Esattamente, queste cose sono in realtà radici aggregate come suggerito in DDD, L'unica cosa è che DDD probabilmente non ti consiglierebbe di chiamarlo una collezione di auto. Piuttosto usa alcuni nomi come Parcheggio, o Area di lavoro ecc.
Nome in codice Jack

Risposte:


40

Prima dei generici in .NET, era pratica comune creare raccolte "tipizzate" in modo da avere class CarCollectionecc per ogni tipo che era necessario raggruppare. In .NET 2.0 con l'introduzione di Generics, è List<T>stata introdotta una nuova classe che ti consente di evitare di creare CarCollectionecc List<Car>.

La maggior parte delle volte, scoprirai che List<T>è sufficiente per i tuoi scopi, tuttavia ci possono essere momenti in cui desideri avere un comportamento specifico nella tua raccolta, se ritieni che sia così, hai un paio di opzioni:

  • Crea una classe che incapsula List<T>per esempiopublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Crea una raccolta personalizzata public class CarCollection : CollectionBase<Car> {}

Se scegli l'approccio di incapsulamento, dovresti esporre almeno l'enumeratore in modo da dichiararlo come segue:

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

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

Senza farlo, non puoi fare un foreachover the collection.

Alcuni motivi per cui potresti voler creare una raccolta personalizzata sono:

  • Non vuoi esporre completamente tutti i metodi in IList<T>oICollection<T>
  • Desideri eseguire azioni aggiuntive dopo aver aggiunto o rimosso un elemento dalla raccolta

È buona pratica? bene dipende dal motivo per cui lo stai facendo, se è per esempio uno dei motivi che ho elencato sopra, allora sì.

Microsoft lo fa abbastanza regolarmente, ecco alcuni esempi abbastanza recenti:

Per quanto riguarda i tuoi FindBymetodi, sarei tentato di metterli in metodi di estensione in modo che possano essere utilizzati contro qualsiasi raccolta contenente automobili:

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

    ...
}

Ciò separa la preoccupazione di interrogare la collezione dalla classe che immagazzina le auto.


Seguendo questo approccio, potrei persino respingere i ClassCollectionmetodi di aggiunta, eliminazione, aggiornamento, aggiungendo un nuovo CarCRUDche incapsulerà tutti questi metodi ...
Luis

@Luis Non consiglierei l' CarCRUDestensione in quanto il suo utilizzo sarebbe difficile, il vantaggio di mettere la logica crud personalizzata nella classe di raccolta è che non c'è modo di bypassarla. Inoltre, potrebbe non interessarti effettivamente della logica Trova nell'assemblaggio principale in cui Carsono dichiarati etc, che potrebbe essere solo un'attività dell'interfaccia utente.
Trevor Pilley,

Proprio come una nota di MSDN: "Non è consigliabile utilizzare la classe CollectionBase per il nuovo sviluppo. Invece, si consiglia di utilizzare la classe Collection <T> generica." - docs.microsoft.com/en-us/dotnet/api/…
Ryan

9

No. La creazione di XXXCollectionclassi praticamente è passata di moda con l'avvento dei generici in .NET 2.0. In effetti, esiste Cast<T>()un'elegante estensione LINQ che la gente usa in questi giorni per ottenere cose da quei formati personalizzati.


1
Che dire dei metodi personalizzati che potremmo avere al suo interno ClassCollection? è una buona pratica metterli sul principale Class?
Luis

3
Credo che rientri nel mantra dello sviluppo software di "dipende". Se stai parlando, ad esempio, del tuo FindCarByModelmetodo, ha senso come metodo sul tuo repository, che è leggermente più complesso di una semplice Carraccolta.
Jesse C. Slicer,

2

È spesso utile disporre di metodi orientati al dominio per trovare / suddividere le raccolte, come nell'esempio FindByCarModel sopra, ma non è necessario ricorrere alla creazione di classi di raccolte wrapper. In questa situazione, ora in genere creerò una serie di metodi di estensione.

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

Aggiungete tutti i metodi di filtro o utilità a quella classe che volete e potete usarli ovunque possiate IEnumerable<Car>, che include qualsiasi cosa ICollection<Car>, array di Car, IList<Car>ecc.

Poiché la nostra soluzione di persistenza ha un provider LINQ, spesso creerò anche metodi di filtro simili che funzionano e ritornano IQueryable<T>, in modo da poter applicare queste operazioni anche al repository.

Il linguaggio di .NET (beh, C #) è cambiato molto dall'1.1. Mantenere le classi di raccolta personalizzate è una seccatura e dall'eredità CollectionBase<T>non si guadagna molto che non si ottiene con la soluzione del metodo di estensione se tutto ciò di cui si ha bisogno sono i filtri specifici del dominio e i metodi di selezione.


1

Penso che l'unico motivo per creare una classe speciale per contenere una raccolta di altri elementi debba essere quando aggiungi qualcosa di valore ad essa, qualcosa di più che semplicemente incapsulare / ereditare da un'istanza di IListo un altro tipo di raccolta.

Ad esempio, nel tuo caso, aggiungendo una funzione che restituirebbe elenchi di auto parcheggiate su spazi di lotto pari / irregolari ... E anche allora ... forse solo se viene spesso riutilizzato, perché se prende solo una riga con un bel LinQ funzione e viene utilizzata una sola volta, qual è il punto? BACIO !

Ora, se hai intenzione di offrire molti metodi di ordinamento / ricerca, allora sì, penso che potrebbe essere utile perché è lì che dovrebbero appartenere, in quella speciale classe di raccolta. Questo è anche un buon modo per "nascondere" la complessità di alcune query "trova" o qualunque cosa tu possa fare in un metodo di ordinamento / ricerca.


Anche se, penso che potrei includere quei metodi sul principaleClass
Luis

Sì, davvero ... puoi
Jalayn,

-1

Preferisco andare con la seguente opzione, quindi puoi aggiungere il tuo metodo alla raccolta e usare il vantaggio dell'elenco.

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

e quindi puoi usarlo come C # 7.0

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

Oppure puoi usarlo come

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

- Versione generica grazie al commento di @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 poi puoi 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
}

non ereditare dall'elenco <T>
Bryan Boettcher il

@Bryan, hai ragione la domanda non è per la raccolta generica. Modificherò la mia risposta per la raccolta generica.
Waleed AK,

1
@WaleedAK l'hai ancora fatto - non ereditare dall'elenco <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Bryan Boettcher

@Bryan: se leggi il tuo link Quando è accettabile? Quando si crea un meccanismo che estende il meccanismo Elenco <T>. , Quindi andrà bene finché non ci sono proprietà aggiuntive
Waleed AK,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.