1410 votes

Pourquoi ne pas hériter de liste<T>?</T>

Lors de la planification de mes programmes, je commence souvent avec une chaîne de pensée de la sorte:

Une équipe de football est juste une liste de joueurs de football. Par conséquent, je dois le représenter avec:

var football_team = new List<FootballPlayer>();

L'ordre de cette liste représentent l'ordre dans lequel les joueurs sont énumérées dans la liste.

Mais je me rends compte plus tard que les équipes ont aussi d'autres propriétés, en plus de la simple liste de joueurs, qui doivent être enregistrés. Par exemple, le total des scores de cette saison, le budget actuel, les couleurs uniformes, string représentant le nom de l'équipe, etc..

Donc je pense que:

Ok, une équipe de football, c'est comme une liste de joueurs, mais en plus, elle a un nom (un string) et un total des scores ( int). .NET ne fournit pas de classe pour le stockage des équipes de football, donc je vais faire ma propre classe. Les plus similaires et pertinents de la structure existante est - List<FootballPlayer>, donc je vais hériter d'elle:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Mais il s'avère qu' une directive de ne pas hériter de la List<T>. Je suis confus par cette directive à deux égards.

Pourquoi pas?

Apparemment List est en quelque sorte optimisé pour les performances. Comment alors? Ce que les problèmes de performance que je ferai si je prolonge List? Ce sera exactement la pause?

Une autre raison pour laquelle je l'ai vu, c'est qu' List est fourni par Microsoft, et je n'ai aucun contrôle dessus, donc je ne peux pas le changer plus tard, après avoir dénoncé une "API publique". Mais j'ai du mal à comprendre cela. Qu'est ce qu'une API publique, et pourquoi devrais-je m'en soucier? Si mon projet actuel ne fonctionne pas et n'est pas susceptible de jamais avoir cette API publique, puis-je ignorer cette ligne directrice? Si je ne hériter d' List et il s'avère que j'ai besoin d'une API publique, quelles difficultés?

Pourquoi est-il le même problème? Une liste est une liste de. Ce qui pourrait peut-être changer? Que pourrais-je peut-être voulez changer?

Et enfin, si Microsoft n'a pas besoin de moi pour hériter d' List, pourquoi n'ont-ils pas faire la classe sealed?

Quoi d'autre dois-je utiliser?

Apparemment, pour des collections personnalisées, Microsoft a fourni un Collection de la classe qui devrait être étendue à la place de List. Mais cette classe est très dépouillé, et n'a pas beaucoup de choses utiles, comme AddRange, par exemple. jvitor83 réponse fournit une performance justification de cette méthode, mais comment est lente, AddRange pas mieux que pas d' AddRange?

Héritant de la Collection , c'est bien plus de travail que d'hériter d' List, et je ne vois aucun bénéfice. Sûrement Microsoft ne me dites pas de faire du travail supplémentaire pour aucune raison, donc je ne peux pas empêcher de se sentir comme je suis en quelque sorte de malentendu quelque chose, et d'hériter Collection est effectivement pas la bonne solution pour mon problème.

J'ai vu des suggestions telles que la mise en œuvre de IList. Tout juste pas de. C'est des dizaines de lignes de code réutilisable, qui y gagne rien moi.

Enfin, certains suggèrent l'emballage de la List quelque chose:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Il y a deux problèmes:

  1. Il rend mon code inutilement verbeux. Je doit maintenant appeler my_team.Players.Count au lieu de simplement en my_team.Count. Heureusement, avec le C#, je peux définir les indexeurs pour faire de l'indexation transparent, et de transmettre toutes les méthodes de l'intérieur de l' List... Mais c'est beaucoup de code! Que dois-je obtenir pour tout ce travail?

  2. Il vient de la plaine n'a pas de sens. Une équipe de football de ne pas "avoir" une liste de joueurs. C' est la liste des joueurs. Vous ne dites pas "Jean McFootballer a rejoint SomeTeam de joueurs". Vous dites "John a rejoint SomeTeam". Vous n'avez pas l'ajout d'une lettre à "une chaîne de caractères", vous ajoutez une lettre d'une chaîne de caractères. Vous n'avez pas ajouter un livre dans une bibliothèque livres, vous ajouter un livre dans une bibliothèque.

Je me rends compte que ce qui se passe "sous le capot" peut être dit d'être "l'ajout de X à Y de la liste interne", mais cela semble être une très contre-intuitif de penser le monde.

Ma question (résumé)

