c# strip - Perché non ereditare dalla lista<T>?




html agility (22)

Questo mi ricorda il "È un" contro "ha un" compromesso ". A volte è più facile e ha più senso ereditare direttamente da una super classe. Altre volte ha più senso creare una classe autonoma e includere la classe che avresti ereditato come variabile membro. È ancora possibile accedere alle funzionalità della classe ma non sono legati all'interfaccia o ad altri vincoli che potrebbero derivare dall'erogazione della classe.

Che fai? Come con molte cose ... dipende dal contesto. La guida che userei è che per ereditare da un'altra classe ci dovrebbe veramente essere una relazione "è una". Quindi se scrivi una classe chiamata BMW, potrebbe ereditare da Car perché una BMW è veramente un'auto. Una classe Horse può ereditare dalla classe Mammal perché un cavallo è effettivamente un mammifero nella vita reale e qualsiasi funzionalità del Mammifero dovrebbe essere rilevante per Horse. Ma puoi dire che una squadra è una lista? Da quello che posso dire, non sembra che una squadra sia davvero una "lista". Quindi in questo caso, avrei una lista come variabile membro.

Quando pianifico i miei programmi, spesso inizio con una catena di pensieri come questa:

Una squadra di calcio è solo una lista di giocatori di calcio. Pertanto, dovrei rappresentarlo con:

var football_team = new List<FootballPlayer>();

L'ordine di questa lista rappresenta l'ordine in cui i giocatori sono elencati nel roster.

Ma mi rendo conto più tardi che le squadre hanno anche altre proprietà, oltre alla semplice lista di giocatori, che devono essere registrate. Ad esempio, il totale parziale dei punteggi in questa stagione, il budget corrente, i colori uniformi, una string rappresenta il nome della squadra, ecc.

Allora penso:

Ok, una squadra di football è come una lista di giocatori, ma in aggiunta ha un nome (una string ) e un totale parziale di punteggi (un int ). .NET non fornisce una classe per la memorizzazione delle squadre di calcio, quindi farò la mia classe. La struttura esistente più simile e rilevante è List<FootballPlayer> , quindi erediterò da essa:

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

Ma si scopre che una linea guida dice che non si dovrebbe ereditare dalla List<T> . Sono completamente confuso da questa linea guida sotto due aspetti.

Perchè no?

Apparentemente List è in qualche modo ottimizzato per le prestazioni . Come mai? Quali problemi di prestazioni dovrei causare se estendo List ? Cosa si romperà esattamente?

Un'altra ragione che ho visto è che List è fornito da Microsoft e non ho alcun controllo su di esso, quindi non posso cambiarlo più tardi, dopo aver esposto una "API pubblica" . Ma faccio fatica a capirlo. Che cos'è un'API pubblica e perché dovrei preoccuparmi? Se il mio progetto attuale non ha e non è probabile che abbia mai questa API pubblica, posso ignorare tranquillamente questa linea guida? Se eredito da List e risulta che ho bisogno di un'API pubblica, quali difficoltà dovrò avere?

Perché è ancora importante? Una lista è una lista. Cosa potrebbe cambiare? Cosa potrei voler cambiare?

E infine, se Microsoft non voleva che ereditassi da List , perché non hanno sealed la classe?

Cos'altro dovrei usare?

Apparentemente, per le collezioni personalizzate, Microsoft ha fornito una classe Collection che dovrebbe essere estesa anziché List . Ma questa classe è molto semplice e non ha molte cose utili, come ad esempio AddRange . La risposta di jvitor83 fornisce un razionale di prestazioni per quel particolare metodo, ma come è un AddRange lento non migliore di nessun AddRange ?

Ereditare dalla Collection è molto più lavoro che ereditare dalla List , e non vedo alcun beneficio. Sicuramente Microsoft non mi direbbe di fare del lavoro extra senza motivo, quindi non posso fare a meno di sentirmi in qualche modo frainteso e l'ereditare Collection non è la soluzione giusta per il mio problema.

Ho visto suggerimenti come l'implementazione di IList . Solo no. Sono dozzine di righe di codice boilerplate che non mi fanno guadagnare nulla.

