Est-ce une bonne pratique de créer une classe de classe d'une autre classe?

35

Disons que j'ai un Carcours:

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

Disons que nous construisons un système sur un parking, je vais utiliser beaucoup de Carcours, alors nous faisons un CarCollectioncours, il peut avoir quelques méthodes supplémentaires telles que FindCarByModel:

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

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

Si je fais un cours ParkingLot, quelle est la meilleure pratique?

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
}

Est-ce même une bonne pratique de créer un ClassCollectionautre Class?

Luis
la source
Quel bénéfice retirez-vous du fait de passer d'un CarCollectioncercle plutôt que d'un List<Car>cercle? D'autant plus que CarCollection n'allonge pas la classe Liste de sauvegarde, ni n'implémente l'interface Collection (je suis sûr que C # a des choses similaires).
List <T> implémente déjà IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> et IEnumerable ... De plus, je pourrais utiliser Linq ...
Luis
Mais public class CarCollectionn'implémente pas IList ou ICollection, etc ... vous ne pouvez donc pas le transmettre à quelque chose qui convient à une liste. Il affirme dans son nom qu'il s'agit d'une collection, mais n'implémente aucune de ces méthodes.
1
étant une question de 6 ans, je vois que personne n'a mentionné qu'il s'agit d'une pratique courante dans DDD. Toute collection doit être abstraite dans une collection personnalisée. Par exemple, supposons que vous vouliez calculer la valeur d'un groupe de voitures. Où mettriez-vous cette logique? dans un service? Ou dans DDD vous auriez un CarColectionavec une TotalTradeValuepropriété dessus. DDD n'est pas le seul moyen de concevoir des systèmes, il suffit de l'indiquer en option.
Storm Muller
1
Exactement, ces éléments sont en réalité des racines agrégées, comme suggéré dans DDD. Seul le problème, DDD ne vous recommanderait probablement pas d'appeler cela une collection de voitures. Utilisez plutôt des noms tels que parking, ou zone de travail, etc.
Nom de code Jack

Réponses:

40

Avant les génériques dans .NET, il était courant de créer des collections 'dactylographiées' afin que vous ayez class CarCollectionetc pour chaque type que vous aviez besoin de grouper. Dans .NET 2.0, avec l'introduction de Generics, une nouvelle classe a List<T>été introduite, ce qui vous évite d'avoir à créer CarCollectionetc. comme vous pouvez le créer List<Car>.

La plupart du temps, vous constaterez que cela List<T>suffit pour vos besoins, mais il peut arriver que vous souhaitiez avoir un comportement spécifique dans votre collection. Si vous pensez que cela est le cas, vous avez plusieurs options:

  • Créez une classe qui encapsule List<T>par exemplepublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Créer une collection personnalisée public class CarCollection : CollectionBase<Car> {}

Si vous optez pour l’encapsulation, vous devez au moins exposer l’énumérateur afin de le déclarer comme suit:

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

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

Sans cela, vous ne pouvez pas en faire foreachplus sur la collection.

Certaines des raisons pour lesquelles vous pouvez créer une collection personnalisée sont les suivantes:

  • Vous ne voulez pas exposer complètement toutes les méthodes IList<T>ouICollection<T>
  • Vous souhaitez effectuer des actions supplémentaires lors de l'ajout ou de la suppression d'un élément de la collection

Est-ce une bonne pratique? Cela dépend de la raison pour laquelle vous le faites. Si c’est par exemple l’une des raisons que j’ai énumérées ci-dessus, alors oui.

Microsoft le fait assez régulièrement, voici quelques exemples assez récents:

En ce qui concerne vos FindByméthodes, je serais tenté de les mettre en extension afin qu’elles puissent être utilisées contre toute collection contenant des voitures:

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

    ...
}

Ceci sépare le souci d'interroger la collection de la classe qui stocke les voitures.

Trevor Pilley
la source
Suite à cette approche, je pourrais même écarter les ClassCollectionméthodes même pour les méthodes d'ajout, de suppression et de mise à jour, en ajoutant une nouvelle CarCRUDqui encapsulera toutes ces méthodes ...
Luis
@ Luis Je ne recommanderais pas l' CarCRUDextension car il serait difficile d'appliquer son utilisation. L'avantage de mettre la logique crud personnalisée dans la classe de collection est qu'il n'y a aucun moyen de la contourner. En outre, vous ne vous souciez peut-être pas de la logique de recherche dans l'assembly principal dans lequel Caretc sont déclarés, ce qui peut être une activité de l'interface utilisateur uniquement.
Trevor Pilley
Tout comme une note de MSDN: "Nous vous déconseillons d'utiliser la classe CollectionBase pour les nouveaux développements. Nous vous recommandons d'utiliser la classe générique Collection <T>." - docs.microsoft.com/en-us/dotnet/api/…
Ryan
9

