Pourquoi ne pas hériter de la Liste?

lors de la planification de mes programmes, je commence souvent avec une chaîne de pensée comme cela:

Une équipe de football est juste une liste de joueurs de football. Par conséquent, je devrais 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 des joueurs, cela doit être enregistré. Par exemple, le total courant des scores cette saison, le budget courant, les couleurs uniformes, un string représentant le nom de l'équipe, etc..

alors je pense:

d'Accord, 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 (un int ). .NET ne fournit pas de classe pour stocker les équipes de football, donc je va faire mon propre classe. La structure existante la plus similaire et la plus pertinente est List<FootballPlayer> , donc j'en hériterai:

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

mais il s'avère que une ligne directrice dit que vous ne devriez pas hériter de List<T> . Je suis complètement confus par cette ligne directrice à deux égards.

pourquoi pas?

apparemment List est en quelque sorte optimisé pour la performance . Comment alors? Quels problèmes de performance vais-je causer si je prolonge List ? Ce sera exactement la pause?

une autre raison que j'ai vu est que List est fourni par Microsoft, et je n'ai aucun contrôle sur elle, donc Je ne peux pas la changer plus tard, après avoir exposé 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 n'a pas et n'est pas susceptible d'avoir jamais cette API publique, puis-je ignorer cette ligne directrice? Si je ne hériter de List et il s'avère que j'ai besoin d'une API publique, quelles difficultés?

quelle importance? Une liste est une liste de. Ce qui pourrait peut-être changer? Que pourrais-je peut-être voulez changer?

et enfin, si Microsoft ne voulait pas que j'hérite de List , pourquoi n'ont-ils pas fait la classe sealed ?

que suis-je d'autre? supposé utiliser?

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

hériter de Collection est beaucoup plus de travail que d'hériter de List , et je ne vois aucun avantage. Sûrement Microsoft ne me dirait pas de faire du travail supplémentaire pour aucune raison, donc je ne peux pas m'empêcher de penser que je suis en quelque sorte malentendu quelque chose, et hériter Collection n'est en fait 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. Ce sont des douzaines de lignes de code boilerplate qui les gains ne m'a rien.

enfin, certains suggèrent d'envelopper le List dans quelque chose:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

il y a deux problèmes avec cela:

  1. il rend mon code inutilement verbeux. Je dois maintenant appeler my_team.Players.Count au lieu de simplement my_team.Count . Heureusement, avec C# je peux définir des indexeurs pour rendre l'indexation transparente, et transmettre toutes les méthodes de l'interne List ... Mais c'est beaucoup de le code! Qu'est-ce que j'obtiens pour tout ce travail?

  2. ça n'a aucun sens. Une équipe de football n'a pas de liste de joueurs. est la liste des joueurs. Vous ne dites pas "John McFootballer a rejoint les joueurs de SomeTeam". Vous dites "John a rejoint SomeTeam". Vous n'ajoutez pas une lettre aux "caractères d'une chaîne", vous ajoutez une lettre à une chaîne. On n'ajoute pas un livre aux livres d'une bibliothèque, on ajoute un livre à une bibliothèque.

je me rends compte que ce qui se passe" sous le capot "peut être considéré comme" ajouter X à la liste interne de Y", mais cela semble être une façon très contre-intuitive de penser au monde.

ma question (résumée)

