Dlaczego nie dziedziczyć z listy?

Kiedy planuję swoje programy, często zaczynam od takiego łańcucha myśli:]}

Drużyna piłkarska to tylko lista piłkarzy. Dlatego powinienem reprezentować go za pomocą:

var football_team = new List<FootballPlayer>();

Kolejność tej listy reprezentuje kolejność, w jakiej gracze są wymienieni w spisie.

Ale zdaję sobie później sprawę, że zespoły mają również inne właściwości, poza zwykłą listą graczy, które muszą być rejestrowane. Na przykład suma wyników w tym sezonie aktualny budżet, jednolite kolory, string reprezentujące nazwę drużyny itp..

Więc myślę:

Dobra, drużyna piłkarska jest jak lista graczy, ale dodatkowo, ma nazwę (a string) i sumę wyników (an int). . NET nie zapewnia klasy do przechowywania drużyn piłkarskich, więc zrobię własną klasę. Najbardziej podobna i odpowiednia istniejąca struktura to List<FootballPlayer>, więc odziedziczę po niej:

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

Ale to okazuje się, że wytyczne mówią, że nie należy dziedziczyć z List<T>. Jestem całkowicie zdezorientowany tymi wytycznymi pod dwoma względami.

Dlaczego nie?

Najwyraźniej List jest w jakiś sposób zoptymalizowany pod kątem wydajności . Jak to? Jakie problemy z wydajnością spowoduję, jeśli przedłużę List? Co dokładnie się zepsuje?

[[31]}innym powodem, dla którego widziałem, jest to, że List jest dostarczany przez Microsoft i nie mam nad nim kontroli, więc nie mogę go później zmienić, po ujawnieniu " publicznego API " . Ale ciężko mi to zrozumieć. Co to jest publiczne API i dlaczego powinno mnie to obchodzić? Jeśli mój obecny projekt nie ma i prawdopodobnie nigdy nie będzie miał tego publicznego API, czy mogę bezpiecznie zignorować te wytyczne? Jeśli odziedziczę po List i okazuje się, że potrzebuję publicznego API, jakie trudności będę miał?

Jakie to ma znaczenie? Lista to lista. Co może się zmienić? Co mógłbym zmienić?

I na koniec, gdyby Microsoft nie chciał, żebym List, dlaczego nie zrobili klasy sealed?

Czego jeszcze mam użyć?

Najwyraźniej, dla kolekcji niestandardowych, Microsoft udostępnił klasę Collection, która powinna być rozszerzona zamiast List. Ale ta klasa jest bardzo naga i nie ma wielu przydatnych rzeczy, takich jak AddRange, na przykład. odpowiedź jvitor83 zapewnia uzasadnienie wydajności dla tej konkretnej metody, ale jak to jest wolne AddRange nie lepsze niż nie AddRange?

Dziedziczenie z Collection to o wiele więcej pracy niż dziedziczenie z List, i nie widzę żadnych korzyści. Na pewno Microsoft nie powiedziałby mi, żebym robił dodatkową pracę bez powodu, więc nie mogę przestać czuć się, jakbym coś źle zrozumiał, a dziedziczenie Collection w rzeczywistości nie jest właściwym rozwiązaniem dla mojego problemu.

Widziałem takie sugestie jak implementacja IList. Po prostu nie. To dziesiątki linii kodu, który nic mi nie da.

Na koniec niektórzy sugerują owijanie List w coś:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Są z tym dwa problemy:

  1. To sprawia, że mój kod jest niepotrzebnie gadatliwy. Muszę teraz zadzwonić my_team.Players.Count zamiast tylko my_team.Count. Na szczęście z C# mogę zdefiniować indeksy, aby indeksowanie było przejrzyste, i przekazać wszystkie metody wewnętrzne List... Ale to dużo kodu! Co dostanę za tę całą pracę?

  2. To po prostu nie ma sensu. Drużyna piłkarska nie " ma " listy zawodników. Informatyka to lista graczy. Nie mówi się "John McFootballer dołączył do niektórych graczy". Mówisz: "John dołączył do jakiegoś zespołu". Nie dodajesz litery do "znaków łańcucha", dodajesz literę do łańcucha. Nie dodajesz książki do książek biblioteki, dodajesz książkę do biblioteki.

Zdaję sobie sprawę, że to, co dzieje się "pod maską", można powiedzieć, że jest "dodawaniem X do wewnętrznej listy Y", ale wydaje się to bardzo intuicyjnym sposobem myślenia o świat.

Moje pytanie (streszczone)

Jaki jest prawidłowy sposób reprezentowania struktury danych w C#, która "logicznie" (to znaczy "dla ludzkiego umysłu") jest tylko list z things z kilkoma dzwonkami i gwizdkami?

Czy dziedziczenie z List<T> jest zawsze niedopuszczalne? Kiedy jest to dopuszczalne? Dlaczego/dlaczego nie? Co musi wziąć pod uwagę programista, decydując czy odziedziczyć po List<T> czy nie?
Author: Community, 0000-00-00

26 answers

Jest tu kilka dobrych odpowiedzi. Dodałbym do nich następujące punkty.

Jaki jest prawidłowy sposób reprezentowania struktury danych w C#, która " logicznie "(czyli" dla ludzkiego umysłu") jest tylko listą rzeczy z kilkoma wodotryskami?

Poproś wszystkich dziesięciu Nie-programistów, którzy są zaznajomieni z istnieniem piłki nożnej, aby wypełnili puste pole:

A football team is a particular kind of _____

Czy ktoś powiedział " lista piłkarzy z kilkoma dzwonkami i gwizdki", a może wszyscy mówili "drużyna sportowa", "klub" lub "organizacja"? Twoje wyobrażenie, że drużyna piłkarska jest szczególnym rodzajem listy graczy jest w Twoim ludzkim umyśle i tylko w Twoim ludzkim umyśle.

List<T> jest mechanizmem . Drużyna piłkarska jest obiektem biznesowym - to jest obiekt, który reprezentuje jakąś koncepcję, która znajduje się w domenie biznesowej programu. Nie mieszaj ich! Drużyna piłkarska jest rodzajem drużyny; ma a roster, a roster to lista graczy . Skład nie jest szczególnym rodzajem listy graczy . Spis to lista graczy. Więc zrób właściwość o nazwie Roster, która jest List<Player>. I zrób to, póki jesteś przy tym, chyba że wierzysz, że każdy, kto wie o drużynie piłkarskiej, może usunąć graczy z listy.

