Ist es eine gute Praxis, eine ClassCollection einer anderen Klasse zu erstellen?

35

Sagen wir, ich habe eine CarKlasse:

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

Nehmen wir an, wir erstellen ein System für einen Parkplatz. Ich werde einen Großteil der CarKlasse verwenden, also CarCollectionerstellen wir eine Klasse. Möglicherweise gibt es einige zusätzliche Methoden wie FindCarByModel:

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

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

Wenn ich eine Klasse mache ParkingLot, was ist die beste Übung?

Option 1:

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

Option 2:

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

Ist es überhaupt eine gute Praxis, ein ClassCollectionanderes zu erschaffen Class?

Luis
quelle
Welchen Nutzen nehmen Sie wahr, wenn Sie CarCollectioneher an einem vorbei gehen als an einem List<Car>herum? Vor allem, weil CarCollection die Backing-List-Klasse nicht erweitert oder sogar die Collection-Schnittstelle implementiert (ich bin mir sicher, dass C # ähnliche Dinge hat).
List <T> implementiert bereits IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> und IEnumerable ... Außerdem könnte ich Linq ...
Luis verwenden
Aber public class CarCollectionnicht implementiert IList oder ICollection, etc ... so kann man es nicht etwas übergeben , die mit einer Liste in Ordnung ist. Es behauptet als Teil seines Namens, dass es eine Sammlung ist, implementiert aber keine dieser Methoden.
1
Da es sich um eine 6-jährige Frage handelt, habe ich gesehen, dass niemand erwähnt hat, dass dies bei DDD eine gängige Praxis ist. Jede Sammlung sollte in eine benutzerdefinierte Sammlung abstrahiert werden. Angenommen, Sie möchten den Wert einer Gruppe von Autos berechnen. Wo würdest du diese Logik hinstellen? in einem Dienst? Oder in DDD hätten Sie eine CarColectionmit einer TotalTradeValueEigenschaft darauf. DDD ist nicht die einzige Möglichkeit, Systeme zu entwerfen, sondern weist lediglich darauf hin, dass dies eine Option ist.
Storm Muller
1
Genau, diese Dinge sind tatsächlich aggregierte Wurzeln, wie in DDD vorgeschlagen. Nur, dass DDD Ihnen wahrscheinlich nicht empfehlen würde, es eine Autosammlung zu nennen. Verwenden Sie lieber einige Namen wie Parkplatz oder Arbeitsbereich usw.
Codename Jack

Antworten:

40

Vor Generics in .NET war es üblich, typisierte Sammlungen zu erstellen, sodass Sie class CarCollectionfür jeden Typ, den Sie gruppieren mussten, usw. haben . In .NET 2.0 wurde mit der Einführung von Generics eine neue Klasse List<T>eingeführt, die es Ihnen erspart, so CarCollectionviel wie möglich zu erstellen List<Car>.

Meistens werden Sie feststellen, dass dies List<T>für Ihre Zwecke ausreicht. Es kann jedoch vorkommen, dass Sie ein bestimmtes Verhalten in Ihrer Sammlung wünschen. Wenn Sie dies für richtig halten, haben Sie mehrere Möglichkeiten:

  • Erstellen Sie eine Klasse, die List<T>beispielsweise kapseltpublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Erstellen Sie eine benutzerdefinierte Sammlung public class CarCollection : CollectionBase<Car> {}

Wenn Sie sich für den Einkapselungsansatz entscheiden, sollten Sie den Enumerator mindestens verfügbar machen, damit Sie ihn wie folgt deklarieren:

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

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

Ohne das können Sie foreachdie Sammlung nicht überschreiben .

Einige Gründe, warum Sie eine benutzerdefinierte Sammlung erstellen möchten, sind:

  • Sie möchten nicht alle Methoden in IList<T>oder vollständig verfügbar machenICollection<T>
  • Sie möchten zusätzliche Aktionen ausführen, wenn Sie ein Element zur Sammlung hinzufügen oder daraus entfernen

Ist es eine gute Übung? Nun, das hängt davon ab, warum Sie es tun. Wenn es zum Beispiel einer der Gründe ist, die ich oben aufgeführt habe, dann ja.

Microsoft macht das ziemlich regelmäßig, hier sind einige relativ neue Beispiele:

Was Ihre FindByMethoden betrifft, wäre ich versucht, sie in Erweiterungsmethoden zu setzen, damit sie für jede Sammlung verwendet werden können, die Autos enthält:

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

    ...
}

Dies trennt das Anliegen, die Sammlung abzufragen, von der Klasse, in der die Autos gespeichert sind.

