Kodėl ne paveldėti iš sąrašo <T>?

Planuodamas savo programas, aš dažnai pradedu nuo šios minties:

Futbolo komanda yra tik žaidėjų sąrašas. Todėl turiu pateikti jį naudodamasis:

 var football_team = new List<FootballPlayer>(); 

Šio sąrašo eiliškumas yra tai, kaip žaidėjai yra išvardyti.

Tačiau vėliau suprantu, kad komandos taip pat turi ir kitų savybių, išskyrus paprastą įrašų sąrašą. Pavyzdžiui, bendras šio sezono taškų skaičius, dabartinis biudžetas, vienodos spalvos, string , komandos pavadinimas ir kt.

Taigi, manau:

Na, futbolo komanda yra lygiai tokia pati, kaip ir žaidėjų sąrašas, tačiau be to, jis turi pavadinimą ( string ) ir bendrus taškus ( int ) .. NET nesuteikia klasės futbolo komandų saugojimui, todėl padarysiu savo klasę. Panašiausia ir atitinkama esama List<FootballPlayer> struktūra, todėl ją paveldu:

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

Tačiau paaiškėja, kad gidas sako, kad neturėtumėte paveldėti iš List<T> . Aš esu visiškai nepatogus dėl šio vadovo dviem būdais.

Kodėl gi ne?

Matyt, List optimizuotas našumui . Kaip taip? Kokių našumo problemų aš paskambinsiu, jei tęsiu List ? Kas tiksliai suskaido?

Kita priežastis, dėl kurios aš mačiau, yra tai, kad List pateikia „Microsoft“, ir aš jo nekontroliuoju, todėl vėliau, kai atidarysite „atvirą API“, negaliu jo pakeisti . Tačiau aš negaliu to suprasti. Kas yra vieša API ir kodėl tai man nerimauja? Jei mano dabartinis projektas neturės ir beveik niekada neturės šios viešosios API, ar galiu saugiai ignoruoti šį vadovą? Jei paveldėsi iš List ir paaiškėjau, kad reikia viešos API Kokius sunkumus turiu?

Kodėl tai svarbu? Sąrašas yra sąrašas. Kas gali pasikeisti? Ką aš galiu pakeisti?

Ir, galiausiai, jei „Microsoft“ nenori man paveldėti iš List , kodėl jie nepadarė sealed klasės?

Ką dar turėčiau naudoti?

Matyt, „Custom“ kolekcijoms „Microsoft“ pateikė Collection klasę, kuri turėtų būti išplėsta vietoj List . Tačiau ši klasė yra labai plika ir neturi daug naudingų dalykų, pavyzdžiui, „ AddRange . „Jvitor83“ atsakyme pateikiamas šio konkretaus metodo veikimo pagrindimas, bet kaip lėtai yra „ AddRange lėtesnis nei „ AddRange ?

Paveldėjo iš Collection daugiau nei paveldėti iš List ir nematau jokios naudos. Žinoma, „Microsoft“ nebūtų manęs manęs daryti papildomų darbų be priežasties, todėl negaliu padėti, bet jaučiuosi taip, lyg kažką nesuprantu, o Collection paveldėjimas tikrai nėra teisingas mano problemos sprendimas.

Aš mačiau tokius pasiūlymus kaip IList . Tiesiog ne. Tai yra daugybė šablonų kodo eilučių, kurios man nieko neduoda.

Galiausiai, kai kurie pasiūlė įvesti List į kažką:

 class FootballTeam { public List<FootballPlayer> Players; } 

Yra dvi problemos:

  • Dėl to mano kodas yra nereikalingas. Dabar turiu skambinti my_team.Players.Count vietoj my_team.Count . Laimei, C #, aš galiu apibrėžti indeksuotojų, kad indeksavimas būtų skaidrus, ir siųsti visus vidaus List metodus ... Bet tai yra daug kodo! Ką gausiu už viską, kas veikia?

  • Tai tiesiog neturi jokios prasmės. Futbolo komanda neturi žaidėjų sąrašo. Tai žaidėjų sąrašas. Jūs nesakote: „John McFutaller prisijungė prie„ SomeTeam “žaidėjų. Jūs sakote: „Jonas prisijungė prie„ SomeTeam “. Neįtraukite raidės į „eilutės simbolius“, įtraukiate raidę į eilutę. Jūs nepridėsite knygos į bibliotekų knygas, įtraukiate knygą į biblioteką.

