Kiedyś tworzyłem wiele abstrakcyjnych klas / metod. Potem zacząłem używać interfejsów.
Teraz nie jestem pewien, czy interfejsy nie powodują, że klasy abstrakcyjne stają się przestarzałe.
Potrzebujesz w pełni abstrakcyjnej klasy? Zamiast tego utwórz interfejs. Potrzebujesz abstrakcyjnej klasy z pewną implementacją? Utwórz interfejs, stwórz klasę. Dziedzicz klasę, zaimplementuj interfejs. Dodatkową korzyścią jest to, że niektóre klasy mogą nie potrzebować klasy nadrzędnej, a jedynie zaimplementować interfejs.
Czy zatem abstrakcyjne klasy / metody są przestarzałe?
object-oriented
architecture
inheritance
interfaces
Borys Jankow
źródło
źródło
Odpowiedzi:
Nie.
Interfejsy nie mogą zapewniać domyślnej implementacji, klasy abstrakcyjne i metoda mogą. Jest to szczególnie przydatne, aby uniknąć powielania kodu w wielu przypadkach.
Jest to również bardzo dobry sposób na zmniejszenie sprzężenia sekwencyjnego. Bez metody / klas abstrakcyjnych nie można wdrożyć wzorca metody szablonu. Proponuję zajrzeć do tego artykułu z Wikipedii: http://en.wikipedia.org/wiki/Template_method_pattern
źródło
Istnieje metoda abstrakcyjna, więc możesz wywoływać ją z klasy bazowej, ale implementować ją w klasie pochodnej. Twoja klasa podstawowa wie:
Jest to dość przyjemny sposób na wykonanie dziury w środkowym wzorze bez wiedzy klasy pochodnej na temat działań związanych z konfiguracją i czyszczeniem. Bez abstrakcyjnych metod, trzeba polegać na klasę pochodną realizacji
DoTask
i pamiętając, aby zadzwonićbase.DoSetup()
ibase.DoCleanup()
przez cały czas.Edytować
Ponadto, dzięki deadalnix za opublikowanie linku do Wzorca Metody Szablonu , co opisałem powyżej, bez znajomości nazwy. :)
źródło
public void DoTask(Action doWork)
Fruit
a twoja klasa pochodna jestApple
, chcesz zadzwonićmyApple.Eat()
, a niemyApple.Eat((a) => howToEatApple(a))
. Nie chceszApple
też dzwonićbase.Eat(() => this.howToEatMe())
. Myślę, że czystsze jest po prostu zastąpienie metody abstrakcyjnej.Nie, nie są przestarzałe.
W rzeczywistości istnieje niejasna, ale fundamentalna różnica między klasami / metodami abstrakcyjnymi a interfejsami.
jeśli zbiór klas, w których jedna z nich musi być użyta, mają wspólne zachowanie, które dzielą (klasy powiązane, to znaczy), to przejdź do klas / metod abstrakcyjnych.
Przykład: urzędnik, urzędnik, dyrektor - wszystkie te klasy mają wspólną funkcję CalculateSalary (), używają abstrakcyjnych klas podstawowych.CalculateSalary () mogą być różnie zaimplementowane, ale na przykład istnieją inne rzeczy, takie jak GetAttendance (), które mają wspólną definicję w klasie podstawowej.
Jeśli w twoich klasach nie ma nic wspólnego (klasy niepowiązane, w wybranym kontekście) między nimi, ale ma działanie, które różni się znacznie pod względem implementacji, przejdź do interfejsu.
Przykład: klasy nie związane z krowami, ławkami, samochodami, telesope, ale Isortable może tam być, aby posortować je w tablicy.
Różnica ta jest zwykle ignorowana, gdy podchodzi się do niej z perspektywy polimorficznej. Ale osobiście uważam, że są sytuacje, w których jeden jest odpowiedni od drugiego z powodu wyjaśnionego powyżej.
źródło
Oprócz innych dobrych odpowiedzi istnieje zasadnicza różnica między interfejsami a klasami abstrakcyjnymi, o której nikt specjalnie nie wspomniał, a mianowicie, że interfejsy są znacznie mniej niezawodne i dlatego nakładają znacznie większe obciążenie testowe niż klasy abstrakcyjne. Na przykład rozważ ten kod C #:
Gwarantuję, że muszę przetestować tylko dwie ścieżki kodu. Autor Frobbit może polegać na fakcie, że frobber jest czerwony lub zielony.
Jeśli zamiast tego mówimy:
Teraz nie wiem absolutnie nic o skutkach tego wezwania do Froba. Muszę mieć pewność, że cały kod we Frobbit jest odporny na wszelkie możliwe implementacje IFrobbera , nawet implementacje osób niekompetentnych (źle) lub aktywnie wrogo nastawionych do mnie lub moich użytkowników (znacznie gorzej).
Klasy abstrakcyjne pozwalają uniknąć tych wszystkich problemów; Użyj ich!
źródło
Sam to powiesz:
brzmi to dużo pracy w porównaniu do „dziedziczenia klasy abstrakcyjnej”. Możesz wykonać pracę dla siebie, podchodząc do kodu z „purystycznego” punktu widzenia, ale uważam, że mam już dość do zrobienia, nie próbując zwiększać obciążenia bez praktycznych korzyści.
źródło
Jak skomentowałem w poście @deadnix: Częściowe implementacje są anty-wzorcem, pomimo tego, że wzór szablonu je formalizuje.
Czyste rozwiązanie dla tego przykładu wikipedii wzorca szablonu :
To rozwiązanie jest lepsze, ponieważ wykorzystuje kompozycję zamiast dziedziczenia . Wzorzec szablonu wprowadza zależność między wdrożeniem reguł Monopoly a wdrożeniem sposobu uruchamiania gier. Są to jednak dwie zupełnie różne obowiązki i nie ma żadnego dobrego powodu, aby je łączyć.
źródło
Nie. Nawet proponowana alternatywa obejmuje użycie klas abstrakcyjnych. Ponadto, ponieważ nie określiłeś języka, idę dalej i powiem, że kod ogólny jest lepszą opcją niż kruche dziedziczenie. Klasy abstrakcyjne mają znaczącą przewagę nad interfejsami.
źródło
Klasy abstrakcyjne nie są interfejsami. Są to klasy, których nie można utworzyć.
Ale wtedy miałbyś nie abstrakcyjną, bezużyteczną klasę. Do wypełnienia dziury funkcjonalnej w klasie podstawowej wymagane są metody abstrakcyjne.
Na przykład biorąc pod uwagę tę klasę
Jak zaimplementowałbyś tę podstawową funkcjonalność w klasie, która nie ma
Frob()
aniIsFrobbingNeeded
?źródło
Jestem twórcą frameworka serwletu, w którym klasy abstrakcyjne odgrywają istotną rolę. Powiedziałbym więcej: potrzebuję metod pół abstrakcyjnych, kiedy metoda musi zostać zastąpiona w 50% przypadków i chciałbym zobaczyć ostrzeżenie kompilatora, że ta metoda nie została zastąpiona. Rozwiązuję problem dodając adnotacje. Wracając do pytania, istnieją dwa różne przypadki użycia klas abstrakcyjnych i interfejsów, a do tej pory nikt nie jest przestarzały.
źródło
Nie sądzę, że są one przestarzałe przez interfejsy, ale mogą być przestarzałe ze względu na strategię.
Podstawowym zastosowaniem klasy abstrakcyjnej jest odroczenie części implementacji; mówiąc „ta część implementacji klasy może być inna”.
Niestety klasa abstrakcyjna zmusza klienta do zrobienia tego poprzez dziedziczenie . Natomiast wzorzec strategii pozwoli ci osiągnąć ten sam wynik bez dziedziczenia. Klient może tworzyć instancje twojej klasy, a nie zawsze definiować własne, a „implementacja” (zachowanie) klasy może się zmieniać dynamicznie. Wzorzec strategii ma dodatkowo tę zaletę, że zachowanie można zmienić w czasie wykonywania, nie tylko w czasie projektowania, a także ma znacznie słabsze powiązanie między zaangażowanymi typami.
źródło
Zasadniczo napotykałem o wiele więcej problemów konserwacyjnych związanych z czystymi interfejsami niż ABC, nawet ABC używane z wielokrotnym dziedziczeniem. YMMV - nie wiem, może nasz zespół użył ich nieodpowiednio.
To powiedziawszy, jeśli użyjemy analogii ze świata rzeczywistego, ile jest wykorzystania czystych interfejsów całkowicie pozbawionych funkcjonalności i stanu? Jeśli użyję USB jako przykładu, jest to dość stabilny interfejs (myślę, że jesteśmy teraz na USB 3.2, ale zachował również kompatybilność wsteczną).
Jednak nie jest to interfejs bezstanowy. Nie jest pozbawiony funkcjonalności. To bardziej jak abstrakcyjna klasa bazowa niż czysty interfejs. W rzeczywistości jest bliżej konkretnej klasy o bardzo specyficznych wymaganiach funkcjonalnych i stanowych, a jedyną abstrakcją jest to, co podłącza się do portu i jest jedyną możliwą do zastąpienia częścią.
W przeciwnym razie byłby to po prostu „dziura” w komputerze o znormalizowanym formacie i o wiele luźniejszych wymaganiach funkcjonalnych, które nie zrobiłyby nic samodzielnie, dopóki każdy producent nie wymyślił własnego sprzętu, aby coś zrobić, w tym momencie staje się znacznie słabszym standardem i niczym więcej niż „dziurą” i specyfikacją tego, co należy zrobić, ale nie ma centralnego przepisu na to, jak to zrobić. Tymczasem możemy skończyć na 200 różnych sposobów, po tym jak wszyscy producenci sprzętu wymyślą własne sposoby na dołączenie funkcjonalności i stanu do tej „dziury”.
I w tym momencie możemy mieć niektórych producentów, którzy wprowadzają inne problemy niż inni. Jeśli potrzebujemy zaktualizować specyfikację, możemy mieć 200 różnych konkretnych implementacji portów USB, przy czym całkowicie różne sposoby radzenia sobie ze specyfikacją wymagają aktualizacji i przetestowania. Niektórzy producenci mogą opracowywać de facto standardowe implementacje, które dzielą między sobą (analogiczna klasa bazowa implementująca ten interfejs), ale nie wszyscy. Niektóre wersje mogą być wolniejsze niż inne. Niektóre mogą mieć lepszą przepustowość, ale gorsze opóźnienia lub odwrotnie. Niektóre mogą zużywać więcej energii baterii niż inne. Niektóre mogą się łuszczyć i nie działać z całym sprzętem, który powinien współpracować z portami USB. Niektóre mogą wymagać przyłączenia reaktora jądrowego do działania, który ma tendencję do zatrucia radiacyjnego użytkowników.
I to właśnie znalazłem osobiście z czystymi interfejsami. Mogą istnieć pewne przypadki, w których mają one sens, na przykład po prostu modelowanie kształtu płyty głównej na podstawie procesora. Analogie współczynników kształtu są w rzeczywistości prawie bezpaństwowe i pozbawione funkcjonalności, jak w przypadku analogicznej „dziury”. Ale często uważam za wielki błąd, gdy drużyny uważają, że jest to jakoś lepsze we wszystkich przypadkach, nawet nie w pobliżu.
Wręcz przeciwnie, uważam, że ABC może rozwiązać znacznie więcej przypadków niż interfejsów, jeśli są to dwie możliwości, chyba że twój zespół jest tak gigantyczny, że tak naprawdę pożądane jest posiadanie analogicznej powyżej równoważnej liczby 200 konkurencyjnych implementacji USB zamiast jednego centralnego standardu utrzymać. W poprzednim zespole, w którym pracowałem, faktycznie musiałem mocno walczyć, aby poluzować standard kodowania, aby umożliwić ABC i wielokrotne dziedziczenie, a przede wszystkim w odpowiedzi na opisane wyżej problemy konserwacyjne.
źródło