Czy dziedziczenie po List<T> jest zawsze niedopuszczalne?

Nie do przyjęcia dla kogo? Ja? Nie.

Kiedy jest to dopuszczalne?

Kiedy budujesz mechanizm, który rozszerza mechanizm List<T> .

Co musi wziąć pod uwagę programista, decydując czy odziedziczyć po List<T> czy nie?

Czy buduję mechanizm czy obiekt biznesowy ?

Ale to dużo kodu! Co dostanę za tę całą pracę?
Poświęciłeś więcej czasu na pisanie swojego pytania, które zajęłoby Ci aby napisać metody przekazywania dla odpowiednich członków List<T> pięćdziesiąt razy. Najwyraźniej nie boisz się zwięzłości, a mówimy tu o bardzo małej ilości kodu; to jest kilka minut pracy.

UPDATE

Przemyślałem to i jest jeszcze jeden powód, aby nie modelować drużyny piłkarskiej jako listy graczy. W rzeczywistości może być złym pomysłem modelowanie drużyny piłkarskiej jako posiadającej listę graczy. Problem z drużyną jako / posiadającą listę zawodników czy to, co masz to migawka zespołu w momencie. Nie wiem jaki jest Twój biznes dla tej klasy, ale gdybym miał klasę, która reprezentowała drużynę piłkarską, chciałbym zadać jej pytania typu " ilu zawodników Seahawks opuściło mecze z powodu kontuzji w latach 2003-2013?"albo" jaki zawodnik, który wcześniej grał w innej drużynie, miał największy rok do roku wzrost liczby rozegranych jardów?"lub" czy Piggersi przejechali całą drogę w tym roku?"

Oznacza to, że drużyna piłkarska wydaje mi się być dobrze modelowana jako zbiór faktów historycznych , takich jak czas rekrutacji zawodnika, kontuzja, emerytura itp. Oczywiście obecny skład graczy jest ważnym faktem, który prawdopodobnie powinien być z przodu iz centrum, ale mogą być inne interesujące rzeczy, które chcesz zrobić z tym obiektem, które wymagają bardziej historycznej perspektywy.

 1196
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 18:05:13

Na koniec niektórzy sugerują owinięcie listy w coś:

To jest właściwa droga. "Niepotrzebnie wordy" to zły sposób, aby na to patrzeć. Ma wyraźne znaczenie, gdy piszesz my_team.Players.Count. Chcesz policzyć zawodników.
my_team.Count

..nic nie znaczy. Policzyć co?

Drużyna nie jest listą - składa się z czegoś więcej niż tylko listy graczy. Drużyna jest właścicielem graczy, więc gracze powinni być jej częścią (członkiem).

If you ' re really worried about it being zbyt gadatliwe, zawsze można ujawnić właściwości od zespołu:

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

..który staje się:

my_team.PlayerCount
To ma znaczenie teraz i przestrzega prawa Demeter.

Należy również rozważyć przestrzeganie zasady ponownego użycia kompozytów. Dziedzicząc po List<T>, twierdzisz, że drużyna jest listą graczy i ujawniasz z niej niepotrzebne metody. Jest to niepoprawne - jak stwierdziłeś, zespół to coś więcej niż lista graczy: ma nazwę, menedżerów, członkowie zarządu, trenerzy, personel medyczny, limity wynagrodzeń itp. Jeśli twoja klasa drużyny zawiera listę graczy, mówisz ,że "drużyna ma listę graczy", ale może mieć również inne rzeczy.

 242
Author: Simon Whitehead,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-18 22:23:39

Wow, twój post ma całą masę pytań i punktów. Większość rozumowania, które otrzymujesz od Microsoftu, jest dokładnie na miejscu. Zacznijmy od wszystkiego o List<T>

  • List<T> jest wysoce zoptymalizowany. Jego głównym zastosowaniem jest użycie jako prywatnego członka obiektu.
  • Microsoft tego nie zapieczętował, ponieważ czasami możesz chcieć utworzyć klasę, która ma bardziej przyjazną nazwę: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Teraz jest to tak proste, jak robienie var list = new MyList<int, string>();.
  • CA1002: nie wystawiaj generic lists: zasadniczo, nawet jeśli planujesz korzystać z tej aplikacji jako jedyny deweloper, warto rozwijać się z dobrymi praktykami kodowania, aby stały się zaszczepione w Ciebie i second nature. Nadal możesz ujawnić listę jako IList<T>, jeśli chcesz, aby jakikolwiek konsument miał indeksowaną listę. To pozwoli Ci zmienić implementację w klasie później.
  • Microsoft uczynił Collection<T> bardzo ogólnym, ponieważ jest to ogólna koncepcja... nazwa mówi wszystko, to tylko kolekcja. Tam są bardziej precyzyjne wersje takie jak SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>, itd. każdy z nich implementuje IList<T> ale nie List<T>.
  • Collection<T> pozwala na dodawanie, usuwanie itd.) być nadpisane, ponieważ są wirtualne. Nie.
  • ostatnia część twojego pytania jest na miejscu. Drużyna piłkarska to coś więcej niż tylko lista graczy, więc powinna to być klasa, która zawiera tę listę graczy. Myśl skład a dziedziczenie . Drużyna piłkarska ma listę graczy (lista), nie jest to lista graczy.

Gdybym pisał ten kod, Klasa pewnie wyglądałaby tak:

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
Author: m-y,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-21 11:20:14
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

Poprzedni kod oznacza: Banda facetów z ulicy grających w piłkę nożną, i tak się składa, że mają imię. Coś w stylu:

Ludzie grający w piłkę nożną

W każdym razie ten kod (z odpowiedzi 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.
}

Oznacza: jest to drużyna piłkarska, która ma kierownictwo, zawodników, administratorów itp. Coś w stylu:

Obrazek przedstawiający członków (i inne atrybuty) drużyny Manchester United

Tak wygląda twoja logika przedstawiona na zdjęciach...

 105
Author: Nean Der Thal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-30 01:40:50