Infine, alcuni suggeriscono di avvolgere la List in qualcosa:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Ci sono due problemi con questo:

  1. Rende il mio codice inutilmente dettagliato. Ora devo chiamare my_team.Players.Count anziché solo my_team.Count . Fortunatamente, con C # posso definire gli indicizzatori per rendere l'indicizzazione trasparente e inoltrare tutti i metodi List interno ... Ma questo è un sacco di codice! Cosa ottengo per tutto ciò che funziona?

  2. Semplicemente non ha alcun senso. Una squadra di calcio non "ha" una lista di giocatori. È la lista dei giocatori. Non dici "John McFootballer è entrato nei giocatori di SomeTeam". Dici "John si è unito a SomeTeam". Non si aggiunge una lettera a "caratteri di una stringa", si aggiunge una lettera a una stringa. Non aggiungi un libro ai libri di una biblioteca, aggiungi un libro a una biblioteca.

Mi rendo conto che ciò che accade "sotto il cofano" si può dire che è "aggiungere la lista interna di X a Y", ma questo sembra un modo molto controintuitivo di pensare al mondo.

La mia domanda (riassunta)

Qual è il modo corretto C # di rappresentare una struttura dati, che, "logicamente" (vale a dire, "per la mente umana") è solo una list di things con poche campane e fischietti?

L'ereditarietà di List<T> sempre inaccettabile? Quando è accettabile? Perché perché no? Cosa deve considerare un programmatore quando decide se ereditare dall'elenco List<T> o no?


Quello che dicono le linee guida è che l'API pubblica non dovrebbe rivelare la decisione di progettazione interna se si sta utilizzando una lista, un set, un dizionario, un albero o altro. Una "squadra" non è necessariamente una lista. Puoi implementarlo come una lista ma gli utenti della tua API pubblica dovrebbero usare la tua classe sulla base della necessità di conoscere. Ciò ti consente di cambiare la tua decisione e utilizzare una diversa struttura dei dati senza influire sull'interfaccia pubblica.


Prima di tutto, ha a che fare con l'usabilità. Se si utilizza l'ereditarietà, la classe Team esporrà comportamenti (metodi) progettati esclusivamente per la manipolazione degli oggetti. Ad esempio, i AsReadOnly() o CopyTo(obj) non hanno senso per l'oggetto team. Invece del metodo AddRange(items) probabilmente vorresti un metodo AddPlayers(players) più descrittivo.

Se si desidera utilizzare LINQ, l'implementazione di un'interfaccia generica come ICollection<T> o IEnumerable<T> avrebbe più senso.

Come già detto, la composizione è il modo giusto per farlo. Basta implementare un elenco di giocatori come variabile privata.


Cosa succede se FootballTeam ha una squadra riserve insieme alla squadra principale?

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

Come lo modellerai?

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

La relazione ha chiaramente a e non è a .

o RetiredPlayers ?

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

Come regola generale, se si desidera ereditare da una raccolta, denominare la classe SomethingCollection .

Semanticamente la tua SomethingCollection senso? Fallo solo se il tuo tipo è una raccolta di Something .

Nel caso di FootballTeam , non suona bene. Una Team è più di una Collection . Una Team può avere allenatori, allenatori, ecc. Come hanno sottolineato le altre risposte.

FootballCollection sembra una raccolta di palloni da calcio o forse una collezione di accessori per il calcio. TeamCollection , una raccolta di squadre.

FootballPlayerCollection sembra una raccolta di giocatori che sarebbe un nome valido per una classe che eredita da List<FootballPlayer> se davvero volevi farlo.

List<FootballPlayer> reale List<FootballPlayer> è un tipo perfettamente valido da affrontare. Forse IList<FootballPlayer> se lo stai restituendo da un metodo.

In sintesi

Chiedilo a te stesso

  1. X a Y ? o ha X a Y ?

  2. I miei nomi di classe significano cosa sono?


Una squadra di calcio non è una lista di giocatori di calcio. Una squadra di calcio è composta da una lista di giocatori di calcio!

Questo è logicamente sbagliato:

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

e questo è corretto:

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

Solo perché penso che l'altro risponda praticamente alla tangente di una squadra di calcio "è-un" List<FootballPlayer>o "ha-un" List<FootballPlayer>, che in realtà non risponde a questa domanda come scritto.

Il PO chiede principalmente chiarimenti sulle linee guida per ereditare da List<T>:

Una linea guida dice che non si dovrebbe ereditare da List<T>. Perchè no?

Perché List<T>non ha metodi virtuali. Questo è meno un problema nel tuo codice, dal momento che di solito puoi cambiare l'implementazione con relativamente poco dolore, ma può essere un affare molto più grande in un'API pubblica.

Che cos'è un'API pubblica e perché dovrei preoccuparmi?

