Rozważ interfejs:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
Interfejs ten jest implementowany przez wiele klas, które generują fale o różnych kształtach (na przykład SineWaveGenerator
i SquareWaveGenerator
).
Chcę zaimplementować klasę, która generuje SoundWave
dane muzyczne, a nie surowe dane dźwiękowe. Otrzyma nazwę nuty i długość wyrażoną w uderzeniach (nie sekundach) i wewnętrznie użyje tej IWaveGenerator
funkcji, aby odpowiednio ją utworzyć SoundWave
.
Pytanie brzmi: czy powinien NoteGenerator
zawierać IWaveGenerator
lub powinien dziedziczyć z IWaveGenerator
implementacji?
Skłaniam się do kompozycji z dwóch powodów:
1- Pozwala mi IWaveGenerator
to NoteGenerator
dynamicznie wstrzykiwać dowolne . Ponadto, muszę tylko jedną NoteGenerator
klasę, zamiast SineNoteGenerator
, SquareNoteGenerator
itp
2- Nie ma potrzeby NoteGenerator
ujawniania interfejsu niższego poziomu zdefiniowanego przez IWaveGenerator
.
Jednak zamieszczam to pytanie, aby usłyszeć inne opinie na ten temat, być może kwestie, o których nie myślałem.
BTW: Powiedziałbym, że NoteGenerator
jest koncepcyjnie, IWaveGenerator
ponieważ generuje SoundWave
s.
Niezależnie od tego, czy NoteGenerator jest „koncepcyjnie”, IWaveGenerator nie ma znaczenia.
Powinieneś dziedziczyć po interfejsie tylko wtedy, gdy planujesz zaimplementować ten dokładnie interfejs zgodnie z zasadą podstawienia Liskowa, tj. Z poprawną semantyką, a także z prawidłową składnią.
Wygląda na to, że NoteGenerator może mieć składniowo ten sam interfejs, ale jego semantyka (w tym przypadku znaczenie parametrów, które przyjmuje) będzie bardzo różna, więc użycie dziedziczenia w tym przypadku byłoby bardzo mylące i potencjalnie podatne na błędy. Masz rację, preferując kompozycję.
źródło
NoteGenerator
że będę implementował,GenerateWave
ale interpretuje parametry inaczej, tak, zgadzam się, że byłby to okropny pomysł. Miałem na myśli, że NoteGenerator jest swoistą specjalizacją generatora fal: może pobierać dane wejściowe „wyższego poziomu” zamiast tylko surowych danych dźwiękowych (np. Nazwa nuty zamiast częstotliwości). TjsineWaveGenerator.generate(440) == noteGenerator.generate("a4")
. Pojawia się więc pytanie, skład lub dziedziczenie.Wygląda na
NoteGenerator
to, że nie jestWaveGenerator
, więc nie powinien implementować interfejsu.Kompozycja to właściwy wybór.
źródło
NoteGenerator
jest koncepcyjnie,IWaveGenerator
ponieważ generujeSoundWave
s.GenerateWave
, to nie jest toIWaveGenerator
. Ale wygląda na to, że używa IWaveGenerator (może więcej?), Stąd kompozycja.GenerateWave
funkcji opisanej w pytaniu. Ale z powyższego komentarza sądzę, że nie o to tak naprawdę chodziło PO.Masz solidne argumenty za kompozycją. Możesz mieć sprawę również dodać dziedziczenia. Można to powiedzieć, patrząc na kod wywołujący. Jeśli chcesz mieć możliwość użycia
NoteGenerator
istniejącego kodu wywołującego, który oczekujeIWaveGenerator
, musisz zaimplementować interfejs. Szukasz potrzeby zastępowalności. Czy koncepcyjnie generator fal „is-a” jest poza sednem sprawy.źródło
IHasWaveGenerator
I odpowiednia metoda na tym interfejsieGetWaveGenerator
zwróciłaby instancjęIWaveGenerator
. Oczywiście nazewnictwo można zmienić. (Próbuję tylko przedstawić więcej szczegółów - daj mi znać, jeśli moje dane są błędne.)Dobrze jest
NoteGenerator
zaimplementować interfejs, a takżeNoteGenerator
mieć wewnętrzną implementację, która odwołuje się (według składu) do innejIWaveGenerator
.Ogólnie kompozycja powoduje, że kod jest łatwiejszy w utrzymaniu (tj. Czytelny), ponieważ nie ma złożoności przesłonięć do uzasadnienia. Twoje spostrzeżenie na temat macierzy klas, które miałbyś podczas korzystania z dziedziczenia, jest również trafne i prawdopodobnie można je uznać za zapach kodu wskazujący na kompozycję.
Dziedziczenie jest lepiej stosowane, gdy masz implementację, którą chcesz specjalizować lub dostosowywać, co nie wydaje się tak w tym przypadku: wystarczy użyć interfejsu.
źródło
NoteGenerator
Wdrożenie nie jest w porządku,IWaveGenerator
ponieważ notatki wymagają beatów. nie sekund.NoteGenerator
jest koncepcyjnie,IWaveGenerator
ponieważ generujeSoundWave
s”, i rozważał dziedziczenie, więc wziąłem umysłową swobodę za możliwość, że może być jakaś implementacja interfejsu, nawet jeśli jest inna lepszy interfejs lub podpis dla klasy.