Jest to klasyczny przykład Skład vs dziedziczenie .

W tym konkretnym przypadku:

Jest to lista zawodników z dodanym zachowaniem

Lub

Czy drużyna jest własnym obiektem, który tak się składa, że zawiera listę graczy.

Rozszerzając listę ograniczasz się na kilka sposobów:

  1. Nie można ograniczyć dostępu(np. uniemożliwić osobom zmieniającym skład). Otrzymasz wszystkie metody listy, czy potrzebujesz / chcesz je wszystkie lub nie.

  2. Co się stanie, jeśli chcesz mieć listy innych rzeczy, jak również. Na przykład zespoły mają trenerów, menedżerów, fanów, sprzęt itp. Niektóre z nich mogą być same w sobie listami.

  3. Ograniczasz możliwości dziedziczenia. Na przykład możesz utworzyć ogólny obiekt Team, a następnie mieć BaseballTeam, FootballTeam itp. to dziedziczy po nim. Aby dziedziczyć z listy musisz zrobić dziedziczenie z zespołu, ale oznacza to, że wszystkie typy zespołów są zmuszone do tego samego wdrożenia tego harmonogramu.

Kompozycja-w tym obiekt nadający pożądane zachowanie wewnątrz obiektu.

Dziedziczenie-Twój obiekt staje się instancją obiektu, który ma pożądane zachowanie.

Oba mają swoje zastosowania, ale jest to oczywisty przypadek, w którym skład jest preferowany.

 84
Author: Tim B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-08 11:02:47

Jak wszyscy zauważyli, drużyna graczy nie jest listą graczy. Ten błąd jest popełniany przez wielu ludzi na całym świecie, być może na różnych poziomach wiedzy. Często problem jest subtelny i czasami bardzo brutto, jak w tym przypadku. Takie projekty są złe, ponieważ naruszają one zasadę substytucji Liskowa. W Internecie znajduje się wiele dobrych artykułów wyjaśniających to pojęcie, np. http://en.wikipedia.org/wiki/Liskov_substitution_principle

W podsumowaniu jest są dwie zasady, które należy zachować w relacji rodzic/dziecko między klasami:

  • dziecko nie powinno wymagać cech mniej niż to, co całkowicie definiuje rodzica.
  • rodzic nie powinien wymagać żadnej cechy oprócz tego, co całkowicie definiuje dziecko.

Innymi słowy, rodzic jest niezbędną definicją dziecka, a dziecko jest wystarczającą definicją rodzica.

Oto sposób na przemyślenie jednego rozwiązania i zastosowanie powyższego zasada, która powinna pomóc uniknąć takiego błędu. Należy przetestować hipotezę, sprawdzając, czy wszystkie operacje klasy macierzystej są ważne dla klasy pochodnej zarówno strukturalnie, jak i semantycznie.

  • czy drużyna piłkarska to lista piłkarzy? (Czy wszystkie właściwości listy odnoszą się do zespołu w tym samym znaczeniu)
    • czy zespół jest zbiorem jednorodnych Bytów? Tak, zespół jest zbiorem graczy
    • jest kolejność włączania zawodników opisująca stan zespołu i czy zespół zapewnia, że sekwencja jest zachowana, chyba że wyraźnie zmieniono? Nie, i nie
    • czy oczekuje się, że gracze zostaną włączeni/wyrzuceni na podstawie ich pozycji w zespole? Nie

Jak widzisz, tylko pierwsza cecha listy ma zastosowanie do zespołu. Dlatego zespół nie jest listą. Lista byłaby szczegółem implementacji sposobu zarządzania zespołem, więc powinna być używana tylko do przechowywania obiektów gracza i być manipulowane metodami klasy zespołowej.

W tym miejscu chciałbym zauważyć, że Klasa Team nie powinna, moim zdaniem, być nawet zaimplementowana przy użyciu listy; powinna być zaimplementowana przy użyciu zestawu struktury danych (na przykład HashSet) w większości przypadków.

 46
Author: Satyan Raina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 17:05:34

Co jeśli FootballTeam ma drużynę rezerw wraz z drużyną główną?

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

Jak byś to wymodelował?

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

Związek jest wyraźnie ma , a nie jest.

Czy RetiredPlayers?

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

Zgodnie z zasadą, jeśli kiedykolwiek chcesz dziedziczyć z kolekcji, nazwij klasę SomethingCollection.

Czy twój SomethingCollection semantycznie ma sens? Zrób to tylko wtedy, gdy twój typ jest zbiorem Something.

W przypadku To nie brzmi dobrze. A Team jest czymś więcej niż a Collection. A {[9] } może mieć trenerów, trenerów itp., jak wskazywały inne odpowiedzi.

FootballCollection brzmi jak kolekcja piłek, a może kolekcja akcesoriów piłkarskich. TeamCollection, zbiór drużyn.

FootballPlayerCollection brzmi jak kolekcja graczy, która byłaby poprawną nazwą dla klasy, która dziedziczy po List<FootballPlayer>, jeśli naprawdę chcesz to zrobić.

Naprawdę List<FootballPlayer> jest idealnym typem do czynienia z. Może IList<FootballPlayer> jeśli zwracasz go z metody.

W podsumowaniu

Zapytaj siebie

  1. na X a Y? lub ma X a Y?

  2. Czy moje nazwy klasowe oznaczają, czym są?

 39
Author: Sam Leach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-03 10:59:59

Po pierwsze, ma to związek z użytecznością. Jeśli używasz dziedziczenia, klasa Team ujawni zachowanie (metody), które są przeznaczone wyłącznie do manipulacji obiektami. Na przykład metody AsReadOnly() lub CopyTo(obj) nie mają sensu dla obiektu team. Zamiast metody AddRange(items) prawdopodobnie potrzebujesz bardziej opisowej metody AddPlayers(players).

Jeśli chcesz używać LINQ, implementacja ogólnego interfejsu, takiego jak ICollection<T> lub IEnumerable<T>, miałaby większy sens.

Jak już wspomniano, kompozycja jest właściwą drogą do śmiało. Wystarczy zaimplementować listę graczy jako prywatną zmienną.

 29
Author: Dmitry S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 03:25:28

Projekt > Realizacja