Non. La création de XXXCollectionclasses est tombée en désuétude avec l'avènement des génériques dans .NET 2.0. En fait, il existe l' Cast<T>()extension astucieuse LINQ que les gens utilisent de nos jours pour extraire des contenus de ces formats personnalisés.

Jesse C. Slicer
la source
1
Qu'en est-il des méthodes personnalisées que nous pourrions avoir à l'intérieur ClassCollection? Est-ce une bonne pratique de les mettre sur le marché Class?
Luis
3
Je crois que cela tombe sous le mantra du développement logiciel de "ça dépend". Si vous parlez de votre FindCarByModelméthode , par exemple, cela fait sens en tant que méthode sur votre référentiel, ce qui est légèrement plus complexe qu'une simple Carcollection.
Jesse C. Slicer
2

Il est souvent pratique de disposer de méthodes orientées domaine pour rechercher / découper des collections, comme dans l'exemple de FindByCarModel ci-dessus, mais il n'est pas nécessaire de recourir à la création de classes de collection wrapper. Dans cette situation, je vais maintenant généralement créer un ensemble de méthodes d'extension.

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

Vous ajoutez autant de méthodes de filtrage ou d’utilitaires à cette classe que vous le souhaitez, et vous pouvez les utiliser n’importe où vous avez IEnumerable<Car>, y compris tout type de ICollection<Car>tableaux Car, IList<Car>etc.

Étant donné que notre solution de persistance a un fournisseur LINQ, je crée fréquemment des méthodes de filtrage similaires qui fonctionnent sur et qui renvoient IQueryable<T>afin que nous puissions également appliquer ces opérations au référentiel.

L'idiome de .NET (enfin, C #) a beaucoup changé depuis la 1.1. La maintenance de classes de collection personnalisées est une tâche ardue, et vous n’avez que peu à gagner en héritant de CollectionBase<T>la solution de méthode d’extension si vous n’avez besoin que de méthodes de filtre et de sélecteur spécifiques à un domaine.

Neil Hewitt
la source
1

Je pense que la seule raison de créer une classe spéciale pour la conservation d'une collection d'autres éléments doit être ajoutée lorsque vous ajoutez quelque chose de valeur, autre chose que simplement encapsuler / hériter d'une instance IListou d'un autre type de collection.

Par exemple, dans votre cas, ajouter une fonction qui renverrait des sous-listes de voitures garées sur des espaces de lots pairs / irréguliers ... fonction et est utilisé qu'une seule fois, quel est le point? KISS !

Maintenant, si vous envisagez d’offrir beaucoup de méthodes de tri / recherche, alors oui, je pense que cela pourrait être utile car c’est là qu’elles devraient appartenir, dans cette classe de collection spéciale. C'est également un bon moyen de "masquer" la complexité de certaines requêtes "trouver" ou de tout ce que vous pouvez faire avec une méthode de tri / recherche.

Jalayn
la source
Même si, je pense que je pourrais inclure ces méthodes sur le principalClass
Luis
Oui, en effet ... vous pouvez
Jalayn
-1

Je préfère utiliser l'option suivante pour pouvoir ajouter votre méthode à la collection et utiliser l'avantage de la liste.

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

et alors vous pouvez l'utiliser comme C # 7.0

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

Ou vous pouvez l'utiliser comme

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

- Version générique grâce au commentaire @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
     }

et alors vous pouvez l'utiliser

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
la source
ne pas hériter de la liste <T>
Bryan Boettcher
@ Bryan, vous avez raison, la question ne concerne pas la collection générique. Je vais modifier ma réponse pour la collecte générique.
Waleed AK
1
@WaleedAK vous l'avez toujours fait - n'héritez pas de la liste <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Bryan Boettcher Le
@ Bryan: si vous lisez votre lien Quand est-ce acceptable? Lorsque vous créez un mécanisme qui étend le mécanisme List <T>. , Tout ira bien tant qu'il n'y aura pas d'autres propriétés
Waleed AK