Quelle est la bonne façon de représenter une structure de données, qui" logiquement "(c'est-à-dire" à l'esprit humain") n'est qu'un list de things avec quelques cloches et des sifflets?

hériter de List<T> est-il toujours inacceptable? Quand est-ce acceptable? Pourquoi/pourquoi pas? Qu'est-ce qu'un programmeur doit prendre en considération, en décidant si hériter de List<T> ou non?

1033
demandé sur Community 0000-00-00 00:00:00
la source

26 ответов

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

Quelle est la façon correcte de représenter une structure de données, qui," logiquement "(c'est-à-dire" à l'esprit humain") est juste une liste de choses avec quelques cloches et sifflets?

demandez à dix personnes qui ne sont pas programmeurs et qui connaissent l'existence du football de remplir le vide:

A football team is a particular kind of _____

A n'importe qui 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 idée qu'une équipe de football est un type particulier de liste de joueurs est dans votre esprit humain et votre esprit humain seul.

List<T> est un mécanisme . Équipe de Football est un objet d'affaires -- qui 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 une sorte de équipe; il a un roster, un roster est une liste de joueurs . Un alignement n'est pas un type particulier de liste de joueurs . Un alignement est une liste de joueurs. Donc faites une propriété appelée Roster qui est un List<Player> . Et faites ReadOnlyList<Player> pendant que vous y êtes, à moins que vous croyez que tous ceux qui connaissent une équipe de football obtient de supprimer les joueurs de la liste.

hériter de List<T> est-il toujours inacceptable?

inacceptable pour qui? Moi? Aucun.

quand est-ce acceptable?

quand vous construisez un mécanisme qui s'étend le mécanisme List<T> .

que doit prendre en considération un programmeur pour décider s'il héritera ou non de List<T> ?

est-ce que je construis un mécanisme ou un objet commercial ?

Mais c'est beaucoup de code! Qu'est-ce que j'obtiens 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 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.

UPDATE

j'y ai réfléchi un peu plus et il y a une autre raison de ne pas modéliser 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 ayant une liste de joueurs aussi. 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 quelle est votre analyse de rentabilisation pour cette classe, mais si j'avais une classe qui représentait une équipe de football, je voudrais lui poser des questions comme "combien de joueurs de Seahawks ont manqué des matchs en raison de blessures entre 2003 et 2013?"ou" quel joueur de Denver qui précédemment joué pour une autre équipe a eu la plus grande augmentation d'une année sur l'autre en verges couru?"ou " est - ce que les Piggers ont fait tout le chemin cette année? "

C'est-à-dire, une équipe de football me semble être bien modelé comme un recueil de faits historiques comme quand un joueur a été recruté, blessé, Retraité, etc. Évidemment, la liste actuelle des joueurs est un fait important qui devrait probablement être front-and-center, mais il peut y avoir d'autres choses intéressantes que vous voulez faire avec cet objet qui exigent une perspective plus historique.

1196
répondu Eric Lippert 2014-02-11 22:05:13
la source

enfin, certains suggèrent d'envelopper la liste dans quelque chose:

C'est la bonne voie. "Inutilement verbeux" est une mauvaise façon de voir cela. Il a un sens explicite quand vous écrivez my_team.Players.Count . Vous voulez compter les joueurs.

my_team.Count

..ne signifie rien. Le comte?

Une équipe n'est pas une liste composée de plus que juste une liste de joueurs. Une équipe possède des joueurs, donc les joueurs devraient être partie (d'un membre).

Si vous êtes vraiment inquiet d'être trop 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 maintenant un sens et adhère à la Loi de Demeter .

Vous devriez également envisager d'adhérer à la Composite réutiliser principe . Par héritant de List<T> , vous dites une équipe est une liste des joueurs et exposant des méthodes inutiles hors de lui. C'est incorrect - comme vous l'avez dit, une équipe est plus qu'une liste de joueurs: elle a un nom, des gestionnaires, des membres du Conseil d'administration, des formateurs, du personnel médical, des plafonds de salaire, etc. En ayant votre classe d'équipe contient une liste de joueurs, vous dites "une équipe a un liste de joueurs", mais il peut également avoir d'autres choses.

242
répondu Simon Whitehead 2017-10-19 01:23:39
la source

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

  • List<T> est hautement optimisé. Son utilisation principale est d'être utilisé comme un membre privé d'un objet.
  • Microsoft ne l'a pas scellé parce que parfois vous pourriez vouloir créer une classe qui a un nom plus amical: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . Maintenant c'est aussi facile que de faire var list = new MyList<int, string>(); .
  • CA1002: N'exposez pas les listes génériques : fondamentalement, même si vous projetez d'utiliser cette application comme le seul développeur, il est intéressant de développer avec de bonnes pratiques de codage, afin qu'ils deviennent inculqués en vous et seconde nature. Vous êtes toujours autorisé à exposer la liste comme IList<T> si vous avez besoin d'un consommateur à disposer d'une liste chaînée. Cela vous permettra de changer l'implémentation dans une classe plus tard sur.
  • Microsoft 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 existe des versions plus précises telles que SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> , etc. chacune d'entre elles mettant en œuvre IList<T> mais et non List<T> .
  • Collection<T> permet les membres (c.-à-d. Ajouter, Supprimer, etc.) pour être remplacé car ils sont virtuels. List<T> ne fonctionne pas.
  • La dernière partie de votre question est sur place. Une équipe de Football est plus qu'une simple liste de joueurs, donc il devrait être une classe qui contient cette liste de joueurs. Pensez Composition vs héritage . Une équipe de Football a une liste de joueurs (une liste), ce n'est pas une liste de joueurs.

si j'écrivais ce code, la classe ressemblerait probablement à ça.:

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.
}
118
répondu m-y 2018-08-21 14:20:14
la source
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

code précédent signifie: un groupe de gars de la rue jouant au football, et il se trouve qu'ils ont un nom. Quelque chose comme:

People playing football

en tout cas, ce code (de la réponse de m-y)

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.
}

signifie: Il s'agit d'une équipe de football dont la direction, les joueurs, les administrateurs, etc. Quelque chose comme:

Image showing members (and other attributes) of the Manchester United team

C'est comme ça votre logique présentée en images...

105
répondu Nean Der Thal 2017-12-30 04:40:50
la source

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

dans ce cas précis:

est l'équipe une liste de joueurs avec un comportement supplémentaire

ou

l'équipe Est un objet qui contient une liste de joueurs.

en étendant la liste, vous vous limitez de plusieurs façons:

  1. Vous ne pouvez pas restreindre l'accès (par exemple, empêcher la modification de la liste). Vous obtenez toutes les méthodes de liste que vous ayez besoin/que vous les vouliez toutes ou non.

  2. que se passe-t-il si vous voulez avoir des listes d'autres choses aussi. Par exemple, les équipes ont des entraîneurs, des gestionnaires, des partisans, de l'équipement, etc. Certaines pourraient bien être des listes dans leur propre droit.

  3. vous limitez votre options pour l'héritage. Par exemple, vous pourriez vouloir créer un objet générique D'équipe, puis avoir BaseballTeam, FootballTeam, etc. qui héritent de cette. Pour hériter de la liste, vous devez faire l'héritage de L'équipe, mais cela signifie Alors que tous les différents types d'équipe sont forcés d'avoir la même mise en œuvre de cette liste.

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

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

ont tous deux leurs usages, mais c'est un cas clair où la composition est préférable.

84
répondu Tim B 2014-03-08 15:02:47
la source

Comme tout le monde l'a souligné, une équipe de joueurs n'est pas une liste de joueurs. Cette erreur est commise par de nombreuses personnes partout dans le monde, peut-être à divers niveaux d'expertise. Souvent le problème est subtil et parfois très grossier, comme dans ce cas. De telles conceptions sont mauvaises parce qu'elles violent le principe de substitution de Liskov . L'internet contient de nombreux bons articles expliquant ce concept, par exemple http://en.wikipedia.org/wiki/Liskov_substitution_principle