Jakie metody i właściwości ujawniasz jest decyzją projektową. To, z jakiej klasy bazowej dziedziczysz, jest szczegółem implementacji. Czuję, że warto cofnąć się do tego pierwszego.

Obiekt jest zbiorem danych i zachowań.

Więc twoje pierwsze pytania powinny być:

  • jakie dane zawiera ten obiekt w tworzonym przeze mnie modelu?
  • jakie zachowanie obiekt wykazuje w tym modelu?
  • Jak to zmiany w przyszłości?

Należy pamiętać, że dziedziczenie implikuje relację" isa "(jest), podczas gdy kompozycja implikuje relację " ma " (hasa). Wybierz odpowiedni dla swojej sytuacji, biorąc pod uwagę, gdzie rzeczy mogą pójść w miarę rozwoju aplikacji.

Rozważ myślenie w interfejsach, zanim pomyślisz w konkretnych typach, ponieważ niektórym łatwiej jest umieścić swój mózg w" trybie projektowania " w ten sposób.

To nie jest coś, co każdy robi świadomie na tym poziomie w codziennym kodowaniu. Ale jeśli rozmyślasz nad tym rodzajem tematu, stąpasz po wodach projektowych. Świadomość tego może być wyzwalająca.

Rozważmy Specyfikę Projektu

Spójrz na List i IList na MSDN lub Visual Studio. Zobacz, jakie metody i właściwości ujawniają. Czy te metody wyglądają jak coś, co ktoś chciałby zrobić z drużyną piłkarską Twoim zdaniem?

Czy footballTeam.Reverse() ma dla ciebie sens? Czy footballTeam.ConvertAll ()

To nie jest podchwytliwe pytanie; odpowiedź może być naprawdę "tak". Jeśli zaimplementujesz / odziedziczysz listę lub IList, utkniesz z nimi; jeśli jest to idealne rozwiązanie dla Twojego modelu, zrób to.

Jeśli zdecydujesz się na tak, to ma to sens i chcesz, aby twój obiekt był traktowany jako kolekcja/lista graczy (zachowanie), a zatem chcesz zaimplementować kolekcję lub listę, zrób to za wszelką cenę. "Type": "content"]}

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

Jeśli chcesz, aby twój obiekt zawierał kolekcję / listę graczy( dane), a zatem chcesz, aby kolekcja lub lista była własnością lub członkiem, zrób to za wszelką cenę. "Type": "content"]}

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

Możesz czuć, że chcesz, aby ludzie mogli tylko wyliczać zestaw graczy, zamiast ich liczyć, dodawać do nich lub usuwać. IEnumerable jest całkowicie poprawną opcją do rozważenia.

Możesz czuć, że żaden z tych interfejsów nie jest przydatny w Twoim model w ogóle. Jest to mniej prawdopodobne (IEnumerable jest przydatne w wielu sytuacjach), ale nadal jest to możliwe.

Każdy, kto próbuje ci powiedzieć, że jedno z nich jest kategorycznie i definitywnie błędne w każdym przypadku jest błędne. Każdy, kto próbuje ci to powiedzieć jest kategorycznie i definitywnie racja w każdym przypadku jest błędny.

Przejdź do realizacji

Gdy już zdecydujesz się na dane i zachowanie, możesz podjąć decyzję o wdrożenie. Obejmuje to konkretne klasy, które zależą od dziedziczenia lub kompozycji.

To może nie być duży krok, a ludzie często łączą projekt i wdrożenie, ponieważ jest całkiem możliwe, aby przejrzeć to wszystko w głowie w sekundę lub dwie i zacząć pisać.

Eksperyment Myślowy

Sztuczny przykład: jak już wspomnieli inni, drużyna nie zawsze jest "tylko" zbiorem graczy. Czy przechowujesz zbiór wyników meczów dla drużyny? Czy w twoim modelu drużyna jest wymienna z klubem? Jeśli tak, a jeśli twój zespół jest zbiorem graczy, być może jest to również zbiorem personelu i / lub zbiorem wyników. Wtedy kończysz na:

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

Niezależnie od projektu, w tym momencie w C# nie będziesz w stanie zaimplementować ich wszystkich poprzez dziedziczenie z listy i tak, ponieważ C# "tylko" obsługuje pojedyncze dziedziczenie. (Jeśli próbowałeś tego malarky w C++, możesz uznać to za dobrą rzecz.) Realizacja jednego zbioru poprzez dziedziczenie i jeden poprzez kompozycję może czuć brudne. I właściwości takie jak Count stają się mylące dla użytkowników, chyba że zaimplementujesz ILIst .Count i IList.Liczenie itp. wyraźnie, a potem są po prostu bolesne, a nie mylące. Możesz zobaczyć, dokąd to zmierza; przeczucie podczas myślenia w dół tej drogi może Ci powiedzieć, że źle jest zmierzać w tym kierunku (i słusznie lub niesłusznie, twoi koledzy mogą również, jeśli wdrożysz to w to A niech to!)

Krótka Odpowiedź (Za Późno)

Wytyczne dotyczące Nie dziedziczenia z klas kolekcji nie są specyficzne dla C#, znajdziesz je w wielu językach programowania. Otrzymuje się mądrość, a nie prawo. Jednym z powodów jest to, że w praktyce uważa się, że kompozycja często wygrywa z dziedziczeniem pod względem zrozumiałości, implementacji i konserwacji. Z obiektami świata rzeczywistego / domeny jest bardziej powszechne znajdowanie użytecznych i spójnych relacji "hasa" niż użytecznych i spójne relacje "isa", chyba że jesteś głęboko w abstrakcji, zwłaszcza w miarę upływu czasu i dokładnych danych i zachowania obiektów w kodzie zmienia. Nie powinno to powodować, że zawsze wykluczasz dziedziczenie z klas kolekcji; ale może to być sugestywne.

 27
Author: El Zorko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 22:27:53

Drużyna piłkarska nie jest listą piłkarzy. Drużyna piłkarska składa się z listy piłkarzy!

To jest logicznie błędne:

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

I to jest poprawne:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}
 24
Author: sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 16:25:06

To zależy od kontekstu