Un'API pubblica è un'interfaccia che esponi ai programmatori di terze parti. Pensa al codice quadro. E ricorda che le linee guida a cui si fa riferimento sono " Linee guida per la progettazione di .NET Framework " e non " Linee guida per la progettazione di applicazioni .NET ". C'è una differenza e, in generale, la progettazione dell'API pubblica è molto più rigida.

Se il mio progetto attuale non ha e non è probabile che abbia mai questa API pubblica, posso ignorare tranquillamente questa linea guida? Se eredito da List e risulta che ho bisogno di un'API pubblica, quali difficoltà dovrò avere?

Praticamente, sì. Puoi prendere in considerazione la logica alla base per vedere se si applica comunque alla tua situazione, ma se non stai creando un'API pubblica allora non devi preoccuparti in particolare delle problematiche relative alle API come il controllo delle versioni (di cui, questo è un sottoinsieme).

Se in futuro aggiungi un'API pubblica, dovrai o astrarre la tua API dall'implementazione (non esponendoti List<T>direttamente) o violare le linee guida con l'eventuale dolore futuro che ciò comporta.

Perché è ancora importante? Una lista è una lista. Cosa potrebbe cambiare? Cosa potrei voler cambiare?

Dipende dal contesto, ma dal momento che stiamo usando FootballTeamcome esempio - immagina che non puoi aggiungere un FootballPlayerse questo causerebbe il superamento del tetto salariale della squadra. Un possibile modo di aggiungere sarebbe qualcosa di simile:

 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 ... ma non puoi override Addperché non lo è virtual(per ragioni di prestazioni).

Se sei in un'applicazione (che, in pratica, significa che tu e tutti i chiamanti sono stati compilati insieme), ora puoi passare all'utilizzo IList<T>e correggere eventuali errori di compilazione:

 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 */
 }

ma, se hai esposto pubblicamente a una terza parte, hai appena apportato una brusca modifica che causerà errori di compilazione e / o di esecuzione.

TL; DR - le linee guida sono per le API pubbliche. Per le API private, fai ciò che vuoi.


Wow, il tuo post ha un'intera serie di domande e punti. La maggior parte del ragionamento che ottieni da Microsoft è esattamente al punto. Iniziamo con tutto ciò che riguarda List<T>

  • List<T> è altamente ottimizzato. Il suo uso principale è quello di essere usato come membro privato di un oggetto.
  • Microsoft non lo ha sigillato perché a volte potresti voler creare una classe con un nome più amichevole: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . Ora è facile come var list = new MyList<int, string>(); .
  • CA1002: Non esporre elenchi generici : in sostanza, anche se si prevede di utilizzare questa app come unico sviluppatore, vale la pena sviluppare con buone pratiche di codifica, in modo che si instillino in te e in una seconda natura. Sei ancora autorizzato a esporre l'elenco come un IList<T> se hai bisogno di un consumatore per avere un elenco indicizzato. Questo ti consente di modificare l'implementazione all'interno di una classe in seguito.
  • Microsoft ha reso Collection<T> molto generica perché è un concetto generico ... il nome dice tutto; è solo una collezione. Esistono versioni più precise come SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> , ecc. Ognuna delle quali implementa IList<T> ma non List<T> .
  • Collection<T> consente ai membri (ad esempio Aggiungi, Rimuovi, ecc.) Di essere sovrascritti perché sono virtuali. List<T> no.
  • L'ultima parte della tua domanda è azzeccata. Una squadra di calcio è più di una semplice lista di giocatori, quindi dovrebbe essere una classe che contiene quella lista di giocatori. Pensa Composizione vs Eredità . Una squadra di calcio ha una lista di giocatori (un roster), non è un elenco di giocatori.

Se stavo scrivendo questo codice la classe sarebbe probabilmente simile a questo:

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

Quando dicono che List<T>è "ottimizzato", penso che vogliano dire che non ha caratteristiche come i metodi virtuali che sono un po 'più costosi. Quindi il problema è che una volta che esponi la List<T>tua API pubblica , perdi la capacità di applicare le regole aziendali o di personalizzare la sua funzionalità in un secondo momento. Ma se stai utilizzando questa classe ereditata come interna all'interno del tuo progetto (al contrario di potenzialmente esposta a migliaia di tuoi clienti / partner / altri team come API) allora potrebbe essere OK se ti fa risparmiare tempo ed è la funzionalità che vuoi duplicare. Il vantaggio dell'ereditarietà List<T>consiste nell'eliminare un sacco di codice wrapper stupido che non verrà mai personalizzato nel futuro prevedibile. Inoltre, se vuoi che la tua classe abbia esattamente la stessa semantica diList<T> per la vita delle tue API quindi potrebbe anche essere OK.