en résumé, il y a deux règles à préserver dans une relation Parent/enfant entre les classes:

  • un enfant ne devrait exiger aucune caractéristique inférieure à ce qui définit complètement le Parent.
  • un Parent ne devrait exiger aucune caractéristique en plus de ce qui définit complètement l'enfant.

En d'autres termes, un Parent est nécessaire définition d'un enfant, et un enfant est une définition suffisante d'un Parent.

Voici une façon de penser à travers sa solution et d'appliquer le principe ci-dessus qui devrait aider à éviter une telle erreur. Il faut tester ses hypothèses en vérifiant si toutes les opérations d'une classe mère sont valables pour la classe dérivée à la fois structurellement et sémantiquement.

  • est-ce qu'une équipe de football est une liste de joueurs de football? ( Faire toutes les propriétés d'une liste s'appliquent à une équipe dans le même sens)
    • est-ce qu'une équipe est un ensemble d'entités homogènes? Oui, l'équipe est une collection de joueurs
    • Est de l'ordre de l'inclusion de joueurs descriptive de l'état de l'équipe et de l'équipe de s'assurer que la séquence est conservée, sauf explicitement changé? Non, et non
    • est-ce que les joueurs devraient être inclus / éliminés en fonction de leur position séquentielle dans l'équipe? N ° 1519100920"

Comme voyez-vous, seulement la première caractéristique d'une liste est applicable à une équipe. Une équipe n'est donc pas une liste. Une liste serait un détail d'implémentation de la façon dont vous gérez votre équipe, donc elle ne devrait être utilisée que pour stocker les objets player et être manipulée avec les méthodes de la classe Team.

à ce stade, je voudrais faire remarquer qu'une classe Team ne devrait même pas, à mon avis, être implémentée en utilisant une liste; elle devrait être implémentée en utilisant une structure de données établie (HashSet, par exemple) dans la plupart des cas. cas.

46
répondu Satyan Raina 2014-02-11 21:05:34
la source

et si le FootballTeam avait une équipe de réserve avec l'équipe principale?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

comment modéliser ça?

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

la relation est clairement a un et non est un .

ou RetiredPlayers ?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

en règle générale, si vous voulez hériter d'une collection, nommez la classe SomethingCollection .

est-ce que votre SomethingCollection a un sens sémantique? Ne faites ceci que si votre type est une collection de Something .

dans le cas de FootballTeam ça ne sonne pas bien. Un Team est plus qu'un Collection . Un Team peut avoir des entraîneurs, des formateurs, etc. comme les autres réponses l'ont souligné.

FootballCollection sonne comme une collection de ballons ou peut-être une collection de football tout l'attirail. TeamCollection , une collection d'équipes.

FootballPlayerCollection sonne comme une collection de joueurs qui serait un nom valide pour une classe qui hérite de List<FootballPlayer> si vous vouliez vraiment faire cela.

Vraiment List<FootballPlayer> est une très bonne type à traiter. Peut-être IList<FootballPlayer> si vous le retournez d'une méthode.

en résumé

Demander vous-même

  1. est-ce que X a Y ? ou A X un Y ?

  2. est-ce que mes noms de classe signifient ce qu'ils sont?

39
répondu Sam Leach 2014-04-03 14:59:59
la source

tout d'abord, il a à voir avec la convivialité. Si vous utilisez l'héritage, la classe Team exposera le comportement (les méthodes) qui sont conçus purement pour la manipulation d'objet. Par exemple, les méthodes AsReadOnly() ou CopyTo(obj) n'ont aucun sens pour l'objet team. Au lieu de la méthode AddRange(items) vous voudriez probablement une méthode plus descriptive AddPlayers(players) .

si vous voulez utiliser LINQ, implémenter une interface générique telle que ICollection<T> ou IEnumerable<T> aurait plus de sens.

comme mentionné, la composition est la bonne façon de procéder. Il suffit d'implémenter une liste de joueurs comme variable privée.

29
répondu Dmitry S. 2014-02-11 07:25:28
la source

Conception > Mise En Œuvre

quelles méthodes et propriétés Vous exposez est une décision de conception. La classe de base dont vous héritez est un détail d'implémentation. Je pense que ça vaut la peine de revenir à la première.

un objet est une collection de données et de comportement.

donc vos premières questions devraient être:

  • quelles données Cet objet contient-il dans le modèle que je crée?
  • quel comportement cet objet présente-t-il dans ce modèle?
  • comment ce changement pourrait-il se produire à l'avenir?

gardez à l'esprit que l'héritage implique une relation "isa" (est a), alors que la composition implique une relation "Hasa" (hasa). À votre avis, choisissez celle qui convient le mieux à votre situation, en tenant compte de l'évolution possible de votre demande.

réfléchissez aux interfaces avant de penser types concrets, comme certaines personnes trouvent plus facile de mettre leur cerveau en" mode de conception " de cette façon.

ce n'est pas quelque chose que tout le monde fait consciemment à ce niveau dans le codage quotidien. Mais si vous réfléchissez à ce genre de sujet, vous marchez dans les eaux du design. Être conscient de cela peut être libérateur.

Tenir Compte Des Caractéristiques De Conception

regardez la liste et IList sur MSDN ou Visual Studio. Voir quelles méthodes et propriétés ils exposer. Est-ce que toutes ces méthodes ressemblent à quelque chose que quelqu'un voudrait faire à une équipe de football à votre avis?

fait équipe de football.Reverse() du sens pour vous? Ne footballTeam.ConvertAll() de ressembler à quelque chose que vous voulez?