Trevor Pilley
quelle
Nach diesem Ansatz könnte ich sogar die ClassCollectionMethoden zum Hinzufügen, Löschen und Aktualisieren verwerfen , indem ich eine neue hinzufüge, CarCRUDdie alle diese Methoden zusammenfasst ...
Luis,
@ Luis Ich würde die CarCRUDErweiterung nicht empfehlen, da die Durchsetzung ihrer Verwendung schwierig wäre. Der Vorteil beim Einfügen der benutzerdefinierten Crud-Logik in die Auflistungsklasse besteht darin, dass sie nicht umgangen werden kann. Darüber hinaus interessiert Sie die Suchlogik in der Core-Assembly, in der Caretc deklariert ist, möglicherweise nicht wirklich. Dies ist möglicherweise nur eine Aktivität der Benutzeroberfläche.
Trevor Pilley
Nur als Anmerkung von MSDN: "Wir empfehlen nicht, die CollectionBase-Klasse für Neuentwicklungen zu verwenden. Stattdessen empfehlen wir, die generische Collection <T> -Klasse zu verwenden." - docs.microsoft.com/en-us/dotnet/api/…
Ryan
9

Nein. Die Erstellung von XXXCollectionKlassen geriet mit dem Aufkommen von Generika in .NET 2.0 fast aus der Mode. Tatsächlich gibt es die raffinierte Cast<T>()LINQ-Erweiterung, mit der Leute heutzutage Dinge aus diesen benutzerdefinierten Formaten herausholen.

Jesse C. Slicer
quelle
1
Was ist mit den benutzerdefinierten Methoden, die wir darin haben könnten ClassCollection? Ist es eine gute Praxis, sie auf die Hauptleitung zu setzen Class?
Luis
3
Ich glaube, das fällt unter das Mantra der Softwareentwicklung von "es kommt darauf an". Wenn Sie zum Beispiel über Ihre FindCarByModelMethode sprechen , ist dies als Methode in Ihrem Repository sinnvoll, die etwas komplexer ist als nur eine CarSammlung.
Jesse C. Slicer
2

Wie im obigen FindByCarModel-Beispiel ist es häufig praktisch, domänenorientierte Methoden zum Suchen / Teilen von Sammlungen zu verwenden, aber es ist nicht erforderlich, Wrapper-Auflistungsklassen zu erstellen. In dieser Situation erstelle ich jetzt in der Regel eine Reihe von Erweiterungsmethoden.

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

Sie fügen Sie so viele Filter oder Utility - Methoden dieser Klasse , wie Sie wollen, und Sie können sie überall nutzen zu können muss IEnumerable<Car>, die alles enthält ICollection<Car>, Anordnungen von Car, IList<Car>usw.

Da unsere Persistenzlösung über einen LINQ-Anbieter verfügt, werde ich häufig auch ähnliche Filtermethoden erstellen, die auf IQueryable<T>das Repository angewendet und zurückgegeben werden , damit wir diese Vorgänge auch auf das Repository anwenden können.

Die Sprache von .NET (naja, C #) hat sich seit 1.1 sehr verändert. Das Verwalten von benutzerdefinierten Auflistungsklassen ist mühsam. CollectionBase<T>Wenn Sie nur domänenspezifische Filter- und Auswahlmethoden benötigen, können Sie mit der Erweiterungsmethodenlösung nur wenig von der Vererbung profitieren.

Neil Hewitt
quelle
1

Ich denke, dass der einzige Grund, eine spezielle Klasse für das Speichern einer Sammlung anderer Elemente zu erstellen, darin bestehen sollte, etwas Wertvolles hinzuzufügen, etwas mehr als nur eine Instanz IListeiner Sammlung oder eine andere Art von Sammlung zu kapseln / zu erben .

Beispiel: Fügen Sie in Ihrem Fall eine Funktion hinzu, die Unterlisten von Autos zurückgibt, die auf geraden / ungeraden Stellplätzen geparkt sind. Und selbst dann. Vielleicht nur, wenn sie häufig wiederverwendet werden, weil sie nur eine Zeile mit einem netten LinQ benötigt Funktion und wird nur einmal verwendet, was ist der Sinn? KUSS !

Wenn Sie viele Sortier- / Suchmethoden anbieten möchten, dann denke ich, dass dies nützlich sein könnte, da sie in diese spezielle Sammlungsklasse gehören sollten. Dies ist auch ein guter Weg, um die Komplexität einiger Suchanfragen zu "verbergen" oder was auch immer Sie in einer Sortier- / Suchmethode tun könnten.

Jalayn
quelle
Trotzdem denke ich, dass ich diese Methoden in den Hauptteil aufnehmen könnteClass
Luis,
Ja, in der Tat ... Sie können
Jalayn
-1

Ich bevorzuge die folgende Option, damit Sie Ihre Methode zur Sammlung hinzufügen und den Vorteil von list nutzen können.

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

und dann können Sie es wie C # 7.0 verwenden

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

Oder du kannst es gerne benutzen

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

- Allgemeine Version dank @Bryan Kommentar

   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
     }

und dann kannst du es benutzen

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
quelle
nicht von List <T> erben
Bryan Boettcher
@ Bryan, du hast Recht, die Frage ist nicht für die generische Sammlung. Ich werde meine Antwort für die generische Sammlung ändern.
Waleed AK
1
@WaleedAK du hast es immer noch getan - nicht von List <T> erben: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Bryan Boettcher
@ Bryan: Wenn Sie Ihren Link lesen Wann ist es akzeptabel? Wenn Sie einen Mechanismus erstellen, der den List <T> -Mechanismus erweitert. , So wird es in Ordnung sein, solange es keine zusätzlichen Eigenschaften gibt
Waleed AK