Kiedy rozważasz swoją drużynę jako listę graczy, rzutujesz "ideę" drużyny piłki nożnej do jednego aspektu: redukujesz "drużynę" do ludzi, których widzisz na boisku. Ta projekcja jest poprawna tylko w pewnym kontekście. W innym kontekście może to być całkowicie błędne. Wyobraź sobie, że chcesz zostać sponsorem zespołu. Więc musisz porozmawiać z menedżerami zespołu. W tym kontekście zespół jest projektowany na listę swoich menedżerów. A te dwie listy zwykle nie pokrywają się zbytnio. Innymi kontekstami są obecni kontra byli gracze itp.

Niejasna semantyka

Problem z traktowaniem drużyny jako listy graczy polega na tym, że jej semantyka zależy od kontekstu i że nie można jej rozszerzyć, gdy kontekst się zmieni. Dodatkowo trudno jest wyrazić, jakiego kontekstu używasz.

Klasy są rozszerzalne

Kiedy używasz klasy z tylko jednym członkiem (np. IList activePlayers), możesz użyj nazwy członka (i dodatkowo jego komentarza), aby wyjaśnić kontekst. Gdy istnieją dodatkowe konteksty, wystarczy dodać dodatkowego członka.

Klasy są bardziej złożone

W niektórych przypadkach utworzenie dodatkowej klasy może być przesadą. Każda definicja klasy musi być załadowana przez classloader i będzie buforowana przez maszynę wirtualną. To kosztuje wydajność pracy i pamięć. Gdy masz bardzo specyficzny kontekst, może być OK, aby rozważyć drużynę piłkarską jako lista zawodników. Ale w tym przypadku, naprawdę powinieneś użyć IList , a nie Klasy pochodzącej z niego.

Wnioski / Rozważania

Gdy masz bardzo specyficzny kontekst, można uznać drużynę za listę graczy. Na przykład wewnątrz metody zapis

IList<Player> footballTeam = ...

Podczas używania F# można nawet utworzyć skrót typu

type FootballTeam = IList<Player>

Ale kiedy kontekst jest szerszy lub nawet niejasny, nie powinieneś tego robić. Jest to szczególnie, podczas tworzenia nowej klasy, gdzie nie jest jasne, w jakim kontekście może być używana w przyszłości. Znakiem ostrzegawczym jest rozpoczęcie dodawania dodatkowych atrybutów do swojej klasy (nazwa drużyny, trener itp.). Jest to wyraźny znak, że kontekst, w którym klasa będzie używana, nie jest stały i zmieni się w przyszłości. W takim przypadku nie można uznać drużyny za listę zawodników, ale należy modelować listę (aktualnie aktywnych, Nie kontuzjowanych, itp.) graczy jako atrybut drużynowo

 20
Author: stefan.schwetschke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-12 16:57:31

Pozwól, że przepiszę twoje pytanie. więc możesz zobaczyć Temat z innej perspektywy.

Kiedy muszę reprezentować drużynę piłkarską, rozumiem, że jest to w zasadzie nazwa. Like: "The Eagles"
string team = new string();

Potem zdałem sobie sprawę, że drużyny również mają graczy.

Dlaczego nie mogę po prostu rozszerzyć typu string tak, że zawiera on również listę graczy?

Twój punkt wejścia w problem jest arbitralny. Spróbuj pomyśleć, co ma drużyna (właściwości), a nie co to jest .

Gdy to zrobisz, możesz sprawdzić, czy dzieli właściwości z innymi klasami. I pomyśl o dziedziczeniu.

 19
Author: user1852503,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-17 01:09:25

Tylko dlatego, że myślę, że inne odpowiedzi dość dużo iść na styczną, czy drużyna piłkarska "jest-a" List<FootballPlayer> czy "ma-a" List<FootballPlayer>, co naprawdę nie odpowiada na to pytanie, jak napisane.

Po prosi o wyjaśnienie wytycznych dotyczących dziedziczenia z List<T>:

Wytyczne mówią, że nie powinieneś dziedziczyć po List<T>. Dlaczego nie?

Ponieważ List<T> nie ma metod wirtualnych. Jest to mniejszy problem w twoim własnym kodzie, ponieważ zazwyczaj możesz Przełącz implementację ze stosunkowo niewielkim bólem - ale może to być znacznie większa sprawa w publicznym API.

Co to jest publiczne API i dlaczego powinno mnie to obchodzić?

[13]}publiczne API jest interfejsem, który można ujawnić programistom zewnętrznym. Myśl o kodzie ramowym. Przypomnijmy, że wytyczne, do których się odwołujemy, to". net Framework Design Guidelines", a nie".NET Application Design Guidelines". Jest różnica i-ogólnie rzecz biorąc - projektowanie publicznych API jest o wiele bardziej rygorystyczne.

Jeśli mój obecny projekt nie ma i prawdopodobnie nigdy nie będzie miał tego publicznego API, czy mogę bezpiecznie zignorować te wytyczne? Jeśli dziedziczę z listy i okaże się, że potrzebuję publicznego API, jakie będę miał trudności?

Mniej więcej tak. Możesz rozważyć jego uzasadnienie, aby sprawdzić, czy i tak odnosi się do twojej sytuacji, ale jeśli nie budujesz publicznego API, nie musisz się szczególnie martwić o API problemy takie jak wersjonowanie (z czego jest to podzbiór).

Jeśli dodasz publiczne API w przyszłości, będziesz musiał albo usunąć swoje API ze swojej implementacji (nie ujawniając bezpośrednio swojego List<T>) lub naruszyć wytyczne z możliwym przyszłym bólem, który się z tym wiąże.

Jakie to ma znaczenie? Lista to lista. Co może się zmienić? Co mógłbym zmienić?

Zależy od kontekstu, ale ponieważ używamy FootballTeam jako przykład-wyobraź sobie, że nie możesz dodać FootballPlayer, jeśli spowodowałoby to przekroczenie limitu wynagrodzeń przez zespół. Możliwy sposób dodania tego byłoby coś w stylu:

 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...ale nie możesz override Add, ponieważ nie jest to virtual (ze względu na wydajność).

Jeśli jesteś w aplikacji (co w zasadzie oznacza, że ty i wszyscy Twoi rozmówcy jesteście kompilowani razem), możesz teraz zmienić użycie IList<T> i naprawić błędy kompilacji:

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