Ce n'est pas une question piège; la réponse pourrait véritablement être "oui". Si vous implémentez/héritez la liste ou IList, vous êtes coincé avec eux; si c'est idéal pour votre modèle, faites-le.

si vous décidez Oui, cela a du sens, et vous voulez que votre objet soit traitable comme une collection/Liste de joueurs (comportement), et vous voulez donc mettre en œuvre ICollection ou IList, par tous les moyens le faire. Notionnel:

class FootballTeam : ... ICollection<Player>
{
    ...
}

si vous voulez que votre objet contienne une collection/Liste de lecteurs (données), et donc que la collection ou la liste soit une propriété ou un membre, faites-le par tous les moyens. Notionnel:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

vous pourriez sentir que vous voulez les gens pour être en mesure d'énumérer seulement l'ensemble des joueurs, plutôt que de les Compter, ajouter à eux ou les supprimer. IEnumerable est une option parfaitement valable à considérer.

vous pourriez penser qu'aucune de ces interfaces n'est utile dans votre modèle. C'est moins probable (IEnumerable est utile dans de nombreuses situations) mais c'est encore possible.

quiconque tente de vous dire que l'un d'eux est catégoriquement et définitivement erroné dans tous les cas est malavisé. Quiconque tente de vous dire qu'il est catégoriquement et définitivement droit dans tous les cas est malavisé.

passer à la mise en œuvre

une fois que vous avez décidé sur les données et le comportement, vous pouvez prendre une décision sur la mise en œuvre. Cela inclut les classes de béton dont vous dépendez par héritage ou composition.

ce n'est peut-être pas un grand pas, et les gens souvent la conception et la mise en œuvre de conflate puisqu'il est tout à fait possible de courir à travers tout dans votre tête dans une seconde ou deux et commencer à taper à distance.

Une Expérience De La Pensée

Un exemple artificiel: comme d'autres l'ont mentionné, une équipe n'est pas toujours "juste" une collection de joueurs. Conservez-vous une collection de résultats de match pour l'équipe? L'équipe est interchangeable avec le club, dans votre modèle? Si oui, et si votre équipe est une collection de joueurs, peut-être il est également une collecte de personnel et/ou une collecte de partitions. Puis on finit avec:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

nonobstant la conception, à ce point dans C# vous ne serez pas en mesure de mettre en œuvre tous ceux-ci en héritant de la liste de toute façon, puisque C# "seulement" soutient l'héritage unique. (Si vous avez essayé ce malarky en C++, vous pouvez considérer cela comme une bonne chose.) La mise en œuvre d'une collecte par héritage et d'une par composition est susceptible de se sentir sale. Et les propriétés tel que Count devient déroutant pour les utilisateurs à moins que vous implémentiez ILIst.Le comte et IList.Le comte etc. explicitement, et ensuite ils sont juste douloureux plutôt que confus. Vous pouvez voir où cela nous mène; le pressentiment tout en pensant à cette avenue pourrait bien vous dire qu'il se sent mal d'aller dans cette direction (et à tort ou à raison, vos collègues pourraient aussi si vous le mettez en œuvre de cette façon!)

La Réponse Courte (Trop Tard)

la ligne directrice ne pas hériter des cours de collection n'est pas spécifique à C#, vous le trouverez dans de nombreux langages de programmation. C'est une sagesse reçue, pas une loi. L'une des raisons est que, dans la pratique, la composition est souvent considérée comme gagnant sur l'héritage en termes de compréhensibilité, d'applicabilité et de maintenabilité. Il est plus commun avec le monde réel / les objets de domaine pour trouver utiles et cohérentes "hasa" relations que utiles et cohérentes " isa " relations à moins que vous êtes profond dans l'abstrait, la plupart surtout au fur et à mesure que le temps passe et que les données précises et le comportement des objets dans le code changent. Cela ne devrait pas vous amener à toujours exclure l'héritage des classes de collecte; mais il peut être suggestif.

27
répondu El Zorko 2014-02-12 02:27:53
la source

Une équipe de football n'est pas une liste de joueurs de football. Une équipe de football est composée de une liste des joueurs de football!

c'est logiquement faux:

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

et ceci est correct:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}
24
répondu sam 2014-02-11 20:25:06
la source

cela dépend du contexte

quand vous considérez votre équipe comme une liste de joueurs, vous projetez l '" idée "d'une équipe de foot ball vers un aspect: vous réduisez l '"équipe" aux gens que vous voyez sur le terrain. Cette projection n'est correcte que dans un certain contexte. Dans un contexte différent, cela pourrait être complètement faux. Imaginez que vous voulez devenir un commanditaire de l'équipe. Vous devez donc parler aux responsables de l'équipe. Dans ce contexte, l'équipe est projetée sur le la liste de ses dirigeants. Et ces deux listes ne se recoupent généralement pas beaucoup. D'autres contextes sont le courant contre les anciens joueurs, etc.

sémantique imprécise

donc le problème quand on considère une équipe comme une liste de ses joueurs est que sa sémantique dépend du contexte et qu'elle ne peut pas être étendue quand le contexte change. De plus, il est difficile d'exprimer le contexte que vous utilisez.

Les Classes

sont extensibles

lorsque vous utilisez une classe avec un seul membre (par exemple IList activePlayers ), vous pouvez utiliser le nom du membre (et en plus son commentaire) pour rendre le contexte clair. Quand il y a des contextes supplémentaires, vous ajoutez juste un membre supplémentaire.

les Classes sont plus complexes