Qu'est-ce que le bon C# manière de représenter une structure de données, qui, "logiquement" (c'est-à-dire, "de l'esprit humain") est juste un list de things avec un peu de cloches et de sifflets?

Est héritant de List<T> toujours inacceptable? Quand est-il acceptable? Pourquoi/pourquoi pas? Que doit faire un programmeur envisager, lors de la décision d'hériter d' List<T> ou pas?

1576voto

Eric Lippert Points 300275

Il y a quelques bonnes réponses ici. Je voudrais ajouter à eux, les points suivants.

Qu'est-ce que le bon C# manière de représenter une structure de données, qui, "logiquement" (c'est-à-dire, "de l'esprit humain") est juste une liste de choses avec un peu de cloches et de sifflets?

Poser toutes les dix non-informatique-programmeur gens qui sont familiers avec l'existence de football pour remplir le vide:

A football team is a particular kind of _____

A quelqu'un dire "liste des joueurs de football avec quelques cloches et de sifflets", ou ont-ils disent tous "sport d'équipe" ou "club" ou "organisation"? Votre notion qu'une équipe de football est un type particulier de la liste des joueurs est dans votre esprit et de votre esprit humain seul.

List<T> est un mécanisme. L'équipe de Football est un business object , -- c'est un objet qui représente un concept qui est dans le domaine d'activité du programme. Ne pas mélanger les! Une équipe de football est un type de l'équipe; il a une liste, une liste est une liste de joueurs. Une liste n'est pas un genre particulier de liste de joueurs. Une liste est une liste de joueurs. Donc, faire une propriété appelée Roster c'est un List<Player>. Et en font ReadOnlyList<Player> pendant que vous y êtes, si vous ne croyez pas que tout le monde qui connaît une équipe de football arrive à supprimer de joueurs.

Est héritant de List<T> toujours inacceptable?

Inacceptable pour qui? Moi? Pas de.

Quand est-il acceptable?

Lorsque vous êtes en train de construire un mécanisme qui s'étend de l' List<T> mécanisme.

Que doit faire un programmeur envisager, lors de la décision d'hériter d' List<T> ou pas?

Suis-je en la construction d'un mécanisme ou d'un objet métier?

Mais c'est beaucoup de code! Que dois-je obtenir pour tout ce travail?

Vous avez passé plus de temps à taper votre question qu'il aurait fallu pour écrire transfert des méthodes pour les membres de l' List<T> cinquante fois. Vous êtes clairement pas peur de la verbosité, et nous parlons d'une très petite quantité de code ici; c'est un travail de quelques minutes.

Mise à JOUR

J'ai plus de pensée, et il y a une autre raison de ne pas le modèle d'une équipe de football comme une liste de joueurs. En fait, il pourrait être une mauvaise idée de modèle à une équipe de football comme avoir une liste de joueurs trop. Le problème avec une équipe/d'avoir une liste de joueurs, c'est que ce que vous avez est un instantané de l'équipe à un moment dans le temps. Je ne sais pas ce que votre entreprise est, pour cette classe, mais si j'avais une classe qui représente une équipe de football je voudrais lui poser des questions comme "combien Seahawks joueurs manqué de jeux en raison d'une blessure entre 2003 et 2013?" ou "Que Denver joueur qui a déjà joué pour une autre équipe avait la plus importante d'une année à l'augmentation de mètres couru?" ou "A la Piggers aller tout le chemin cette année?"

C'est, une équipe de football, me semble être bien modélisé comme une collection de faits historiques comme quand un joueur a été recruté, blessé, à la retraite, etc. Évidemment, l'actuel joueur de la liste est un fait important qui doit probablement être à l'avant-plan, mais il y a peut être d'autres choses intéressantes que vous voulez faire avec cet objet qui nécessitent une plus point de vue historique.

273voto

Simon Whitehead Points 27669

Enfin, certains suggèrent l'emballage de la Liste dans quelque chose:

C'est la façon correcte. "Inutilement verbeux" est une mauvaise façon de voir cela. Il a une signification explicite lorsque vous écrivez my_team.Players.Count. Vous voulez compter les joueurs.

my_team.Count

..ne signifie rien. Le comte?

Votre conception est interrompue lorsque vous voulez hériter d' List. Votre équipe n'est pas une liste.. c'est un enfermant l'entité qui détient d'autres entités. Une équipe possède des joueurs, afin que les joueurs devraient être de la partie (un membre).

Si vous êtes vraiment inquiet à ce sujet étant "inutilement verbeux", vous pouvez toujours exposer les propriétés de l'équipe:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