Suprantu, kad tai, kas vyksta „po gaubtu“, galite pasakyti: „pridedant XY į vidinį sąrašą“, tačiau tai atrodo kaip labai prieštaringas požiūris į pasaulį.

Mano klausimas yra (apibendrintas)

Kas yra teisingas C # metodas, rodantis duomenų struktūrą, kuri yra „logiška“ (ty „žmogaus protui“) yra tik things List su keliais varpais ir švilpukais?

Argumentas List<T> visada List<T> nepriimtinas? Kada tai priimtina? Kodėl, kodėl gi ne? Ką turėtų atsižvelgti programuotojas, nuspręsdamas, ar paveldėti iš List<T> ?

1033
11 февр. Superbest yra nustatytas vasario 11 d 2014-02-11 06:01 '14 at 6:01 AM 2014-02-11 06:01
@ 26 atsakymai

Čia yra gerų atsakymų. Norėčiau į juos įtraukti šiuos punktus.

Koks yra tinkamas būdas atvaizduoti # duomenų struktūrą, kuri yra „logiška“ (ty „žmogaus protui“) - ar tai tik dalykų sąrašas su keliais varpais ir švilpukais?

Paklauskite dešimties ne kompiuterių programuotojų, kurie yra susipažinę su futbolu, užpildyti spragą:

 A football team is a particular kind of _____ 

Ar kas nors pasakė „žaidėjų sąrašą su keliais varpais“, ar jie visi sakė „sporto komanda“, „klubas“ ar „organizacija“? Jūsų nuomonė, kad futbolo komanda yra specialus žaidėjų sąrašas, yra jūsų protas ir žmogaus protas.

List<T> yra mechanizmas. Futbolo komanda yra verslo objektas, ty objektas, kuris yra koncepcija, esanti programos verslo srityje. Negalima maišyti! Futbolo komanda yra tam tikra komanda; Jis turi sąrašą - žaidėjų sąrašą. Sąrašas nėra atskiras žaidėjų sąrašas. Sąrašas yra žaidėjų sąrašas. Todėl sukurkite Roster , kuris yra List<Player> . Ir atlikite jį su „ ReadOnlyList<Player> kai esate jo veikloje, jei netikite, kad visi, kurie žino apie futbolo komandą, gali pašalinti žaidėjus iš sąrašo.

Argumentas List<T> visada List<T> nepriimtinas?

Kam nepriimtina? Aš? Ne

Kada tai priimtina?

Kai sukuriate mechanizmą, kuris praplečia List<T> .

Ką turėtų atsižvelgti programuotojas, nuspręsdamas, ar paveldėti iš List<T> ?

Ar sukuriu mechanizmą ar verslo objektą?

Bet tai yra daug kodo! Ką gausiu už viską, kas veikia?

Jūs praleidote daugiau laiko įvesdami savo klausimą, kad jums reikės įrašyti perdavimo metodus atitinkamiems „ List<T> nariams penkiasdešimt kartų. Akivaizdu, kad nebijote „švelnumo“, ir čia kalbame apie labai mažą kodo kiekį; tai kelias minutes darbo.

UPDATE

Aš šiek tiek galvojau, ir yra dar viena priežastis, kad futbolo rinktinė nebūtų imituojama kaip žaidėjų sąrašas. Tiesą sakant, futbolo rinktinės pavyzdys gali būti žaidėjų sąrašas. Su komanda, turinčia žaidėjų sąrašą, problema yra ta, kad jūs turite momentinį komandos momentinį vaizdą. Nežinau, kokie yra jūsų verslui šiai klasei, bet jei turėčiau klasę, atstovaujančią futbolo komandai, norėčiau užduoti jam klausimus: „Kiek„ Seahawks “žaidėjų praleido žaidimą dėl žalos nuo 2003 iki 2013 m.? arba „Kurį žaidėją Denveryje, kuris anksčiau grojo kitai komandai, per pastaruosius metus buvo didžiausias augimas?“ arba „ Ar šiais metaisPigers “vyko?

Tai reiškia, kad man atrodo, kad futbolo komanda yra gerai modeliuota kaip istorinių faktų rinkinys, pavyzdžiui, kai žaidėjas buvo įdarbintas, sužeistas, pensininkas ir pan. Akivaizdu, kad dabartinis žaidėjų sąrašas yra svarbus faktas, kuris tikriausiai turėtų būti priešais centrą, tačiau gali būti ir kitų įdomių dalykų, kuriuos norite padaryti su šiuo objektu, kuriam reikia daugiau istorinės perspektyvos.