Spesso vedo molta gente fare un sacco di lavoro extra solo perché la regola FxCop lo dice o il blog di qualcuno dice che è una pratica "cattiva". Molte volte, questo si trasforma in codice per progettare la stranezza di palooza del modello. Come con molte linee guida, trattalo come linea guida che può avere eccezioni.


Dipende dal contesto

Quando consideri la tua squadra come una lista di giocatori, stai proiettando l'idea di una squadra di football su un aspetto: riduci la "squadra" alle persone che vedi sul campo. Questa proiezione è corretta solo in un certo contesto. In un contesto diverso, questo potrebbe essere completamente sbagliato. Immagina di voler diventare uno sponsor della squadra. Quindi devi parlare con i dirigenti della squadra. In questo contesto il team viene proiettato nell'elenco dei suoi manager. E questi due elenchi di solito non si sovrappongono molto. Altri contesti sono la corrente rispetto ai precedenti giocatori, ecc.

Semantica non chiara

Quindi il problema di considerare una squadra come una lista dei suoi giocatori è che la sua semantica dipende dal contesto e che non può essere estesa quando il contesto cambia. Inoltre è difficile esprimere, quale contesto stai usando.

Le classi sono estensibili

Quando si utilizza una classe con un solo membro (ad esempio IList activePlayers), è possibile utilizzare il nome del membro (e in aggiunta il suo commento) per rendere chiaro il contesto. Quando ci sono contesti aggiuntivi, basta aggiungere un membro aggiuntivo.

Le lezioni sono più complesse

In alcuni casi potrebbe essere eccessivo creare una classe extra. Ogni definizione di classe deve essere caricata attraverso il classloader e verrà memorizzata nella cache dalla macchina virtuale. Ciò ti costa prestazioni e memoria durante l'esecuzione. Quando hai un contesto molto specifico, potrebbe essere OK considerare una squadra di calcio come una lista di giocatori. Ma in questo caso, dovresti usare solo una IList, non una classe derivata da essa.

Conclusione / Considerazioni

Quando hai un contesto molto specifico, è OK considerare una squadra come una lista di giocatori. Per esempio all'interno di un metodo è completamente OK scrivere

IList<Player> footballTeam = ...

Quando si utilizza F #, può anche essere OK creare un'abbreviazione di tipo

type FootballTeam = IList<Player>

Ma quando il contesto è più ampio o addirittura poco chiaro, non dovresti farlo. Questo è particolarmente vero quando crei una nuova classe, in cui non è chiaro in quale contesto possa essere utilizzata in futuro. Un segnale di avvertenza è quando si inizia ad aggiungere attributi aggiuntivi alla classe (nome della squadra, allenatore, ecc.). Questo è un chiaro segno che il contesto in cui verrà utilizzata la classe non è fisso e cambierà in futuro. In questo caso non puoi considerare la squadra come una lista di giocatori, ma dovresti modellare la lista dei giocatori (attualmente attivi, non feriti, ecc.) Come attributo della squadra.


Anche se non ho un confronto complesso come la maggior parte di queste risposte, vorrei condividere il mio metodo per gestire questa situazione. Estendendo IEnumerable<T>, puoi consentire alla tua Teamclasse di supportare le estensioni di query di Linq, senza esporre pubblicamente tutti i metodi e le proprietà di 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
{
    ...
}

Infine, alcuni suggeriscono di avvolgere la lista in qualcosa:

Questo è il modo corretto. "Stranamente verboso" è un brutto modo di guardare a questo. Ha un significato esplicito quando scrivi my_team.Players.Count . Vuoi contare i giocatori.

my_team.Count

..non significa niente. Conta cosa?

Una squadra non è una lista - il consistere di più di un semplice elenco di giocatori. Una squadra possiede giocatori, quindi i giocatori dovrebbero farne parte (un membro).

Se sei davvero preoccupato che sia eccessivamente prolisso, puoi sempre esporre le proprietà del team:

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

..che diventa:

my_team.PlayerCount

Questo ha un significato ora e aderisce a The Law Of Demeter .