Dans certains cas, il peut être exagéré pour créer une classe supplémentaire. Chaque définition de classe doit être chargée par le classloader et sera mise en cache par le virtual machine. Cela vous coûte les performances d'exécution et la mémoire. Lorsque vous avez un contexte très spécifique, il peut être correct de considérer une équipe de football comme une liste de joueurs. Mais dans ce cas, vous devriez vraiment utiliser un IList , et non pas une classe dérivée.

Conclusion / Considérations

Quand vous avez un contexte très spécifique, il est correct de considérer l'équipe comme une liste de joueurs. Par exemple, à l'intérieur d'une méthode, il est tout à fait correct d'écrire

IList<Player> footballTeam = ...

en utilisant F#, il peut même être correct de créer un type abréviation

type FootballTeam = IList<Player>

mais quand le contexte est plus large ou même pas clair, vous ne devriez pas faire cela. C'est notamment le cas, lorsque vous créez une nouvelle classe, où il n'est pas clair dans quel contexte il peut être utilisé dans l'avenir. Un signe d'avertissement, c'est quand vous commencez à ajouter des attributs supplémentaires à votre classe (nom de l'équipe, coach, etc.). C'est un signe clair que le contexte où la classe sera utilisé n'est pas fixe et qui va changer dans l'avenir. Dans ce cas, vous ne pouvez pas considérer l'équipe comme une liste de joueurs, mais vous devriez modéliser la liste des (actuellement actif, pas blessé, etc. des joueurs comme un attribut de l'équipe.

20
répondu stefan.schwetschke 2014-02-12 20:57:31
la source

laissez-moi réécrire votre question. donc, vous pourriez voir le sujet d'un point de vue différent.

quand j'ai besoin de représenter une équipe de football, je comprends que c'est essentiellement un nom. Comme:" Les Aigles "

string team = new string();

plus tard, j'ai réalisé que les équipes avaient aussi des joueurs.

pourquoi ne puis-je pas simplement étendre le type de chaîne pour qu'il contienne aussi une liste de joueurs?

Votre point d'entrée dans le problème est arbitraire. Essayez de penser ce que fait une équipe ont (Propriétés), pas ce que est .

après cela, vous pourriez voir s'il partage des biens avec d'autres catégories. Et de penser à propos de l'héritage.

19
répondu user1852503 2014-02-17 05:09:25
la source

juste parce que je pense que les autres réponses vont à peu près sur une tangente de savoir si une équipe de football "est-a" List<FootballPlayer> ou "a-a" List<FootballPlayer> , ce qui ne répond vraiment pas à cette question telle qu'elle est écrite.

l'OP demande principalement des clarifications sur les lignes directrices pour hériter de List<T> :

une directive dit que vous ne devriez pas hériter de List<T> . Pourquoi pas?

Parce que List<T> n'a pas de méthodes virtuelles. Cela pose moins de problèmes dans votre propre code, puisque vous pouvez généralement désactiver l'implémentation avec relativement peu de douleur - mais cela peut être beaucoup plus important dans une API publique.

Qu'est-ce qu'une API publique et pourquoi devrais-je m'en soucier?

une API publique est une interface que vous exposez à des programmeurs tiers. Pensez au Code-cadre. Et de rappeler que les directives citées sont la ".NET Cadre Design Guidelines" et pas le ".NET Application Design Guidelines". Il y a une différence, et - en général - la conception de L'API publique est beaucoup plus stricte.

si mon projet actuel n'a pas et n'est pas susceptible d'avoir jamais cette API publique, puis-je ignorer en toute sécurité cette ligne directrice? Si J'hérite de List et qu'il s'avère que j'ai besoin d'une API publique, quelles difficultés aurai-je?

à peu près, oui. Vous voudrez peut-être considérer la raison d'être derrière cela pour voir si cela s'applique à votre situation de toute façon, mais si vous n'êtes pas en train de construire une API publique, alors vous n'avez pas particulièrement besoin de vous soucier des problèmes D'API comme le versioning (dont, c'est un sous-ensemble).

si vous ajoutez une API publique dans le futur, vous devrez soit faire abstraction de votre API de votre implémentation (en n'exposant pas votre List<T> directement) ou violez les lignes directrices avec la douleur future possible qui implique.

quelle importance? Une liste est une liste de. Ce qui pourrait peut-être changer? Que pourrais-je peut-être voulez changer?

dépend du contexte, mais puisque nous utilisons FootballTeam comme exemple - imaginez que vous ne pouvez pas ajouter un FootballPlayer si cela pousserait l'équipe à dépasser le plafond salarial. Une façon d'ajouter que ce serait quelque chose comme:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...mais vous ne pouvez pas override Add parce que ce n'est pas virtual (pour des raisons de performance).

si vous êtes dans une application (ce qui, fondamentalement, signifie que vous et tous vos appelants sont compilés ensemble), alors vous pouvez maintenant passer à l'aide de IList<T> et corriger les erreurs de compilation:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

mais, si vous avez publiquement exposé à un tiers vous venez de faire un changement de rupture qui causera compiler et/ou des erreurs d'exécution.

TL; DR - les lignes directrices s'appliquent aux IPA publics. Pour le soldat APIs, faites ce que vous voulez.

15
répondu Mark Brackett 2015-04-07 00:35:20
la source

permet aux gens de dire

myTeam.subList(3, 5);

un sens? Sinon, ça ne devrait pas être une liste.

14
répondu Cruncher 2014-02-12 01:42:45
la source

cela dépend du comportement de votre objet" team". Si elle se comporte comme une collection, il pourrait être correct de la représenter d'abord avec une liste simple. Alors vous pourriez commencer à remarquer que vous continuez à dupliquer du code qui itère sur la liste; à ce point vous avez l'option de créer un objet FootballTeam qui enveloppe la liste des joueurs. La classe FootballTeam devient la maison pour tout le code qui itère sur la liste des joueurs.

ça rend mon code inutilement verbeux. Je dois maintenant appeler my_team.Joueur.Le comte, au lieu de juste my_team.Compter. Heureusement, avec C# je peux définir des indexeurs pour rendre l'indexation transparente, et transmettre toutes les méthodes de la liste interne... Mais ça fait beaucoup de code! Qu'est-ce que j'obtiens pour tout ce travail?

Encapsulation. Vos clients n'ont pas besoin de savoir ce qui se passe à L'intérieur de L'équipe de football. Pour tous vos clients savent, il pourrait être mis en œuvre en regardant la liste de joueurs dans une base de données. Ils n'ont pas besoin de savoir, ce qui améliore votre conception.

ça n'a aucun sens. Une équipe de football n'a pas de liste de joueurs. Il s'agit de la liste de joueurs. Vous ne dites pas "John McFootballer a rejoint les joueurs de SomeTeam". Vous dites "John a rejoint SomeTeam". Vous n'ajoutez pas une lettre aux "caractères d'une chaîne", vous ajoutez une lettre à une chaîne. On n'ajoute pas un livre aux livres d'une bibliothèque, on ajoute un livre à une bibliothèque.

exactement :) vous direz équipe de football.Ajouter(jean), pas footballTeam.Liste.Ajouter(jean). La liste interne ne sera pas visible.