Ale, jeśli publicznie ujawnił do 3rd party po prostu dokonane zmiany łamiące, które spowodują kompilacji i / lub błędy uruchomieniowe.

TL;DR-the guidelines are for public API. W przypadku prywatnych API rób, co chcesz.

 15
Author: Mark Brackett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-06 21:35:20

Pozwala ludziom mówić

myTeam.subList(3, 5);
Czy to ma jakiś sens? Jeśli nie, to nie powinna to być Lista.
 14
Author: Cruncher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 21:42:45

Zależy to od zachowania Twojego obiektu "team". Jeśli zachowuje się jak kolekcja, może być w porządku reprezentować ją najpierw za pomocą zwykłej listy. Wtedy możesz zacząć zauważać, że ciągle duplikujesz kod, który jest powtarzany na liście; w tym momencie masz możliwość utworzenia obiektu FootballTeam, który zawija listę graczy. Klasa FootballTeam staje się domem dla całego kodu, który powtarza się na liście graczy.

To sprawia, że mój kod niepotrzebnie gadatliwy. Muszę zadzwonić do my_team.Graczy.Policz zamiast tylko my_team.Licz. Na szczęście dzięki C# mogę zdefiniować indeksy, aby indeksowanie było przejrzyste, i przekazać wszystkie metody wewnętrznej listy... Ale to dużo kodu! Co dostanę za tę całą pracę?

Enkapsulacja. Twoi klienci nie muszą wiedzieć, co dzieje się wewnątrz FootballTeam. Wszyscy twoi klienci wiedzą, że może to zostać zaimplementowane przez wyszukanie listy graczy w bazie danych. Nie muszą wiedzieć, a to ulepsza Twój projekt.

To po prostu nie ma sensu. Drużyna piłkarska nie " ma " listy zawodników. Jest to lista zawodników. Nie mówi się "John McFootballer dołączył do niektórych graczy". Mówisz: "John dołączył do jakiegoś zespołu". Nie dodajesz litery do "znaków łańcucha", dodajesz literę do łańcucha. Nie dodajesz książki do książek biblioteki, dodajesz książkę do biblioteki.

Dokładnie:) powiesz footballTeam.Add (john), not footballTeam.Lista.Add (john). Wewnętrzna lista nie będzie widoczna.

 13
Author: xpmatteo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 16:51:21

Jest tu wiele doskonałych odpowiedzi, ale chcę dotknąć czegoś, o czym nie wspomniałem: projektowanie obiektowe jest o wzmacnianie obiektów.

Chcesz zawrzeć wszystkie swoje zasady, dodatkową pracę i wewnętrzne szczegóły wewnątrz odpowiedniego obiektu. W ten sposób inne obiekty wchodzące w interakcję z tym nie muszą się martwić o to wszystko. W rzeczywistości, chcesz pójść o krok dalej i aktywnie zapobiec innym obiektom z pominięciem tych wewnętrzne.

Gdy dziedziczysz z List, wszystkie inne obiekty mogą widzieć cię jako listę. Mają bezpośredni dostęp do metod dodawania i usuwania graczy. I stracisz kontrolę; na przykład:

Załóżmy, że chcesz rozróżnić, Kiedy gracz odchodzi, wiedząc, czy przeszedł na emeryturę, zrezygnował lub został zwolniony. Można zaimplementować metodę RemovePlayer, która pobiera odpowiednią liczbę danych wejściowych. Jednak dziedzicząc z List, nie będziesz w stanie zapobiec bezpośredniemu dostępowi do Remove, RemoveAll a nawet Clear. W rezultacie, masz rzeczywiście disempowered twoja klasa.


[[11]}dodatkowe przemyślenia na temat enkapsulacji... Zgłosiłeś następujące obawy:
To sprawia, że mój kod jest niepotrzebnie gadatliwy. Muszę zadzwonić do my_team.Graczy.Policz zamiast tylko my_team.Licz.

Masz rację, to byłoby niepotrzebnie gadatliwe dla wszystkich klientów, aby używać Twojego zespołu. Problem ten jest jednak bardzo mały w porównaniu z faktem że naraziłeś się wszystkim, żeby mogli bawić się z Twoim zespołem bez Twojej zgody.

Mówisz:

To po prostu nie ma sensu. Drużyna piłkarska nie " ma " listy zawodników. Jest to lista zawodników. Nie mówi się "John McFootballer dołączył do niektórych graczy". Mówisz: "John dołączył do jakiegoś zespołu".

Mylisz się co do pierwszego bitu: porzuć słowo "lista" i jest oczywiste, że zespół ma graczy.
Jednak trafiasz w sedno z drugim. Nie chcesz, żeby klienci dzwonili ateam.Players.Add(...). Chcesz, żeby zadzwonili ateam.AddPlayer(...). A twoja implementacja (prawdopodobnie między innymi) wywoła Players.Add(...) wewnętrznie.


Mam nadzieję, że widzisz, jak ważna jest enkapsulacja dla celu, jakim jest wzmocnienie twoich obiektów. Chcesz pozwolić każdej klasie dobrze wykonywać swoją pracę bez obawy o ingerencję ze strony innych obiektów.

 11
Author: Disillusioned,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-15 20:05:59

Jaki jest poprawny C # sposób przedstawiania struktury danych...

Pamiętaj: "wszystkie modele są błędne, ale niektóre są przydatne."- George E. P. Box

Nie ma "poprawnego sposobu", tylko użytecznego.

Wybierz taki, który jest przydatny dla Ciebie i / Twoich użytkowników. To wszystko. Rozwijaj się ekonomicznie, nie przesadzaj z inżynierią. Im mniej kodu napiszesz, tym mniej kodu będziesz musiał debugować. (przeczytaj poniższe wydania).

-- Edited

Moją najlepszą odpowiedzią będzie... to zależy. Dziedziczenie z listy naraziłoby klientów tej klasy na metody, które mogą nie być narażone, przede wszystkim dlatego, że FootballTeam wygląda jak podmiot gospodarczy.

-- Wydanie 2

Szczerze nie pamiętam, o co mi chodziło w komentarzu "nie przesadzaj". Chociaż uważam, że podejście KISS jest dobrym przewodnikiem, chcę podkreślić, że dziedziczenie klasy biznes z listy stworzy więcej problemy niż rozwiązuje, z powodu wycieku abstrakcji .