Dovresti anche considerare di aderire al principio del riutilizzo composito . Tramite l'ereditarietà di List<T> , stai dicendo che una squadra è una lista di giocatori e che ne espone metodi inutili. Questo non è corretto - come hai affermato, una squadra è più di una lista di giocatori: ha un nome, manager, membri del consiglio di amministrazione, allenatori, personale medico, stipendi, ecc. Facendo in modo che la tua classe della squadra contenga un elenco di giocatori, Stai dicendo "Una squadra ha una lista di giocatori", ma può anche avere altre cose.


Qual è la corretta C # modo di rappresentare una struttura di dati ...

Ricorda: "Tutti i modelli sono sbagliati, ma alcuni sono utili". - George EP Box

Non esiste un "modo corretto", solo uno utile.

Scegli uno che è utile per te e / i tuoi utenti. Questo è tutto. Sviluppare economicamente, non sovra-ingegnerizzare. Meno codice scrivi, meno codice dovrai eseguire il debug. (leggi le seguenti edizioni).

-- Modificato

La mia migliore risposta sarebbe ... dipende. L'ereditarietà di un elenco esporrebbe i client di questa classe a metodi che potrebbero non essere esposti, principalmente perché FootballTeam si presenta come un'entità aziendale.

- Edizione 2

Sinceramente non ricordo a cosa mi riferivo nel commento "non sovra-ingegnere". Mentre credo che la mentalità dei KISS sia una buona guida, voglio sottolineare che ereditare una business class da List creerebbe più problemi di quanti ne risolva, a causa della perdita di astrazione .

D'altra parte, credo che ci sia un numero limitato di casi in cui è sufficiente ereditare dalla lista. Come ho scritto nella precedente edizione, dipende. La risposta a ciascun caso è fortemente influenzata dalla conoscenza, dall'esperienza e dalle preferenze personali.

Grazie a @kai per avermi aiutato a pensare più precisamente alla risposta.


Se gli utenti della classe hanno bisogno di tutti i metodi e le proprietà ** Elenco ha, si dovrebbe derivare la classe da esso. Se non ne hanno bisogno, racchiudi l'Elenco e crea wrapper per i metodi di cui i tuoi utenti di classe hanno realmente bisogno.

Questa è una regola rigorosa, se si scrive un'API pubblica o qualsiasi altro codice che verrà utilizzato da molte persone. Puoi ignorare questa regola se hai una piccola app e non più di 2 sviluppatori. Questo ti farà risparmiare un po 'di tempo.

Per le piccole app, potresti anche considerare di scegliere un altro linguaggio meno rigoroso. Ruby, JavaScript - tutto ciò che ti permette di scrivere meno codice.


Ci sono alcune buone risposte qui. Vorrei aggiungere loro i seguenti punti.

Qual è il modo corretto C # di rappresentare una struttura dati, che, "logicamente" (vale a dire, "per la mente umana") è solo una lista di cose con poche campane e fischietti?

Chiedi a una decina di persone non programmatrici di computer che hanno familiarità con l'esistenza del calcio per riempire lo spazio vuoto:

A football team is a particular kind of _____

Qualcuno ha detto "elenco di giocatori di calcio con alcune campane e fischietti", o tutti hanno detto "squadra sportiva" o "club" o "organizzazione"? La tua idea che una squadra di calcio sia un particolare tipo di elenco di giocatori è nella tua mente umana e nella tua mente umana da sola.

List<T> è un meccanismo . La squadra di calcio è un oggetto di business , ovvero un oggetto che rappresenta un concetto che si trova nel dominio aziendale del programma. Non mescolare quelli! Una squadra di calcio è una specie di squadra; ha una lista, una lista è una lista di giocatori . Un elenco non è un tipo particolare di elenco di giocatori . Un elenco è un elenco di giocatori. Quindi crea una proprietà chiamata Roster che è una List<Player> . E rendi ReadOnlyList<Player> mentre ci sei, a meno che tu non creda che tutti quelli che conoscono una squadra di calcio possano eliminare giocatori dal roster.

L'ereditarietà di List<T> sempre inaccettabile?

Inaccettabile per chi? Me? No.

Quando è accettabile?

Quando stai costruendo un meccanismo che estende il meccanismo List<T> .

Cosa deve considerare un programmatore quando decide se ereditare dall'elenco List<T> o no?

Sto costruendo un meccanismo o un oggetto aziendale ?

Ma questo è un sacco di codice! Cosa ottengo per tutto ciò che funziona?