13
répondu xpmatteo 2014-02-11 20:51:21
la source

il y a beaucoup d'excellentes réponses ici, mais je veux parler de quelque chose que je n'ai pas vu mentionné: design orienté objet est d'environ habiliter les objets .

vous voulez encapsuler toutes vos règles, travaux supplémentaires et détails internes à l'intérieur d'un objet approprié. De cette façon, les autres objets interagissant avec celui-ci n'ont pas à s'inquiéter de tout cela. En fait, si vous voulez aller plus loin et activement prévenir autres objets de contourner ces mécanismes internes.

lorsque vous héritez de List , tous les autres objets peuvent vous voir comme une liste. Ils ont un accès direct aux méthodes d'ajout et de suppression de joueurs. Et vous aurez perdu votre contrôle; par exemple:

supposez que vous voulez différencier quand un joueur quitte en sachant s'ils ont pris leur retraite, démissionné ou ont été licenciés. Vous pouvez implémenter une méthode RemovePlayer qui prend une entrée appropriée enum. Cependant, en héritant de List , vous ne pourrez pas empêcher l'accès direct à Remove , RemoveAll et même Clear . En conséquence, vous avez en fait " désemparé votre classe FootballTeam .


réflexions Complémentaires sur l'encapsulation... Vous avez soulevé la question suivante:

ça fait mon code inutilement verbeux. Je dois maintenant appeler my_team.Joueur.Le comte, au lieu de juste my_team.Compter.

vous avez raison, ce serait inutilement verbeux pour tous les clients de vous utiliser équipe. Cependant, ce problème est très petit en comparaison du fait que vous avez exposé List Players à tous et divers afin qu'ils puissent jouer avec votre équipe sans votre consentement.

vous continuez à dire:

Il vient de la plaine n'a pas de sens. Une équipe de football n'a pas de liste de joueurs. Il s'agit de la liste de joueurs. Vous ne dites pas "John McFootballer a rejoint les joueurs de SomeTeam". Vous dites "John a rejoint SomeTeam".

vous vous trompez sur le premier point: supprimez le mot "liste", et il est en fait évident qu'une équipe a des joueurs.

Cependant, vous frappez le clou sur la tête avec la seconde. Vous ne voulez pas que les clients appellent ateam.Players.Add(...) . Vous n' je veux qu'ils appellent ateam.AddPlayer(...) . Et votre mise en œuvre pourrait (entre autres choses) appeler Players.Add(...) en interne.


avec un peu de chance, vous pouvez voir à quel point l'encapsulation est importante pour l'objectif d'habiliter vos objets. Vous voulez permettre à chaque classe de bien faire son travail sans crainte d'interférence d'autres objets.

11
répondu Disillusioned 2014-02-16 00:05:59
la source

Qu'est-ce que le correct c# façon de représenter une structure de données...

Remeber, " tous les modèles sont faux, mais certains sont utiles."- George E. P. Box

il n'y a pas de" voie correcte", seulement une voie utile.

Choisissez une option qui vous sera utile et/ou qui sera utile à vos utilisateurs. C'est tout. développer économiquement, ne pas trop ingénieurs. Le moins de code vous écrire le moins de code, vous aurez besoin de déboguer. (lire les éditions suivantes).

-- de l'ouvrage

Ma meilleure réponse serait... il dépend. Hériter d'une liste exposerait les clients de cette catégorie à des méthodes qui ne devraient pas être exposées, principalement parce que FootballTeam ressemble à une entité commerciale.

-- Édition 2

Je ne me souviens vraiment pas de ce que je faisais référence sur le " don't plus-ingénierie", commentaire. Bien que je crois que le KISS mindset est un bon guide, je tiens à souligner que l'héritage d'une classe d'affaires de la liste créerait plus de problèmes qu'il ne résout, en raison de fuite d'abstraction .

d'un autre côté, je crois qu'il y a un nombre limité de cas où le simple fait d'hériter de List est utile. Comme je l'ai écrit dans l'édition précédente, il dépend. La réponse à chaque cas, est fortement influencée par connaissance, expérience et préférences personnelles.

merci à @kai de m'avoir aidé à réfléchir plus précisément à la réponse.

11
répondu Arturo Tena 2015-09-16 01:36:20
la source