..qui devient:

my_team.PlayerCount

Cela a un sens maintenant et adhère à La Loi De Déméter.

Vous devriez également envisager d'adhérer à la Composite réutiliser principe. En héritant de List<T>, vous êtes en train de dire qu'une équipe est une liste de joueurs et d'exposer inutile méthodes. Ceci est incorrect, - comme vous l'avez indiqué, une équipe est plus qu'une liste de joueurs: il a un nom, les gestionnaires, les membres du conseil d'administration, des formateurs, du personnel médical, des plafonds salariaux, etc. En ayant votre équipe de classe contiennent une liste de joueurs, vous êtes en train de dire "Une équipe a une liste de joueurs", mais elle peut aussi avoir d'autres choses.

163voto

m-y Points 12871

Wow, votre post a toute une flopée de questions et de points. La plupart du raisonnement que vous obtenez à partir de Microsoft est exactement sur ce point. Commençons avec tout, List<T>

  • List<T> est hautement optimisé. C'est la principale utilisation est d'être utilisé comme un membre privé d'un objet.
  • Microsoft n'a pas sceller, parce que parfois, vous souhaiterez peut-être créer une classe qui a une plus convivial nom: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Maintenant, il est aussi facile que de faire ce var list = new MyList<int, string>();.
  • CA1002: Ne pas exposer générique listes: en gros, même si vous prévoyez d'utiliser cette application comme le seul développeur, il est intéressant de développer, avec les bonnes pratiques de codage, de sorte qu'ils deviennent inculqué et une seconde nature. Vous êtes toujours autorisé à exposer la liste en tant que IList<T> si vous avez besoin d'un consommateur à disposer d'une liste chaînée. Cela vous permet de changer la mise en œuvre au sein d'une classe plus tard.
  • Microsoft a Collection<T> très générique parce que c'est un concept générique... le nom dit tout, c'est juste une collection. Il y a de plus précis versions telles que SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>, etc. chaque de qui mettent en oeuvre IList<T> mais pas List<T>.
  • Collection<T> permet aux membres (Ajouter, Supprimer, etc.) pour être remplacées car elles sont virtuelles. List<T> ne le sont pas.
  • La dernière partie de votre question est sur place. Une équipe de Football est plus que juste une liste de joueurs, donc il devrait y avoir une classe qui contient la liste des joueurs. Pensez Composition vs Héritage. Une équipe de Football a une liste de joueurs (une liste), il n'est pas une liste de joueurs.

Si j'avais écrit ce code, la classe serait probablement ressembler à quelque chose comme ceci:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

153voto

MeNoTalk Points 725
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Code précédent signifie: un tas de gars de la rue, jouer au football, et ils arrivent à avoir un nom. Quelque chose comme:

enter image description here

De toute façon, ce code (à partir de ma réponse)

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
    get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

Moyens: c'est une équipe de football qui, par la direction, les joueurs, les admins, etc. Quelque chose comme:

enter image description here

C'est de cette façon est votre logique présentée dans les photos...

115voto

Tim B Points 19851

C'est un exemple classique de composition vs héritage.

Dans ce cas précis:

L'équipe est une liste de joueurs avec ajout de comportement

ou

L'équipe est un objet qui contient une liste de joueurs.

En étendant la Liste que vous limiter vous-même dans un certain nombre de façons:

  1. Vous ne pouvez pas restreindre l'accès (par exemple, empêcher la modification de la liste). Vous bénéficiez de toutes les Liste de méthodes que vous avez besoin/envie à tout le monde ou pas.

  2. Qu'advient-il si vous voulez avoir des listes de bien d'autres choses. Par exemple, des équipes, des entraîneurs, des gestionnaires, des fans, de l'équipement, etc. Certaines pourraient bien être des listes dans leur propre droit.

  3. Vous limitez vos options pour l'héritage. Par exemple, vous pourriez créer un générique de l'Équipe de l'objet, puis ont BaseballTeam FootballTeam, etc. qui héritent de cette. Pour hériter de la Liste dont vous avez besoin pour faire de l'héritage de l'Équipe, mais cela signifie que tous les différents types de l'équipe sont obligés d'avoir la même mise en œuvre de cette liste.

Composition - y compris un objet donnant le comportement que vous souhaitez à l'intérieur de votre objet.

L'héritage - votre objet est une instance de l'objet qui a le comportement que vous souhaitez.

Les deux ont leur utilité, mais c'est un cas clair où la composition est préférable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X