Hai passato più tempo a scrivere la tua domanda che ti avrebbe portato a scrivere metodi di inoltro per i membri rilevanti della List<T> cinquanta volte. Ovviamente non hai paura della verbosità, e qui stiamo parlando di una quantità molto piccola di codice; questo è un paio di minuti di lavoro.

AGGIORNARE

Ci ho pensato un po 'e c'è un altro motivo per non modellare una squadra di calcio come una lista di giocatori. In realtà potrebbe essere una cattiva idea modellare una squadra di calcio come una lista di giocatori. Il problema con una squadra come / avere una lista di giocatori è che quello che hai è un'istantanea della squadra in un momento . Non so quale sia il tuo business case per questa classe, ma se avessi una classe che rappresentasse una squadra di calcio vorrei chiedergli domande come "quanti giocatori di Seahawks hanno saltato i giochi a causa di un infortunio tra il 2003 e il 2013?" o "Quale giocatore di Denver che in precedenza ha giocato per un'altra squadra ha avuto il maggiore aumento anno su anno dei cantieri?" o " I Piggers sono andati fino in fondo quest'anno? "

Cioè, una squadra di calcio mi sembra ben modellata come una raccolta di fatti storici come quando un giocatore è stato reclutato, ferito, in pensione, ecc. Ovviamente l'attuale lista dei giocatori è un fatto importante che dovrebbe probabilmente essere frontale e centro, ma potrebbero esserci altre cose interessanti che vuoi fare con questo oggetto che richiedono una prospettiva più storica.


Dipende dal comportamento del tuo oggetto "team". Se si comporta proprio come una raccolta, potrebbe essere OK rappresentarla prima con una lista semplice. Quindi potresti iniziare a notare che continui a duplicare il codice che itera sulla lista; a questo punto hai la possibilità di creare un oggetto FootballTeam che avvolge la lista dei giocatori. La classe FootballTeam diventa la casa per tutto il codice che itera sulla lista dei giocatori.

Rende il mio codice inutilmente dettagliato. Ora devo chiamare my_team.Players.Count anziché solo my_team.Count. Fortunatamente, con C # posso definire gli indicizzatori per rendere l'indicizzazione trasparente e inoltrare tutti i metodi dell'elenco interno ... Ma questo è un sacco di codice! Cosa ottengo per tutto ciò che funziona?

Incapsulamento. I tuoi clienti non devono sapere cosa succede all'interno di FootballTeam. Per tutti i vostri clienti lo sanno, potrebbe essere implementato guardando l'elenco dei giocatori in un database. Non hanno bisogno di sapere, e questo migliora il tuo design.

Semplicemente non ha alcun senso. Una squadra di calcio non "ha" una lista di giocatori. È la lista dei giocatori. Non dici "John McFootballer è entrato nei giocatori di SomeTeam". Dici "John si è unito a SomeTeam". Non si aggiunge una lettera a "caratteri di una stringa", si aggiunge una lettera a una stringa. Non aggiungi un libro ai libri di una biblioteca, aggiungi un libro a una biblioteca.

Esattamente :) dirai footballTeam.Add (john), non footballTeam.List.Add (john). L'elenco interno non sarà visibile.


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

Codice precedente significa: un gruppo di ragazzi della strada che giocano a calcio, e capita di avere un nome. Qualcosa di simile a:

Ad ogni modo, questo codice (dalla mia risposta)

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

Mezzi: questa è una squadra di calcio che ha dirigenti, giocatori, amministratori, ecc. Qualcosa del tipo:

Ecco come viene presentata la tua logica nelle immagini ...


Penso di non essere d'accordo con la tua generalizzazione. Una squadra non è solo una collezione di giocatori. Una squadra ha molte più informazioni a riguardo - nome, emblema, collezione di dirigenti / personale amministrativo, raccolta di tecnici di coaching, quindi raccolta di giocatori. Quindi, correttamente, la tua classe FootballTeam dovrebbe avere 3 raccolte e non essere di per sé una raccolta; se è quello di modellare correttamente il mondo reale.

Potresti prendere in considerazione una classe PlayerCollection che, come Specialized StringCollection, offre alcune altre funzionalità, come la convalida e i controlli prima che gli oggetti vengano aggiunti o rimossi dallo store interno.

Forse, la nozione di scommettitori PlayerCollection si adatta al tuo approccio preferito?

public class PlayerCollection : Collection<Player> 
{ 
}

E poi il FootballTeam può assomigliare a questo:

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();
}