1196
11 февр. Atsakymą pateikė Eric Lippert , vasario 11 d. 2014-02-11 08:43 '14 at 8:43 2014-02-11 08:43

Galiausiai, kai kurie pasiūlymai įtraukti sąrašą į kažką:

Tai teisingas kelias. „Tikrai verbiškumas“ yra blogas būdas jį pažvelgti. Jis turi aiškią reikšmę, kai rašote my_team.Players.Count . Norite suskaičiuoti žaidėjus.

 my_team.Count 

.. nieko nereiškia. Pagalvokite, ką?

Komanda nėra sąrašas, sudarytas ne tik iš žaidėjų sąrašo. Komandai priklauso žaidėjai, todėl žaidėjai turi būti jos nariai (nariai).

Jei tikrai susirūpinote, kad tai pernelyg griežta, visada galite nustatyti ypatybes iš komandos:

border=0
 public int PlayerCount { get { return Players.Count; } } 

.. kuris tampa:

 my_team.PlayerCount 

Dabar tai prasminga ir laikosi Demeterio įstatymo .

Taip pat turėtumėte apsvarstyti sudėtinio pakartotinio naudojimo principą. Pasakydami iš List<T> , jūs sakote, kad komanda yra žaidėjas ir iš jo atskleidžia nereikalingus metodus. Tai neteisinga. Kaip minėjote, komanda yra daugiau nei žaidėjų sąrašas: ji turi vardą, vadovus, valdybos narius, trenerius, medicinos personalą, atlyginimą ir kt. Jei jūsų komandos klasėje yra žaidėjų sąrašas, jūs „komanda turi žaidėjų sąrašą“, tačiau gali būti ir kitų dalykų.

242
11 февр. Simon Whitehead atsakymą pateikė vasario 11 d. 2014-02-11 06:05 '14 at 6:05 2014-02-11 06:05

Wow, jūsų paštu yra daug klausimų ir taškų. Dauguma argumentų, kuriuos gaunate iš „Microsoft“, yra ten. Pradėkite nuo visko apie List<T>

  • List<T> labai optimizuotas. Jo pagrindinis naudojimas turėtų būti naudojamas kaip privatus objekto narys.
  • „Microsoft“ class MyList<T, TX>: List<CustomObject<T, Something<TX>> {... } , nes kartais jums gali prireikti sukurti klasę su draugiškesniu pavadinimu: class MyList<T, TX>: List<CustomObject<T, Something<TX>> {... } . Dabar tai taip paprasta, kaip var list = new MyList<int, string>(); ,
  • CA1002: nelaikykite bendrų sąrašų : iš esmės, net jei jūs ketinate naudoti šią programą kaip vienintelį kūrėją, verta plėtoti gerą kodavimo praktiką, kad jie taptų jūsų ir antrojo pobūdžio. Jūs vis tiek galite rodyti sąrašą kaip IList<T> jei reikia vartotojo, kad jis turėtų indeksų sąrašą. Tai leis vėliau pakeisti klasėje įdiegtą programą.
  • „Microsoft“ sukūrė Collection<T> labai bendrai, nes ji yra bendra koncepcija ... pavadinimas sako viską; tai tik kolekcija. Yra tikslesnės versijos, tokios kaip SortedCollection<T> , SortedCollection<T> , ReadOnlyCollection<T> ir tt, kiekvienas iš jų įgyvendina IList<T> bet ne List<T> .
  • Collection<T> leidžia nariams (t.y. pridėti, ištrinti ir pan.) Panaikinti, nes jie yra virtualūs. List<T> - ne.
  • Paskutinė jūsų klausimo dalis yra įdiegta. Futbolo komanda yra ne tik žaidėjų sąrašas, todėl ji turi būti klasė, kurioje yra šis žaidėjų sąrašas. Pagalvokite apie kompoziciją ir paveldėjimą . Futbolo rinktinėje yra žaidėjų sąrašas (sąrašas), tai nėra žaidėjų sąrašas.

Jei rašau šį kodą, klasė greičiausiai atrodytų taip:

 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
11 февр. atsakymas pateiktas mano vasario 11 d. 2014-02-11 06:26 '14 at 6:26 AM 2014-02-11 06:26
 class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal; } 