Z drugiej strony, uważam, że istnieje ograniczona liczba przypadków, w których po prostu dziedziczenie z listy jest przydatne. Jak pisałem w poprzednim wydaniu, to zależy. Odpowiedź na każdy przypadek zależy zarówno od wiedzy, doświadczenia, jak i osobistych preferencji.

Dzięki @kai za pomoc w dokładniejszym przemyśleniu odpowiedzi.

 11
Author: Arturo Tena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 22:36:20

To przypomina mi" jest "kontra" ma " kompromis. Czasami łatwiej jest i sprawia, żeWięcej sensu dziedziczyć bezpośrednio z super klasy. Innym razem bardziej sensowne jest utworzenie samodzielnej klasy i dołączenie klasy, którą odziedziczyłbyś jako zmienną członkowską. Nadal możesz uzyskać dostęp do funkcjonalności klasy, ale nie jesteś związany z interfejsem ani innymi ograniczeniami, które mogą wynikać z dziedziczenia po klasie.

Czym się zajmujesz? Jak z wieloma things...it zależy od kontekstu. Przewodnikiem, którego użyłbym jest to, że aby odziedziczyć po innej klasie, naprawdę powinna istnieć relacja "jest". Więc jeśli piszesz klasę o nazwie BMW, może ona dziedziczyć z samochodu, ponieważ BMW naprawdę jest samochodem. Klasa koni może dziedziczyć od klasy ssaków, ponieważ koń jest w rzeczywistości ssakiem, a każda funkcjonalność ssaka powinna być istotna dla konia. Ale czy możesz powiedzieć, że drużyna to lista? Z tego co wiem, to nie wygląda na zespół. naprawdę" to " lista. Więc w tym przypadku miałbym listę jako zmienną członkowską.

 10
Author: Paul J Abernathy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 14:56:27

Mój brudny sekret: nie obchodzi mnie, co ludzie mówią, a ja to robię. . NET Framework jest rozpowszechniany za pomocą "XxxxCollection" (UIElementCollection dla przykładu top of My head).

Więc co mnie powstrzymuje:

team.Players.ByName("Nicolas")

Kiedy Uznam to za lepsze niż

team.ByName("Nicolas")

Co więcej, mój PlayerCollection może być używany przez inne klasy, takie jak" Club " bez powielania kodu.

club.Players.ByName("Nicolas")

Najlepsze praktyki dnia wczorajszego, mogą nie być tymi jutrzejszymi. Nie ma powodu, dla którego większość najlepszych praktyk, większość są tylko szerokie porozumienie wśród społeczności. Zamiast pytać społeczność, czy będzie cię winić, gdy to zrobisz, zadaj sobie pytanie, co jest bardziej czytelne i możliwe do utrzymania?

team.Players.ByName("Nicolas") 

Lub

team.ByName("Nicolas")
Naprawdę. Masz jakieś wątpliwości? Teraz może trzeba grać z innymi ograniczeniami technicznymi, które uniemożliwiają użycie List w prawdziwym przypadku użycia. Ale nie dodawaj ograniczeń, które nie powinny istnieć. Jeśli Microsoft nie udokumentował dlaczego, to z pewnością jest to" najlepsza praktyka " pochodząca z nigdzie.
 8
Author: Nicolas Dorier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-17 01:11:59

Wytyczne mówią, że publiczne API nie powinno ujawniać wewnętrznej decyzji projektowej, czy używasz listy, zestawu, słownika, drzewa czy czegokolwiek innego. "Zespół" niekoniecznie jest listą. Możesz zaimplementować ją jako listę, ale użytkownicy twojego publicznego API powinni używać twojej klasy na zasadzie potrzeby wiedzy. Pozwala to na zmianę decyzji i korzystanie z innej struktury danych bez wpływu na publiczny interfejs.

 8
Author: QuentinUK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-18 02:53:22

Kiedy mówią List<T> jest "zoptymalizowany", myślę, że chcą powiedzieć, że nie ma funkcji takich jak wirtualne metody, które są nieco droższe. Problem polega więc na tym, że gdy raz wystawisz List<T> w swoim publiczne API, tracisz możliwość egzekwowania reguł biznesowych lub dostosowywania ich funkcjonalności później. Ale jeśli używasz tej odziedziczonej klasy jako wewnętrznej w swoim projekcie (w przeciwieństwie do potencjalnie narażonych na tysiące klientów/partnerów/innych zespołów jako API), może to być bądź w porządku, jeśli oszczędza twój czas i jest to funkcjonalność, którą chcesz zduplikować. Zaletą dziedziczenia z List<T> jest to, że eliminujesz wiele głupich kodów opakowaniowych, które nigdy nie zostaną dostosowane w przewidywalnej przyszłości. Również jeśli chcesz, aby Twoja klasa miała dokładnie taką samą semantykę jak List<T> przez całe życie Twoich API, to może być OK.

Często widzę wiele osób robi mnóstwo dodatkowej pracy tylko z powodu zasady FxCop tak mówi lub czyjś blog mówi, że to " zły" praktyka. Wiele razy, to zamienia kod do projektowania wzorca Palooza dziwactwa. Podobnie jak w przypadku wielu wytycznych, traktuj je jako wytyczne, że Może mieć wyjątki.

 5
Author: ShitalShah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-16 07:50:31

Jeśli użytkownicy Twojej klasy potrzebują wszystkich metod i właściwości** List, powinieneś wyprowadzić z niej swoją klasę. Jeśli ich nie potrzebują, załącz listę i stwórz wrappery dla metod, których potrzebują użytkownicy Twojej klasy.

Jest to ścisła zasada, jeśli piszesz public API , lub jakikolwiek inny kod, który będzie używany przez wiele osób. Możesz zignorować tę zasadę, Jeśli masz małą aplikację i nie więcej niż 2 programistów. To zaoszczędzi ci trochę czasu.

Dla małych aplikacji, można również rozważyć wybór innego, mniej surowego języka. Ruby, JavaScript-wszystko, co pozwala na pisanie mniej kodu.

 3
Author: Ivan Nikitin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-16 07:59:49