Volevo solo aggiungere che Bertrand Meyer, l'inventore di Eiffel e design per contratto, avrebbe Teamereditato dall'esterno List<Player>senza nemmeno battere ciglio.

Nel suo libro, Object-Oriented Software Construction , discute l'implementazione di un sistema GUI in cui le finestre rettangolari possono avere finestre figlio. Ha semplicemente Windowereditato da entrambi Rectanglee Tree<Window>riutilizzare l'implementazione.

Tuttavia, C # non è Eiffel. Quest'ultimo supporta l'ereditarietà multipla e la rinomina delle funzionalità . In C #, quando si sottoclasse, si eredita sia l'interfaccia che l'implementazione. È possibile sovrascrivere l'implementazione, ma le convenzioni di chiamata vengono copiate direttamente dalla superclasse. In Eiffel, tuttavia, è possibile modificare i nomi dei metodi pubblici, in modo da poter rinominare Adde Removeda Hiree Firenel proprio Team. Se un'istanza di Teamè upcast torna a List<Player>, il chiamante utilizza Adde Removemodificarlo, ma i vostri metodi virtuali Hiree Firesarà chiamato.


Design> Implementazione

Quali metodi e proprietà esponi sono una decisione di progettazione. La classe base da cui si eredita è un dettaglio di implementazione. Sento che vale la pena fare un passo indietro verso il primo.

Un oggetto è una raccolta di dati e comportamenti.

Quindi le tue prime domande dovrebbero essere:

  • Quali dati comprende questo oggetto nel modello che sto creando?
  • Quale comportamento mostra questo oggetto in quel modello?
  • Come potrebbe questo cambiamento in futuro?

Ricorda che l'ereditarietà implica una relazione "isa" (è a), mentre la composizione implica una relazione "ha a" (hasa). Scegli quello giusto per la tua situazione nella tua vista, tenendo presente dove le cose potrebbero andare mentre la tua applicazione si evolve.

Considera di pensare nelle interfacce prima di pensare in tipi concreti, poiché alcune persone trovano più facile mettere il cervello in "modalità di progettazione" in questo modo.

Questo non è qualcosa che tutti fanno consapevolmente a questo livello nella codifica di tutti i giorni. Ma se stai rimuginando questo tipo di argomento, stai calpestando le acque del design. Essere consapevoli di ciò può essere liberatorio.

Prendi in considerazione le specifiche del design

Dai un'occhiata a List <T> e IList <T> su MSDN o Visual Studio. Scopri quali metodi e proprietà espongono. Questi metodi sembrano tutti qualcosa che qualcuno vorrebbe fare a un FootballTeam dal tuo punto di vista?

FootballTeam.Reverse () ha senso per te? FootballTeam.ConvertAll <TOutput> () assomiglia a qualcosa che vuoi?

Questa non è una domanda trabocchetto; la risposta potrebbe davvero essere "sì". Se implementi / erediti List <Player> o IList <Player>, sei bloccato con loro; se questo è l'ideale per il tuo modello, fallo.

Se decidi di sì, questo ha senso, e vuoi che il tuo oggetto sia curabile come una collezione / lista di giocatori (comportamento), e quindi vuoi implementare ICollection o IList, farlo assolutamente. fittiziamente:

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

Se vuoi che il tuo oggetto contenga una collezione / elenco di giocatori (dati), e quindi vuoi che la raccolta o l'elenco sia una proprietà o un membro, fallo assolutamente. fittiziamente:

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

Potresti sentire che vuoi che le persone siano in grado di enumerare solo il set di giocatori, piuttosto che contarli, aggiungerli o rimuoverli. IEnumerable <Player> è un'opzione perfettamente valida da considerare.

Potresti pensare che nessuna di queste interfacce sia utile nel tuo modello. Questo è meno probabile (IEnumerable <T> è utile in molte situazioni) ma è ancora possibile.

Chiunque tenti di dirti che uno di questi è categoricamente e definitivamente sbagliato in ogni caso è fuorviante. Chiunque tenti di dirti che è categoricamente e definitivamente giusto in ogni caso è fuorviante.

Passare all'attuazione

Una volta che hai deciso i dati e il comportamento, puoi prendere una decisione sull'implementazione. Questo include le classi concrete da cui dipende l'ereditarietà o la composizione.

Questo potrebbe non essere un grande passo in avanti, e le persone spesso confondono design e implementazione, dal momento che è abbastanza possibile sfogarsi in testa in un secondo o due e iniziare a digitare.