Ankstesnis kodas reiškia: vaikinas iš gatvės, žaidžiantis futbolą, ir jie, atrodo, turi vardą. Kažkas panašaus:

2019

105
11 февр. vasario 11 d. Nean Der Thal atsakymas . 2014-02-11 18:45 '14, 18:45 pm 2014-02-11 18:45

Tai klasikinis kompozicijos ir paveldėjimo pavyzdys.

Šiuo konkrečiu atveju:

Ar komanda yra žaidėjų, turinčių papildomą elgesį, sąrašas.

arba

Ar komanda turi savo objektą, kuriame yra žaidėjų sąrašas.

Išplėsti sąrašą galite apriboti save keliais būdais:

  • Jūs negalite apriboti prieigos (pvz., Neleiskite žmonėms keisti sąrašo). Gausite visus sąrašo metodus, nesvarbu, ar norite, ar norite juos visus, ar ne.

  • Kas atsitiks, jei norite turėti kitų dalykų sąrašus. Pavyzdžiui, komandos turi trenerių, vadovų, gerbėjų, įrangos ir kt. Kai kurie iš jų gali būti savarankiški sąrašai.

  • Jūs apribojate savo paveldėjimo galimybes. Pavyzdžiui, galite sukurti bendrą komandos objektą, tada gauti „BaseballTeam“, „FootballTeam“ ir kt. Norėdami paveldėti iš sąrašo, turite paveldėti iš komandos, tačiau tai reiškia, kad visos skirtingų tipų komandos yra priverstos įgyvendinti tą patį sąrašą.

Sudėtis - įskaitant objektą, kuris suteikia elgesį, kurį norite įdėti į savo objektą.

Paveldėjimas - jūsų objektas tampa objekto, kuris turi norimą elgesį, pavyzdžiu.

Abu turi savo paskirtį, tačiau tai yra aiškus atvejis, kai kompozicija yra pageidautina.

84
11 февр. Atsakymą pateikė Tim B , vasario 11 d. 2014-02-11 13:58 '14 at 13:58 2014-02-11 13:58

Kaip visi pažymėjo, žaidėjų komanda nėra žaidėjų sąrašas. Šią klaidą daro daugelis žmonių visame pasaulyje, galbūt skirtinguose žinių lygiuose. Dažnai problema yra subtilus ir kartais labai grubus, kaip šiuo atveju. Tokios konstrukcijos yra blogos, nes pažeidžia Liškovo pakeitimo principą . Internete yra daug gerų straipsnių, paaiškinančių šią koncepciją, pvz., Http://en.wikipedia.org/wiki/Liskov_subITION_principle

Taigi yra dvi taisyklės, kurias reikia išlaikyti santykiuose tarp tėvų ir vaikų tarp klasių:

  • Vaikas neturėtų turėti jokių savybių, kurios yra mažesnės už tėvą.
  • Tėvui neturėtų būti būdinga savybė, kuri visiškai apibrėžia vaiką.

Kitaip tariant, tėvas yra būtinas vaiko apibrėžimas, o vaikas yra pakankamas tėvų apibrėžimas.

Čia yra būdas apsvarstyti vieną sprendimą ir taikyti pirmiau minėtą principą, kuris padės išvengti tokios klaidos. Būtina išbandyti hipotezę, patikrinant, ar visos patronuojančios klasės operacijos galioja ir struktūrinei, ir semantinei išvestinei klasei.

  • Ar futbolo rinktinėje yra futbolo žaidėjų sąrašas? (Visos sąrašo savybės yra tos pačios vertės komanda)
    • Ar komanda yra homogeniškų objektų kolekcija? Taip, komanda yra žaidėjų kolekcija.
    • Ar žaidėjų įtraukimo į komandos statusą aprašymas ir komanda užtikrina, kad seka būtų išsaugota be jokių akivaizdžių pokyčių? Ne ir ne
    • Ar tikimasi, kad žaidėjai bus įjungti / išjungti pagal jų nuoseklią poziciją komandoje? Ne

Kaip matote, komandai taikoma tik pirmoji sąrašo ypatybė. Todėl komanda nėra sąrašas. Sąraše bus pateikta informacija apie tai, kaip valdote savo komandą, todėl ji turėtų būti naudojama tik žaidėjų objektų saugojimui ir manipuliavimui juos naudojant komandos klasės metodus.