Cela me rappelle le "un" et "a un" compromis. Il est parfois plus facile et plus sensé d'hériter directement d'une super-classe. D'autres fois, il est plus logique de créer une classe autonome et d'inclure la classe dont vous auriez hérité comme variable membre. Vous pouvez toujours accéder à la fonctionnalité de la classe mais n'êtes pas lié à l'interface ou à toute autre contrainte qui pourrait provenir de l'héritage de la classe.

Que faites-vous? Comme avec beaucoup de choses...ça dépend du contexte. Le guide que j'utiliserais est que pour hériter d'une autre classe il devrait vraiment y avoir une relation "est une". Donc, si vous écrivez une classe appelée BMW, il pourrait hériter de la voiture parce qu'une BMW est vraiment une voiture. Une classe de chevaux peut hériter de la classe de mammifères parce qu'un cheval est en fait un mammifère dans la vie réelle et toute fonctionnalité de mammifère devrait être pertinente pour le cheval. Mais pouvez-vous dire que l'équipe est une liste? À partir de ce que je peux dire, il n'a pas on dirait qu'une équipe est vraiment "une" liste. Donc, dans ce cas, j'aurais une liste comme variable membre.

10
répondu Paul J Abernathy 2014-02-11 18:56:27
la source

mon sale secret: je me fiche de ce que les gens disent, et je le fais. .net Framework est étalé avec "XxxxCollection" (UIElementCollection pour top of my head example).

alors ce qui m'arrête de dire:

team.Players.ByName("Nicolas")

quand je le trouve meilleur que

team.ByName("Nicolas")

de plus, ma PlayerCollection pourrait être utilisée par d'autres classes, comme" Club " sans aucune duplication de code.

club.Players.ByName("Nicolas")

les Meilleures pratiques de hier, peut-être pas celle de demain. Il n'y a aucune raison derrière la plupart des meilleures pratiques, la plupart ne font l'objet que d'un large consensus au sein de la communauté. Au lieu de demander à la communauté si elle ne vous blâme quand vous ne que vous demandez, c'est plus lisible et maintenable?

team.Players.ByName("Nicolas") 

ou

team.ByName("Nicolas")

vraiment. Avez-vous des doutes? Maintenant peut-être que vous avez besoin de jouer avec d'autres contraintes techniques qui vous empêchent d'utiliser List dans votre cas d'utilisation réelle. Mais n'ajoutez pas une contrainte qui ne devrait pas exister. Si Microsoft n'a pas documenté le pourquoi, alors il s'agit certainement d'une "meilleure pratique" venant de nulle part.

8
répondu Nicolas Dorier 2014-02-17 05:11:59
la source

ce que les lignes directrices disent, c'est que l'API publique ne devrait pas révéler la décision de conception interne de savoir si vous utilisez une liste, un ensemble, un dictionnaire, un arbre ou quoi que ce soit. Une "équipe" n'est pas nécessairement une liste. Vous pouvez l'implémenter sous forme de liste, mais les utilisateurs de votre API publique devraient utiliser votre classe sur la base du besoin de savoir. Cela vous permet de modifier votre décision et d'utiliser une structure de données différente sans affecter l'interface publique.

8
répondu QuentinUK 2014-02-18 06:53:22
la source

Quand ils disent List<T> est "optimisé" je pense qu'ils veulent dire qu'il n'a pas de caractéristiques comme des méthodes virtuelles qui sont peu plus cher. Donc le problème est qu'une fois que vous exposez List<T> dans votre API publique , vous perdez la capacité d'appliquer les règles d'affaires ou de personnaliser ses fonctionnalités plus tard. Mais si vous utilisez cette classe héritée comme interne dans votre projet (par opposition à potentiellement exposé à des milliers de vos clients/partenaires / autres équipes en tant QU'API) alors, il peut être OK si elle sauve votre temps et il est la fonctionnalité que vous voulez dupliquer. L'avantage d'hériter de List<T> est que vous éliminez beaucoup de code d'emballage stupide qui ne sera jamais personnalisé dans un avenir prévisible. En outre, si vous voulez que votre classe ait explicitement la même sémantique que List<T> pour la vie de vos API alors aussi il peut être OK.

je vois souvent beaucoup de gens faire des tonnes de le travail supplémentaire juste à cause de la règle FxCop le dit ou le blog de quelqu'un dit que c'est une "mauvaise" pratique. Plusieurs fois, cela tourne le code dans le motif palooza bizarre motif. Comme pour le lot de ligne directrice, traitez-le comme ligne directrice que peut ont des exceptions.

5
répondu ShitalShah 2014-02-16 11:50:31
la source

Si vos utilisateurs ont besoin de toutes les méthodes et propriétés** Liste a, vous devez tirer votre classe. S'ils n'en ont pas besoin, joignez la liste et faites des wrappers pour les méthodes dont les utilisateurs de votre classe ont réellement besoin.

C'est une règle stricte, si vous écrivez un API publique , ou tout autre code qui sera utilisé par de nombreuses personnes. Vous pouvez ignorer cette règle, si vous avez une application minuscule et pas plus de 2 développeurs. Cela permettra d'économiser un peu de temps.

pour les applications minuscules, vous pouvez également envisager de choisir un autre, moins strict langage. Ruby, JavaScript-tout ce qui vous permet d'écrire moins de code.

3
répondu Ivan Nikitin 2014-02-16 11:59:49
la source

je voulais juste ajouter que Bertrand Meyer, l'inventeur de Eiffel et de la conception par contrat, aurait Team hérite de List<Player> sans sourciller.

dans son livre, Object-Oriented Software Construction , il parle de la mise en œuvre d'un système GUI où les fenêtres rectangulaires peuvent avoir des fenêtres enfants. Il a simplement Window hériter à la fois de Rectangle et Tree<Window> pour réutiliser le application.

cependant, C# n'est pas Eiffel. Ce dernier supporte l'héritage multiple et renommer les caractéristiques . Dans C#, quand vous sous-classe, vous héritez à la fois l'interface et la mise en œuvre. Vous pouvez outrepasser l'implémentation, mais les conventions d'appel sont copiées directement à partir de la superclasse. En Eiffel, cependant, vous pouvez modifier les noms des méthodes publiques, de sorte que vous pouvez renommer Add et Remove à Hire et Fire dans votre Team . Si une instance de Team est rediffusée à List<Player> , l'appelant utilisera Add et Remove pour la modifier, mais vos méthodes virtuelles Hire et Fire seront appelées.

3
répondu Alexey 2015-04-27 14:35:13
la source

bien que je n'aie pas de comparaison complexe comme la plupart de ces réponses le font, j'aimerais partager ma méthode pour gérer cette situation. En étendant IEnumerable<T> , vous pouvez permettre à votre classe Team de supporter les extensions de requête Linq, sans exposer publiquement toutes les méthodes et propriétés de List<T> .

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}
3
répondu Null511 2018-01-20 05:43:24
la source