Un esperimento di pensiero

Un esempio artificiale: come hanno già detto altri, una squadra non è sempre "solo" una collezione di giocatori. Mantenere una collezione di punteggi delle partite per la squadra? La squadra è intercambiabile con il club, nel tuo modello? Se è così, e se la tua squadra è una raccolta di giocatori, forse è anche una raccolta di personale e / o una raccolta di punteggi. Quindi si finisce con:

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

Nonostante il design, a questo punto in C # non sarà possibile implementare tutto ciò ereditando comunque da List <T>, poiché C # "only" supporta l'ereditarietà singola. (Se hai provato questo malarky in C ++, si può considerare questa una buona cosa.) Implementazione di una raccolta tramite eredità e uno via la composizione rischia di sentirsi sporca. E proprietà come Count diventano confuse per gli utenti a meno che non implementi ILIst <Player> .Count e IList <StaffMember> .Count ecc. Esplicitamente, e quindi sono solo dolorosi piuttosto che confusi. Puoi vedere dove sta andando; sentirsi bene mentre si pensa questa via può ben dirvi che è sbagliato dirigersi in questa direzione (e a torto oa ragione, i vostri colleghi potrebbero anche averlo implementato in questo modo!)

La risposta breve (troppo tardi)

La linea guida per non ereditare dalle classi di raccolta non è specifica per C #, la troverai in molti linguaggi di programmazione. È ricevuto saggezza non una legge. Una ragione è che nella pratica si considera che la composizione vinca spesso sull'eredità in termini di comprensibilità, implementazione e manutenibilità. È più comune con oggetti del mondo reale / dominio trovare relazioni "hasa" utili e consistenti rispetto alle relazioni "isa" utili e coerenti, a meno che non siate in profondità nell'astratto, soprattutto con il passare del tempo e i dati precisi e il comportamento degli oggetti nel codice i cambiamenti. Questo non dovrebbe farti escludere sempre l'ereditarietà dalle classi di raccolta; ma può essere suggestivo.


Fammi riscrivere la tua domanda. quindi potresti vedere il soggetto da una prospettiva diversa.

Quando ho bisogno di rappresentare una squadra di calcio, capisco che è fondamentalmente un nome. Come: "Le aquile"

string team = new string();

Poi più tardi ho realizzato che anche i team hanno giocatori.

Perché non posso semplicemente estendere il tipo di stringa in modo che tenga anche un elenco di giocatori?

Il tuo punto di accesso al problema è arbitrario. Provate a pensare che cosa fa una squadra ha (proprietà), non è quello che è .

Dopo averlo fatto, puoi vedere se condivide le proprietà con altre classi. E pensa all'eredità.


Il mio sporco segreto: non mi interessa cosa dice la gente, e lo faccio. .NET Framework è distribuito con "XxxxCollection" (UIElementCollection per l'esempio migliore).

Quindi cosa mi impedisce di dire:

team.Players.ByName("Nicolas")

Quando lo trovo meglio di

team.ByName("Nicolas")

Inoltre, il mio PlayerCollection potrebbe essere utilizzato da un'altra classe, come "Club" senza alcuna duplicazione di codice.

club.Players.ByName("Nicolas")

Le migliori pratiche di ieri potrebbero non essere quelle di domani. Non c'è alcun motivo dietro la maggior parte delle migliori pratiche, la maggior parte sono solo un ampio consenso tra la comunità. Invece di chiedere alla comunità se ti darà la colpa quando lo chiedi a te stesso, cosa è più leggibile e mantenibile?

team.Players.ByName("Nicolas") 

o

team.ByName("Nicolas")

Veramente.Hai qualche dubbio? Ora forse hai bisogno di giocare con altri vincoli tecnici che ti impediscono di usare List <T> nel tuo caso d'uso reale. Ma non aggiungere un vincolo che non dovrebbe esistere. Se Microsoft non ha documentato il perché, allora è sicuramente una "pratica migliore" che arriva dal nulla.


new_list = my_list[:]

new_list = my_list Cerca di capire questo. Diciamo che my_list è nella memoria heap nella posizione X cioè my_list punta alla X. Ora assegnando new_list = my_list stai Lasciando new_list puntando alla X. Questo è noto come Copia superficiale.

Ora se assegni new_list = my_list[:] stai semplicemente copiando ogni oggetto di my_list in new_list. Questo è noto come Deep Copy.

L'altro modo in cui puoi farlo sono:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)






c# .net list oop inheritance