Šiuo metu norėčiau atkreipti dėmesį į tai, kad, mano nuomone, Komandų klasė net neturėtų būti įgyvendinama naudojant sąrašą; ji daugeliu atvejų turi būti įdiegta naudojant „Set data structure“ (pvz., „HashSet“).

46
11 февр. atsakymą pateikė Satyan Raina , vasario 11 d. 2014-02-11 19:56 '14, 19:56, 2014-02-11 19:56

Ką daryti, jei FootballTeam komanda kartu su pagrindine komanda turi atsargų komandą?

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

Kaip tai modeluoti?

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

Akivaizdu, kad santykiai yra ir nėra.

arba „ RetiredPlayers ?

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

Paprastai, jei kada nors norite paveldėti kolekciją, pavadinkite klasę „ SomethingCollection .

Ar jūsų SomethingCollection kolekcija yra semantinė? Padarykite tai tik tuo atveju, jei jūsų tipas yra Something .

FootballTeam tai skamba neteisingai. Team daugiau nei Collection . Team gali turėti trenerių, trenerių ir pan., Kaip rodo kiti atsakymai.

FootballCollection skamba kaip futbolo kamuoliukų kolekcija arba galbūt futbolo aksesuarų kolekcija. TeamCollection , komandų rinkinys.

FootballPlayerCollection skamba kaip žaidėjų kolekcija, kuri bus galiojantis tos klasės, kuri paveldi iš List<FootballPlayer> , pavadinimas, jei tikrai norėjote.

Iš tiesų, List<FootballPlayer> yra visiškai geras tipas. Gal IList<FootballPlayer> jei grąžinsite jį iš metodo.

Apibendrinant

Paklauskite savęs

  • Ar X a Y ? arba turi X a Y ?

  • Ar mano klasės pavadinimai žino, kas jie yra?

39
12 февр. Atsakymą davė Sam Leach , vasario 12 d. 2014-02-12 14:41 '14, 14:41 val. 2014-02-12 14:41

Pirmiausia tai yra dėl naudojimosi. Jei naudojatės paveldėjimu, Team klasė atskleis elgesį (metodus), skirtus tik manipuliuoti objektais. Pavyzdžiui, metodai AsReadOnly() arba CopyTo(obj) neturi prasmės komandų objektui. Vietoj „ AddRange(items) metodo jums tikriausiai reikės daugiau aprašomojo „ AddPlayers(players) metodo.

Jei norite naudoti LINQ, naudodami universalią sąsają, pvz., ICollection<T> arba IEnumerable<T> , bus prasmingesnė.

Kaip jau minėta, sudėtis yra teisingas kelias. Tiesiog įgyvendinkite žaidėjų sąrašą kaip privatų kintamąjį.

29
11 февр. Dmitrio S. atsakymas vasario 11 d 2014-02-11 06:25 '14 - 6:25 2014-02-11 06:25

Dizainas> Įgyvendinimas

Kokie yra jūsų pateikiami metodai ir savybės yra projektinis sprendimas. Kokia pagrindinė klasė, kurią paveldi, yra įgyvendinimo detalė. Manau, kad verta žengti pirmąjį žingsnį atgal.

Objektas yra duomenų ir elgesio rinkinys.

Taigi, jūsų pirmieji klausimai turėtų būti:

  • Kokie duomenys yra šiame objekte, kurį sukuriu?
  • Kokį elgesį šis objektas rodo šiame modelyje?
  • Kaip tai gali pasikeisti ateityje?

Turėkite omenyje, kad paveldėjimas reiškia isa (a) ryšį, o sudėtis reiškia santykius su (turi). Savo pristatyme pasirinkite tinkamą situaciją, atsižvelgiant į tai, kad situacija gali keistis, kai jūsų paraiška vystosi.

Apsvarstykite galimybę galvoti apie sąsajas prieš pradedant galvoti apie konkrečius tipus, nes kai kuriems žmonėms lengviau įdėti savo smegenis „dizaino režimu“.

Tai ne tai, ką kiekvienas kasdien sąmoningai atlieka kasdieniniame kodavime. Bet jei jūs galvojate apie tokią temą, jūs pateksite į projekto vandenis. Žinojimas apie tai gali būti išleidimas.

Apsvarstykite projektavimo specifikaciją.

Pažvelkite į <T> ir IList <T> sąrašą MSDN arba Visual Studio. Pažiūrėkite, kokius metodus ir savybes jie atskleidžia. Ar turite visus šiuos metodus, panašius į tai, ką norėtumėte daryti su „FootballTeam“?