Je ne suis pas d'accord avec votre généralisation. Une équipe n'est pas qu'une collection de joueurs. Une équipe a tellement plus d'informations sur elle - nom, l'emblème, la collection de la direction/personnel administratif, la collection de l'équipe d'entraîneurs, puis la collection des joueurs. Ainsi, correctement, votre classe FootballTeam devrait avoir 3 collections et ne pas être elle-même une collection; si elle est de modéliser correctement le monde réel.

vous pourriez considérer une classe de PlayerCollection qui comme les spécialistes StringCollection offre d'autres possibilités - comme la validation et les contrôles avant que les objets ne soient ajoutés ou retirés du magasin interne.

peut-être, la notion D'un PlayerCollection betters convient à votre approche préférée?

public class PlayerCollection : Collection<Player> 
{ 
}

et puis L'équipe de football peut ressembler à ceci:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}
0
répondu Eniola 2016-11-23 18:55:34
la source

préfèrent les Interfaces aux Classes

Les Classes

devraient éviter de dériver de classes et mettre en place les interfaces minimales nécessaires.

breaks Inheritance Encapsulation

dérivant des classes breaks encapsulation :

  • expose les détails internes sur la façon dont votre collection est mise en œuvre
  • déclare une interface (ensemble des fonctions et des biens publics) qui peuvent ne pas être appropriés

entre autres, cela rend plus difficile de reformuler votre code.

les Classes sont un Détail de l'Implémentation

Les Classes

sont un détail d'implémentation qui doit être caché des autres parties de votre code. En bref, un System.List est une implémentation spécifique d'un type de données abstraites, qui peut ou non être approprié maintenant et dans le futur.

du point de vue conceptuel, le fait que le type de données System.List est appelé" liste " est un peu une diversion. Un System.List<T> est une collection mutable ordonnée qui supporte les opérations amorties O(1) pour ajouter, insérer et enlever des éléments, et O(1) pour extraire le nombre d'éléments ou obtenir et régler élément par index.

plus L'Interface est petite plus le Code est Flexible

lors de la conception d'une structure de données, plus l'interface est simple, plus le code est flexible. Il suffit de regarder à quel point LINQ est puissant pour une démonstration de cela.

Comment choisir les Interfaces

quand vous pensez " liste "vous devriez commencer par vous dire,"je dois représenter une collection de joueurs de baseball". Alors disons que tu décides de modéliser ça avec une classe. Ce que vous devez faire d'abord est de décider ce que la quantité minimale d'interfaces que cette classe devra exposer.

quelques questions qui peuvent aider à guider ce processus:

  • dois-je avoir le compte? Si vous n'envisagez pas de mettre en œuvre IEnumerable<T>
  • cette collection va-t-elle changer après avoir été initialisée? Si ce n'est pas considéré IReadonlyList<T> .
  • est-il important que je puisse accéder aux articles par index? Considérez ICollection<T>
  • Est l'ordre dans lequel j'ai ajouter des éléments à la collection important? Peut-être que c'est un ISet<T> ?
  • si vous voulez vraiment ces choses alors allez de l'avant et mettre en œuvre IList<T> .

de cette façon, vous ne couplerez pas d'autres parties du code à des détails de mise en œuvre de votre collection de joueurs de baseball et sera libre de changer la façon dont il est mis en œuvre aussi longtemps que vous respectez l'interface.

en adoptant cette approche, vous constaterez que le code devient plus facile à lire, à remanier et à réutiliser.

Notes sur la façon d'Éviter Standard

implémenter des interfaces dans un IDE moderne devrait être facile. Cliquez avec le bouton droit de la souris et choisissez "Implement Interface". Ensuite, transférez toutes les implémentations à une classe de membre si vous en avez besoin.

cela dit, si vous constatez que vous avez écrit beaucoup de passe-partout, c'est peut-être parce que vous êtes exposant plus de fonctions que vous devriez être. C'est la même raison pour laquelle vous ne devriez pas hériter d'une classe.

vous pouvez également concevoir des interfaces plus petites qui ont du sens pour votre application, et peut-être juste quelques fonctions d'extension helper pour mapper ces interfaces à toutes les autres que vous avez besoin. C'est l'approche que j'ai adoptée dans ma propre interface IArray pour le LinqArray bibliothèque .

0
répondu cdiggins 2018-10-07 08:01:01
la source

Autres questions sur c# design .net oop inheritance