Chciałem tylko dodać, że Bertrand Meyer, wynalazca Eiffla i projektu na podstawie umowy, miałby Team odziedziczyć po List<Player> bez mrugnięcia powieką.

W swojej książce, Object-Oriented Software Construction, omawia implementację systemu GUI, w którym prostokątne okna mogą mieć okna potomne. Po prostu ma Window dziedziczenie z Rectangle i Tree<Window>, Aby ponownie wykorzystać implementację.

Jednak C# nie jest Eifflem. Ten ostatni obsługuje wiele dziedziczenie i zmiana nazwy funkcji . W C#, gdy podklasujesz, dziedziczysz Zarówno interfejs, jak i implementację. Można nadpisać implementację, ale wywołujące ją konwencje są kopiowane bezpośrednio z klasy nadrzędnej. W Eiffel możesz jednak zmodyfikować nazwy publicznych metod, dzięki czemu możesz zmienić nazwy Add i Remove na Hire i Fire w swoim Team. Jeśli instancja Team jest upcast z powrotem do List<Player>, wywołujący użyje Add i Remove, aby ją zmodyfikować, ale twoje metody wirtualne Hire i Fire zostaną wywołane.

 3
Author: Alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-27 11:35:13

Chociaż nie mam skomplikowanego porównania, jak większość z tych odpowiedzi, chciałbym podzielić się moją metodą radzenia sobie z tą sytuacją. Rozszerzając IEnumerable<T>, możesz zezwolić swojej klasie Team na obsługę rozszerzeń zapytań Linq, bez publicznego ujawniania wszystkich metod i właściwości 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
Author: Null511,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-20 02:43:24

Chyba nie zgadzam się z Twoim uogólnieniem. Drużyna to nie tylko kolekcja graczy. Zespół ma o wiele więcej informacji na jego temat - nazwę, emblemat, zbiór kadry zarządzającej/administracyjnej, zbiór załogi trenerskiej, a następnie zbiór zawodników. Tak właściwie, twoja klasa FootballTeam powinna mieć 3 Kolekcje, a nie sama być kolekcją; jeśli ma prawidłowo modelować świat realny.

Można rozważyć klasę PlayerCollection, która podobnie jak specjalistyczne StringCollection oferuje inne funkcje - takie jak Walidacja i sprawdzanie przed dodaniem lub usunięciem obiektów ze sklepu wewnętrznego.

Być może, pojęcie playercollection betters pasuje do Twojego preferowanego podejścia?

public class PlayerCollection : Collection<Player> 
{ 
}

A potem Piłka nożna może wyglądać tak:

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
Author: Eniola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-23 15:55:34

Preferuj Interfejsy zamiast klas

Klasy powinny unikać wyprowadzania z klas i zamiast tego implementować minimalne niezbędne interfejsy.

Dziedziczenie przerywa enkapsulację

Wyprowadzenie z klas przerywa enkapsulację :

  • ujawnia wewnętrzne szczegóły dotyczące implementacji kolekcji
  • deklaruje interfejs (zbiór publicznych funkcji i właściwości), które mogą nie być odpowiednie

Między innymi to utrudnia refaktoryzację kodu.

Klasy są szczegółami implementacji

Klasy są szczegółami implementacji, które powinny być ukryte przed innymi częściami kodu. W skrócie a System.List jest specyficzną implementacją abstrakcyjnego typu danych, która może być lub nie być odpowiednia teraz i w przyszłości.

Koncepcyjnie fakt, że System.List typ danych nazywa się "list" jest trochę czerwony-śledź. A System.List<T> jest zmienną uporządkowaną kolekcją, która obsługuje o (1) operacje dodawania, wstawiania i usuwania elementów oraz o(1) operacje pobierania liczby elementów lub uzyskiwania i ustawiania elementu według indeksu.

Im mniejszy interfejs, tym bardziej elastyczny kod

Przy projektowaniu struktury danych, im prostszy jest interfejs, tym bardziej elastyczny jest kod. Wystarczy spojrzeć na to, jak potężne jest LINQ dla demonstracji tego.

Jak wybrać Interfejsy

Kiedy myślisz, że "lista" powinieneś zacząć mówiąc sobie: "muszę reprezentować kolekcję baseballistów". Więc powiedzmy, że zdecydujesz się na modelowanie tego z klasą. Najpierw powinieneś zdecydować, jaka jest minimalna ilość interfejsów, które ta klasa będzie musiała ujawnić.

Kilka pytań, które mogą pomóc w tym procesie:

    Czy muszę liczyć? Jeśli nie rozważ wdrożenia IEnumerable<T>
  • czy ta kolekcja zmieni się po jej zainicjowaniu? Jeśli nie wziąć pod uwagę IReadonlyList<T>.
  • czy to ważne, że mogę uzyskać dostęp do elementów według indeksu? Rozważ ICollection<T>
  • czy kolejność dodawania przedmiotów do kolekcji jest ważna? Może jest to ISet<T>?
  • jeśli rzeczywiście chcesz te rzeczy to śmiało i wdrożyć IList<T>.

W ten sposób nie będziesz łączył innych części kodu ze szczegółami implementacji Twojej kolekcji graczy baseballowych i będziesz mógł dowolnie zmieniać sposób implementacji o ile szanujesz interfejs.

Dzięki takiemu podejściu zauważysz, że kod staje się łatwiejszy do odczytania, refaktoryzacji i ponownego użycia.

Uwagi o unikaniu kotła

Implementacja interfejsów w nowoczesnym IDE powinna być łatwa. Kliknij prawym przyciskiem myszy i wybierz "zaimplementuj interfejs". Następnie prześlij wszystkie implementacje do klasy członkowskiej, jeśli zajdzie taka potrzeba.

To powiedziawszy, jeśli stwierdzisz, że piszesz dużo kotła, to potencjalnie dlatego, że ujawniasz więcej funkcji niż powinno być. Z tego samego powodu nie powinieneś dziedziczyć po klasie.

Możesz również zaprojektować mniejsze interfejsy, które mają sens dla Twojej aplikacji, a może tylko kilka pomocniczych funkcji rozszerzeń, aby mapować te interfejsy do innych, których potrzebujesz. Takie podejście zastosowałem w moim własnym interfejsie IArray dla Biblioteki LinqArray.

 0
Author: cdiggins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-07 05:01:01