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 (anint
). . NET nie zapewnia klasy do przechowywania drużyn piłkarskich, więc zrobię własną klasę. Najbardziej podobna i odpowiednia istniejąca struktura toList<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.
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?
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:
To sprawia, że mój kod jest niepotrzebnie gadatliwy. Muszę teraz zadzwonić
my_team.Players.Count
zamiast tylkomy_team.Count
. Na szczęście z C# mogę zdefiniować indeksy, aby indeksowanie było przejrzyste, i przekazać wszystkie metody wewnętrzneList
... Ale to dużo kodu! Co dostanę za tę całą pracę?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?
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? 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.
Nie do przyjęcia dla kogo? Ja? Nie.Czy dziedziczenie po
List<T>
jest zawsze niedopuszczalne?
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.
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
To jest właściwa droga. "Niepotrzebnie wordy" to zły sposób, aby na to patrzeć. Ma wyraźne znaczenie, gdy piszeszNa koniec niektórzy sugerują owinięcie listy w coś:
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.
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 robienievar 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 jakSortedCollection<T>
,ObservableCollection<T>
,ReadOnlyCollection<T>
, itd. każdy z nich implementujeIList<T>
ale nieList<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.
}
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:
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:
Tak wygląda twoja logika przedstawiona na zdjęciach...
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:
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.
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.
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.
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.
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
na
X
aY
? lub maX
aY
?Czy moje nazwy klasowe oznaczają, czym 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-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ą.
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
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ę
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
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
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.
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
}
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
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.
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.
[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.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 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.
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.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.
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.
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.
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ą.
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 ListWarning: 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.
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.
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.
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ą.
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.
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
{
...
}
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();
}
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
- 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>
.
IEnumerable<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.
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