Kiedy powinienem używać interfejsu, a kiedy powinienem używać klasy bazowej?
Czy powinien to zawsze być interfejs, jeśli nie chcę definiować podstawowej implementacji metod?
Jeśli mam klasę psów i kotów. Dlaczego miałbym chcieć wdrożyć IPet zamiast PetBase? Rozumiem, że mam interfejsy dla ISheds lub IBarków (IMakesNoise?), Ponieważ mogą one być umieszczane na zwierzęciu za zwierzę, ale nie rozumiem, którego użyć dla ogólnego zwierzaka.
I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.
. Nie mogę zrozumieć, co oznacza fragment. Możemy utworzyć kilka typów podstawowych i utworzyć typ pochodny dla dowolnego z nich, aby programista mógł wybrać typ podstawowy. Czy ktoś mógłby wyjaśnić, czego mi brakuje? Wierzę, że może to być część tego pytania. A może powinienem opublikować kolejny na temat konkretnego fragmentu?Odpowiedzi:
Weźmy przykład klasy Dog and Cat i zilustrujmy za pomocą C #:
Zarówno pies, jak i kot są zwierzętami, w szczególności czworonożnymi ssakami (zwierzęta są zbyt ogólne). Załóżmy, że masz abstrakcyjną klasę Mammal dla obu z nich:
Ta klasa bazowa prawdopodobnie będzie miała domyślne metody, takie jak:
Wszystkie są zachowaniami, które mają mniej więcej taką samą implementację między oboma gatunkami. Aby to zdefiniować, będziesz mieć:
Załóżmy teraz, że istnieją inne ssaki, które zwykle zobaczymy w zoo:
Będzie to nadal ważne, ponieważ u podstaw funkcjonalności
Feed()
iMate()
pozostanie takie samo.Jednak żyrafy, nosorożce i hipopotamy nie są dokładnie zwierzętami, z których można zrobić zwierzęta domowe. W tym miejscu przydatny będzie interfejs:
Realizacja powyższej umowy nie będzie taka sama między kotem i psem; umieszczenie ich implementacji w klasie abstrakcyjnej do dziedziczenia będzie złym pomysłem.
Twoje definicje psów i kotów powinny teraz wyglądać następująco:
Teoretycznie możesz zastąpić je z wyższej klasy podstawowej, ale w istocie interfejs pozwala dodawać do klasy tylko to, czego potrzebujesz, bez potrzeby dziedziczenia.
W związku z tym, ponieważ zwykle możesz dziedziczyć tylko jedną klasę abstrakcyjną (w większości statycznie typowanych języków OO, czyli ... wyjątki obejmują C ++), ale możesz implementować wiele interfejsów, pozwala to na konstruowanie obiektów ściśle według wymagań .
źródło
Cóż, Josh Bloch powiedział w Effective Java 2d :
Wolę interfejsy niż klasy abstrakcyjne
Niektóre główne punkty:
Z drugiej strony interfejsy są bardzo trudne do ewolucji. Dodanie metody do interfejsu spowoduje uszkodzenie wszystkich jej implementacji.
PS .: Kup książkę. Jest o wiele bardziej szczegółowy.
źródło
Interfejsy i klasy podstawowe reprezentują dwie różne formy relacji.
Dziedziczenie (klasy podstawowe) reprezentuje relację „jest-a”. Na przykład pies lub kot „is-a” pet. Relacja ta zawsze reprezentuje (pojedynczy) cel klasy (w połączeniu z „zasadą jednej odpowiedzialności” ).
Z drugiej strony interfejsy reprezentują dodatkowe cechy klasy. Nazwałbym to relacją „jest”, jak w „
Foo
jest jednorazowy”, stądIDisposable
interfejs w języku C #.źródło
Nowoczesny styl polega na definiowaniu IPet i PetBase.
Zaletą interfejsu jest to, że inny kod może go używać bez żadnych powiązań z innym kodem wykonywalnym. Całkowicie „czysty”. Interfejsy można również mieszać.
Ale klasy podstawowe są przydatne do prostych implementacji i wspólnych narzędzi. Zapewnij również abstrakcyjną klasę podstawową, aby zaoszczędzić czas i kod.
źródło
Interfejsy
Klasy podstawowe
źródło
Ogólnie rzecz biorąc, powinieneś faworyzować interfejsy zamiast klas abstrakcyjnych. Jednym z powodów użycia klasy abstrakcyjnej jest to, że masz wspólną implementację między konkretnymi klasami. Oczywiście nadal powinieneś zadeklarować interfejs (IPet) i mieć implementację tego interfejsu klasy abstrakcyjnej (PetBase). Korzystając z małych, wyraźnych interfejsów, możesz użyć wielokrotności, aby jeszcze bardziej zwiększyć elastyczność. Interfejsy pozwalają na maksymalną elastyczność i przenośność typów ponad granicami. Podczas przekazywania referencji przez granice zawsze należy przekazywać interfejs, a nie konkretny typ. Pozwala to odbiorcy określić konkretną implementację i zapewnia maksymalną elastyczność. Jest to absolutnie prawdziwe, gdy programujesz w sposób TDD / BDD.
Gang Czterech stwierdził w swojej książce „Ponieważ dziedziczenie wystawia podklasę na szczegóły dotyczące implementacji jej rodzica, często mówi się, że„ dziedziczenie przerywa enkapsulację ”. Wierzę, że to prawda.
źródło
Jest to dość specyficzne dla platformy .NET, ale książka Framework Design Guidelines twierdzi, że ogólnie klasy zapewniają większą elastyczność w ewoluujących ramach. Po dostarczeniu interfejsu nie ma możliwości jego zmiany bez zerwania kodu używającego tego interfejsu. Jednak dzięki klasie możesz ją modyfikować i nie łamać kodu, który do niej prowadzi. Tak długo, jak dokonasz odpowiednich modyfikacji, w tym dodając nowe funkcje, będziesz w stanie rozszerzać i rozwijać swój kod.
Krzysztof Cwalina mówi na stronie 81:
Biorąc to pod uwagę, z pewnością jest miejsce na interfejsy. Jako ogólną wytyczną zawsze należy podać abstrakcyjną implementację interfejsu klasy podstawowej, jeśli nie ma innego przykładu jako przykład sposobu implementacji interfejsu. W najlepszym przypadku ta klasa podstawowa pozwoli zaoszczędzić dużo pracy.
źródło
Juan,
Lubię myśleć o interfejsach jako sposobie charakteryzowania klasy. Określona klasa ras psów, powiedzmy YorkshireTerrier, może być potomkiem klasy psów nadrzędnych, ale implementuje także IFurry, IStubby i IYippieDog. Tak więc klasa określa, czym jest klasa, ale interfejs mówi nam o tym.
Zaletą tego jest to, że mogę na przykład zebrać wszystkie IYippieDog i wrzucić je do mojej kolekcji Ocean. Teraz mogę sięgnąć po określony zestaw obiektów i znaleźć te, które spełniają kryteria, na które patrzę, bez szczegółowej inspekcji klasy.
Uważam, że interfejsy naprawdę powinny definiować podzbiór publicznego zachowania klasy. Jeśli określa wszystkie zachowania publiczne dla wszystkich klas, które implementują, to zwykle nie musi istnieć. Nic mi nie mówią.
Ta myśl jest jednak sprzeczna z ideą, że każda klasa powinna mieć interfejs i należy do niego kodować. W porządku, ale kończy się wiele interfejsów jeden do jednego do klas i to sprawia, że wszystko jest mylące. Rozumiem, że pomysł jest taki, że tak naprawdę nic nie kosztuje, a teraz możesz z łatwością wymieniać różne elementy. Uważam jednak, że rzadko to robię. Przez większość czasu po prostu modyfikuję istniejącą klasę i mam dokładnie takie same problemy, jakie zawsze występowałem, jeśli interfejs publiczny tej klasy wymaga zmiany, z tym że muszę go teraz zmienić w dwóch miejscach.
Więc jeśli myślisz tak jak ja, zdecydowanie powiedziałbyś, że Kot i Pies są IPettable. Jest to charakterystyka, która pasuje do nich obu.
Inną kwestią jest to, czy powinny mieć tę samą klasę podstawową? Pytanie brzmi, czy należy je ogólnie traktować tak samo. Z pewnością są to Zwierzęta, ale czy to pasuje do tego, w jaki sposób będziemy je razem wykorzystywać.
Powiedz, że chcę zebrać wszystkie klasy zwierząt i umieścić je w moim pojemniku na Arkę.
A może muszą to być ssaki? Być może potrzebujemy jakiejś fabryki doju krzyżowego?
Czy w ogóle muszą być ze sobą powiązane? Czy wystarczy wiedzieć, że oba są IPettable?
Często odczuwam chęć wyprowadzenia całej hierarchii klas, kiedy naprawdę potrzebuję tylko jednej klasy. Robię to w oczekiwaniu, że kiedyś będę tego potrzebować i zwykle nigdy tego nie robię. Nawet kiedy to robię, zwykle muszę dużo zrobić, aby to naprawić. To dlatego, że pierwszą klasą, którą tworzę, nie jest Pies, nie mam tyle szczęścia, tylko Dziobak. Teraz cała moja hierarchia klas oparta jest na dziwnym przypadku i mam dużo zmarnowanego kodu.
W pewnym momencie możesz również stwierdzić, że nie wszystkie Koty są IPettable (jak ten bezwłosy). Teraz możesz przenieść ten interfejs do wszystkich pasujących klas pochodnych. Przekonasz się, że o wiele mniej przełomowa zmiana, że nagle wszystkie Koty nie są już uzyskiwane z Bazy PettableBase.
źródło
Oto podstawowa i prosta definicja interfejsu i klasy bazowej:
Twoje zdrowie
źródło
W miarę możliwości zalecam stosowanie kompozycji zamiast dziedziczenia. Używaj interfejsów, ale do implementacji podstawowej używaj obiektów członkowskich. W ten sposób możesz zdefiniować fabrykę, która konstruuje twoje obiekty tak, by zachowywały się w określony sposób. Jeśli chcesz zmienić to zachowanie, wówczas tworzysz nową metodę fabryczną (lub fabrykę abstrakcyjną), która tworzy różne typy podobiektów.
W niektórych przypadkach może się okazać, że twoje podstawowe obiekty wcale nie potrzebują interfejsów, jeśli wszystkie zmienne zachowania są zdefiniowane w obiektach pomocniczych.
Dlatego zamiast IPet lub PetBase możesz skończyć ze zwierzakiem, który ma parametr IFurBehavior. Parametr IFurBehavior jest ustawiany przez metodę CreateDog () PetFactory. Ten parametr jest wywoływany dla metody shed ().
Jeśli to zrobisz, zauważysz, że kod jest znacznie bardziej elastyczny, a większość prostych obiektów radzi sobie z bardzo podstawowymi zachowaniami w całym systemie.
Polecam ten wzorzec nawet w językach wielokrotnego dziedziczenia.
źródło
Zostało to dobrze wyjaśnione w tym artykule na temat Java World .
Osobiście używam interfejsów do definiowania interfejsów - tj. Części projektu systemu, które określają, w jaki sposób należy uzyskać dostęp.
Nierzadko będę miał klasę implementującą jeden lub więcej interfejsów.
Klasy abstrakcyjne, których używam jako podstawy do czegoś innego.
Poniżej znajduje się wyciąg z wyżej wymienionego artykułu Artykuł JavaWorld.com, autor Tony Sintes, 04/20/01
Niektórzy twierdzą, że należy zdefiniować wszystkie klasy w kategoriach interfejsów, ale myślę, że zalecenie wydaje się nieco ekstremalne. Korzystam z interfejsów, gdy widzę, że coś w moim projekcie często się zmienia.
źródło
Pamiętaj również, aby nie zostać zmiecionym z OO ( patrz blog ) i zawsze modeluj obiekty w oparciu o wymagane zachowanie, jeśli projektujesz aplikację, w której jedynym wymaganym zachowaniem jest ogólna nazwa i gatunek zwierzęcia, potrzebujesz tylko jedna klasa Zwierzę z właściwością dla nazwy, zamiast milionów klas dla każdego możliwego zwierzęcia na świecie.
źródło
Mam szorstką zasadę
Funkcjonalność: prawdopodobnie będzie różna we wszystkich częściach: Interfejs.
Dane i funkcje, części będą w większości takie same, części inne: klasa abstrakcyjna.
Dane i funkcjonalność, faktycznie działające, jeśli zostaną rozszerzone tylko z niewielkimi zmianami: klasa zwykła (konkretna)
Dane i funkcjonalność, bez planowanych zmian: zwykła (konkretna) klasa z ostatecznym modyfikatorem.
Dane, a może i funkcjonalność: tylko do odczytu: członkowie enum.
Jest to bardzo szorstkie i gotowe i wcale nie jest ściśle określone, ale istnieje spektrum od interfejsów, w których wszystko ma być zmienione, do wyliczeń, w których wszystko jest ustawione trochę jak plik tylko do odczytu.
źródło
Interfejsy powinny być małe. Bardzo mały. Jeśli naprawdę rozbijasz swoje obiekty, wówczas interfejsy prawdopodobnie będą zawierały tylko kilka bardzo specyficznych metod i właściwości.
Klasy abstrakcyjne to skróty. Czy są rzeczy, które współużytkują wszystkie pochodne PetBase, które możesz zakodować raz i zrobić z nimi? Jeśli tak, to czas na abstrakcyjną klasę.
Ograniczają się także klasy abstrakcyjne. Mimo że stanowią doskonały skrót do tworzenia obiektów potomnych, każdy dany obiekt może implementować tylko jedną klasę abstrakcyjną. Wiele razy uważam to za ograniczenie klas abstrakcyjnych i dlatego używam wielu interfejsów.
Klasy abstrakcyjne mogą zawierać kilka interfejsów. Klasa abstrakcyjna PetBase może implementować IPet (zwierzęta mają właścicieli) i niestrawność (zwierzęta jedzą, a przynajmniej powinny). Jednak PetBase prawdopodobnie nie wdroży IMammal, ponieważ nie wszystkie zwierzęta są ssakami i nie wszystkie ssaki są zwierzętami domowymi. Możesz dodać MammalPetBase, który rozszerza PetBase i dodać IMammal. FishBase może mieć PetBase i dodać IFish. IFish miałby ISwim i IUnderwaterBreather jako interfejsy.
Tak, mój przykład jest nadmiernie skomplikowany w stosunku do prostego przykładu, ale to część wspaniałej rzeczy na temat tego, jak interfejsy i klasy abstrakcyjne działają razem.
źródło
Źródło : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C # to wspaniały język, który dojrzał i ewoluował w ciągu ostatnich 14 lat. Jest to świetne dla nas, programistów, ponieważ dojrzały język zapewnia nam mnóstwo funkcji językowych, które są do naszej dyspozycji.
Jednak z dużą mocą staje się duża odpowiedzialność. Niektóre z tych funkcji mogą być niewłaściwie używane, a czasem trudno jest zrozumieć, dlaczego zdecydujesz się korzystać z jednej funkcji nad drugą. Przez lata funkcja, z którą widziałem wielu programistów, polega na tym, kiedy wybrać interfejs lub klasę abstrakcyjną. Oba mają wady i zalety, a każdy z nich ma odpowiedni czas i miejsce. Ale jak zdecydować?
Oba zapewniają ponowne wykorzystanie wspólnej funkcjonalności między typami. Najbardziej oczywistą różnicą jest to, że interfejsy nie zapewniają żadnej implementacji dla ich funkcjonalności, podczas gdy klasy abstrakcyjne pozwalają na implementację niektórych „podstawowych” lub „domyślnych” zachowań, a następnie mają możliwość „zastąpienia” tego domyślnego zachowania typami pochodnymi klas, jeśli to konieczne .
Wszystko to dobrze i dobrze, zapewnia świetne ponowne użycie kodu i przestrzega zasady DRY (Don't Repeat Yourself) tworzenia oprogramowania. Klasy abstrakcyjne są świetne do użycia, gdy masz relację „jest”.
Na przykład: Golden retriever „jest” typem psa. Pudel też. Oboje mogą szczekać, tak jak wszystkie psy. Możesz jednak stwierdzić, że park pudli różni się znacznie od „domyślnej” kory psa. Dlatego warto wdrożyć coś w następujący sposób:
Jak widać, byłby to świetny sposób na zachowanie stanu DRY w kodzie i umożliwienia wywoływania implementacji klasy podstawowej, gdy dowolny z typów może polegać tylko na domyślnej Bark zamiast implementacji specjalnego przypadku. Klasy takie jak GoldenRetriever, Boxer, Lab mogą wszyscy odziedziczyć „domyślną” (klasę basu) Kora bez żadnych opłat tylko dlatego, że implementują klasę abstrakcyjną Dog.
Ale jestem pewien, że już to wiedziałeś.
Jesteś tutaj, ponieważ chcesz zrozumieć, dlaczego możesz chcieć wybrać interfejs zamiast klasy abstrakcyjnej lub odwrotnie. Cóż, jednym z powodów, dla których możesz chcieć wybrać interfejs zamiast klasy abstrakcyjnej jest to, że nie masz lub chcesz zapobiec domyślnej implementacji. Wynika to zwykle z tego, że typy, które implementują interfejs niezwiązany w relacji „jest”. W rzeczywistości nie muszą być w ogóle powiązane, z wyjątkiem faktu, że każdy typ „jest w stanie” lub ma „zdolność” do zrobienia czegoś lub posiadania czegoś.
Co to do cholery znaczy? Na przykład: człowiek nie jest kaczką… a kaczka nie jest człowiekiem. Dość oczywiste. Jednak zarówno kaczka, jak i człowiek mają „zdolność” pływania (biorąc pod uwagę, że człowiek zdał lekcje pływania w 1. klasie :)). Ponadto, ponieważ kaczka nie jest człowiekiem lub odwrotnie, nie jest to realizacja „jest”, ale relacja „jest w stanie”, a my możemy użyć interfejsu do zilustrowania tego:
Korzystanie z interfejsów takich jak powyższy kod pozwoli ci przekazać obiekt do metody, która „jest w stanie” coś zrobić. Kod nie dba o to, jak to robi… Wie tylko, że może wywoływać metodę Swim na tym obiekcie, a ten obiekt będzie wiedział, jakie zachowanie podejmie w czasie wykonywania na podstawie jego typu.
Po raz kolejny pomaga to Twojemu kodowi pozostać SUCHYM, dzięki czemu nie będziesz musiał pisać wielu metod, które wywołują obiekt, aby wykonać tę samą funkcję podstawową (ShowHowHumanSwims (human), ShowHowDuckSwims (duck) itp.)
Korzystanie z interfejsu w tym miejscu pozwala metodom wywoływania nie martwić się o to, jaki typ jest który lub w jaki sposób zachowanie jest realizowane. Po prostu wie, że biorąc pod uwagę interfejs, każdy obiekt będzie musiał zaimplementować metodę Swim, więc można bezpiecznie wywołać go we własnym kodzie i pozwolić, aby zachowanie metody Swim było obsługiwane w ramach jego własnej klasy.
Podsumowanie:
Więc moją główną zasadą jest użycie klasy abstrakcyjnej, gdy chcesz zaimplementować funkcję „domyślną” dla hierarchii klas lub / i klasy lub typy, z którymi pracujesz, mają relację „jest” (np. Pudel „jest ”Typ psa).
Z drugiej strony używaj interfejsu, gdy nie masz relacji „jest”, ale masz typy, które dzielą „zdolność” do zrobienia czegoś lub posiadania czegoś (np. Kaczka „nie jest” człowiekiem. Jednak kaczka i człowiek dzielą się „Umiejętność” pływania).
Inną różnicą, na którą należy zwrócić uwagę między klasami abstrakcyjnymi a interfejsami, jest to, że klasa może implementować jeden lub wiele interfejsów, ale klasa może dziedziczyć tylko z JEDNEJ klasy abstrakcyjnej (lub dowolnej innej klasy pod tym względem). Tak, możesz zagnieżdżać klasy i mieć hierarchię dziedziczenia (którą wiele programów robi i powinno mieć), ale nie możesz dziedziczyć dwóch klas w jednej definicji klasy pochodnej (ta reguła dotyczy C #. W niektórych innych językach możesz to zrobić, zwykle tylko z powodu braku interfejsów w tych językach).
Pamiętaj także o korzystaniu z interfejsów w celu przestrzegania zasady segregacji interfejsów (ISP). ISP stwierdza, że żaden klient nie powinien być zmuszany do polegania na metodach, których nie używa. Z tego powodu interfejsy powinny koncentrować się na konkretnych zadaniach i zwykle są bardzo małe (np. IDisposable, IComparable).
Inną wskazówką jest, jeśli opracowujesz małe, zwięzłe elementy funkcjonalności, korzystaj z interfejsów. Jeśli projektujesz duże jednostki funkcjonalne, użyj klasy abstrakcyjnej.
Mam nadzieję, że to wyjaśni niektóre osoby!
Również jeśli możesz wymyślić jakieś lepsze przykłady lub chcesz coś wskazać, zrób to w komentarzach poniżej!
źródło
Sprawa dla klas podstawowych nad interfejsami została dobrze wyjaśniona w Podmain .NET Coding Guidelines:
źródło
Jedną ważną różnicą jest to, że możesz odziedziczyć tylko jedną klasę bazową, ale możesz zaimplementować wiele interfejsów. Dlatego chcesz użyć klasy podstawowej tylko wtedy, gdy masz absolutną pewność, że nie będziesz musiał dziedziczyć innej klasy podstawowej. Dodatkowo, jeśli okaże się, że twój interfejs robi się duży, powinieneś zacząć rozbijać go na kilka logicznych elementów, które definiują niezależną funkcjonalność, ponieważ nie ma reguły, że twoja klasa nie może zaimplementować ich wszystkich (lub że możesz zdefiniować inny interfejs, który po prostu dziedziczy je wszystkie, aby je grupować).
źródło
Kiedy zacząłem uczyć się o programowaniu obiektowym, popełniłem łatwy i prawdopodobnie powszechny błąd, używając dziedziczenia do dzielenia się wspólnym zachowaniem - nawet jeśli takie zachowanie nie było istotne dla natury obiektu.
Aby dalej opierać się na przykładzie często używanym w tym konkretnym pytaniu, istnieje wiele rzeczy, które można karmić - dziewczyny, samochody, puszyste koce ... - więc mógłbym mieć klasę Petable, która zapewniła to wspólne zachowanie i różne klasy dziedziczące z tego.
Jednak bycie zwierzęciem nie należy do natury żadnego z tych przedmiotów. Istnieją znacznie ważniejsze pojęcia, które są istotne dla ich natury - dziewczyna to osoba, samochód to pojazd lądowy, kot to ssak ...
Zachowania należy przypisać najpierw do interfejsów (w tym domyślnego interfejsu klasy) i awansować do klasy podstawowej tylko wtedy, gdy są one (a) wspólne dla dużej grupy klas, które są podzbiorami większej klasy - w tym samym sensie, że „kot” i „osoba” to podzbiory „ssaka”.
Problem polega na tym, że po zrozumieniu projektowania obiektowego wystarczająco lepiej niż ja na początku zwykle robisz to automatycznie, nawet o tym nie myśląc. Tak więc sama prawda o stwierdzeniu „kod do interfejsu, a nie klasa abstrakcyjna” staje się tak oczywista, że trudno ci uwierzyć, że ktoś zechce to powiedzieć - i zaczniesz odczytywać w niej inne znaczenia.
Inną rzeczą, którą dodam, jest to, że jeśli klasa jest czysto abstrakcyjna - bez żadnych nieabstrakcyjnych, nie odziedziczonych członków lub metod narażonych na dziecko, rodzica lub klienta - to dlaczego jest to klasa? Może być zastąpiony, w niektórych przypadkach interfejsem, aw innych przez Null.
źródło
Wolę interfejsy niż klasy abstrakcyjne
Uzasadnienie, główne punkty do rozważenia [dwa już tutaj wspomniane] to:
Oczywiście temat ten został obszernie omówiony w innym miejscu [2,3].
[1] Oczywiście dodaje więcej kodu, ale jeśli zwięzłość jest twoją główną troską, prawdopodobnie powinieneś był unikać Javy!
[2] Joshua Bloch, Effective Java, pozycje 16–18.
[3] http://www.codeproject.com/KB/ar ...
źródło
Poprzednie komentarze na temat używania klas abstrakcyjnych do wspólnej implementacji są zdecydowanie na znak. Jedną z korzyści, o których jeszcze nie wspomniałem, jest to, że użycie interfejsów znacznie ułatwia implementację próbnych obiektów do celów testowania jednostkowego. Zdefiniowanie IPet i PetBase zgodnie z opisem Jason Cohen pozwala łatwo wyśmiewać różne warunki danych, bez obciążania fizycznej bazy danych (dopóki nie zdecydujesz, że nadszedł czas, aby przetestować prawdziwą rzecz).
źródło
Nie używaj klasy bazowej, chyba że wiesz, co to znaczy i że ma ona zastosowanie w tym przypadku. Jeśli ma to zastosowanie, użyj go, w przeciwnym razie użyj interfejsów. Zwróć jednak uwagę na odpowiedź dotyczącą małych interfejsów.
Dziedziczenie publiczne jest nadużywane w OOD i wyraża o wiele więcej, niż większość programistów zdaje sobie sprawę lub jest gotowa sprostać. Zobacz zasadę substytucyjności Liskowa
Krótko mówiąc, jeśli A „jest” B, to A wymaga nie więcej niż B i dostarcza nie mniej niż B, dla każdej metody, którą eksponuje.
źródło
Inną opcją, o której należy pamiętać, jest użycie relacji „has-a”, aka ”, realizowanej w kategoriach„ lub ”kompozycji. Czasami jest to czystszy, bardziej elastyczny sposób na uporządkowanie rzeczy niż używanie dziedziczenia „jest”.
Logiczne może być stwierdzenie, że zarówno Pies, jak i Kot „mają” zwierzaka, ale pozwala uniknąć typowych pułapek wielokrotnego dziedziczenia:
Tak, ten przykład pokazuje, że w wykonywaniu tych czynności jest dużo duplikacji kodu i braku elegancji. Ale należy również docenić, że pomaga to utrzymać oddzielenie Psa i Kota od klasy Pet (w tym, że Pies i Kot nie mają dostępu do prywatnych członków Pet) i pozostawia miejsce dla Psa i Kota na dziedziczenie po czymś innym - -prawdopodobnie klasa Mammal.
Kompozycja jest preferowana, gdy nie jest wymagany dostęp prywatny i nie trzeba odwoływać się do psów i kotów za pomocą ogólnych odniesień / wskaźników dla zwierząt domowych. Interfejsy zapewniają tę ogólną funkcję odniesienia i mogą pomóc w ograniczeniu szczegółowości kodu, ale mogą również zaciemniać rzeczy, gdy są źle zorganizowane. Dziedziczenie jest przydatne, gdy potrzebujesz dostępu do prywatnego członka, a korzystając z niego, zobowiązujesz się do silnego połączenia klas psów i kotów z klasą zwierząt domowych, co jest stratą.
Pomiędzy dziedziczeniem, kompozycją i interfejsami nie ma jednego sposobu, który jest zawsze właściwy, i pomaga zastanowić się, w jaki sposób wszystkie trzy opcje mogą być używane w harmonii. Spośród tych trzech elementów dziedziczenie jest zazwyczaj opcją, z której należy korzystać najrzadziej.
źródło
Pod względem koncepcyjnym interfejs służy do formalnego i półformalnego zdefiniowania zestawu metod, które zapewni obiekt. Formalnie oznacza zbiór nazw i podpisów metod, a półformalnie oznacza czytelną dla ludzi dokumentację związaną z tymi metodami.
Interfejsy są jedynie opisami API (w końcu API oznacza interfejs programowania aplikacji ), nie mogą zawierać żadnej implementacji i nie można używać ani uruchamiać interfejsu. Zawierają jedynie wyraźną umowę dotyczącą sposobu interakcji z przedmiotem.
Klasy zapewniają implementację i mogą zadeklarować, że implementują zero, jeden lub więcej interfejsów. Jeśli klasa ma być dziedziczona, konwencja polega na prefiksie nazwy klasy „Base”.
Istnieje różnica między klasą podstawową a abstrakcyjną klasą podstawową (ABC). ABC łączy interfejs i implementację razem. Streszczenie poza programowaniem komputerowym oznacza „streszczenie”, czyli „abstract == interface”. Abstrakcyjna klasa bazowa może następnie opisać zarówno interfejs, jak i pusty, częściowego lub całkowitego wdrożenia, który jest przeznaczony do dziedziczone.
Opinie na temat tego, kiedy używać interfejsów w porównaniu z abstrakcyjnymi klasami podstawowymi w porównaniu do zwykłych klas będą się bardzo różnić w zależności od tego, co opracowujesz, i języka, w którym się rozwijasz. Interfejsy są często kojarzone tylko z językami o typie statycznym, takim jak Java lub C #, ale dynamicznie pisane języki mogą mieć również interfejsy i abstrakcyjne klasy podstawowe . Python na przykład rozróżnienie pusta pomiędzy klasy, co stwierdzi, że realizuje się interfejs i obiektu, który jest wystąpienie klasy i mówi się, że zapewniają , że interfejs. W dynamicznym języku jest możliwe, że dwa obiekty, które są instancjami tej samej klasy , mogą zadeklarować, że zapewniają zupełnie inne interfejsy. W Pythonie jest to możliwe tylko w przypadku atrybutów obiektu, podczas gdy metody są współużytkowane przez wszystkie obiekty klasy . Jednak w Ruby obiekty mogą mieć metody dla poszczególnych instancji, więc możliwe jest, że interfejs między dwoma obiektami tej samej klasy może się różnić tak bardzo, jak chce programista (jednak Ruby nie ma wyraźnego sposobu deklarowania interfejsów).
W językach dynamicznych interfejs do obiektu jest często domyślnie zakładany, albo przez introspekcję obiektu i zapytanie go, jakie zapewnia metody ( spójrz przed skokiem ), albo najlepiej po prostu przez użycie pożądanego interfejsu na obiekcie i wyłapanie wyjątków, jeśli obiekt nie zapewnia tego interfejsu ( łatwiej prosić o wybaczenie niż pozwolenie ). Może to prowadzić do „fałszywych trafień”, w których dwa interfejsy mają tę samą nazwę metody, ale są semantycznie różne. Jednak kompromis polega na tym, że kod jest bardziej elastyczny, ponieważ nie trzeba przesadnie określać z góry, aby przewidzieć wszystkie możliwe zastosowania kodu.
źródło
To zależy od twoich wymagań. Jeśli IPet jest dość prosty, wolałbym to zaimplementować. W przeciwnym razie, jeśli PetBase zaimplementuje mnóstwo funkcji, których nie chcesz powielać, skorzystaj z niego.
Minusem zaimplementowania klasy podstawowej jest wymóg
override
(lubnew
) istniejących metod. To czyni je wirtualnymi metodami, co oznacza, że musisz uważać na sposób korzystania z instancji obiektu.Wreszcie, jedno dziedzictwo .NET mnie zabija. Naiwny przykład: powiedz, że kontrolujesz użytkownika, więc dziedziczysz
UserControl
. Ale teraz nie możesz też dziedziczyćPetBase
. Zmusza to do reorganizacji, na przykład doPetBase
członkostwa w klasie.źródło
Zazwyczaj też nie wdrażam, dopóki go nie potrzebuję. Preferuję interfejsy zamiast klas abstrakcyjnych, ponieważ daje to nieco większą elastyczność. Jeśli w niektórych dziedziczących klasach występuje wspólne zachowanie, przesuwam je w górę i tworzę abstrakcyjną klasę podstawową. Nie widzę potrzeby korzystania z obu rozwiązań, ponieważ zasadniczo służą one temu samemu celowi, a posiadanie obu jest nieprzyjemnym zapachem kodu (imho), że rozwiązanie zostało przeprojektowane.
źródło
Jeśli chodzi o C #, w pewnym sensie interfejsy i klasy abstrakcyjne mogą być wymienne. Różnice są jednak następujące: i) interfejsy nie mogą implementować kodu; ii) z tego powodu interfejsy nie mogą wywoływać dalej stosu do podklasy; oraz iii) tylko klasa abstrakcyjna może być dziedziczona w klasie, podczas gdy wiele interfejsów może być implementowanych w klasie.
źródło
Z definicji interfejs zapewnia warstwę do komunikacji z innym kodem. Wszystkie publiczne właściwości i metody klasy domyślnie implementują niejawny interfejs. Możemy również zdefiniować interfejs jako rolę, gdy kiedykolwiek jakaś klasa musi odgrywać tę rolę, musi go wdrożyć, dając mu różne formy implementacji w zależności od klasy, która go implementuje. Dlatego kiedy mówisz o interfejsie, mówisz o polimorfizmie, a kiedy mówisz o klasie podstawowej, mówisz o dziedziczeniu. Dwie koncepcje ups!
źródło
Odkryłem, że wzór Interfejs> Streszczenie> Beton działa w następującym przypadku użycia:
Klasa abstrakcyjna definiuje domyślne wspólne atrybuty klas konkretnych, ale wymusza interfejs. Na przykład:
Teraz, ponieważ wszystkie ssaki mają włosy i sutki (AFAIK, nie jestem zoologiem), możemy przenieść to do abstrakcyjnej klasy podstawowej
A potem konkretne klasy po prostu definiują, że idą w pozycji pionowej.
Ta konstrukcja jest dobra, gdy istnieje wiele konkretnych klas i nie chcesz utrzymywać płyty kotłowej tylko w celu zaprogramowania interfejsu. Gdyby nowe metody zostały dodane do interfejsu, spowodowałoby to uszkodzenie wszystkich klas wynikowych, więc nadal zyskujesz zalety podejścia opartego na interfejsie.
W tym przypadku streszczenie równie dobrze może być konkretne; jednak abstrakcyjne oznaczenie pomaga podkreślić, że ten wzór jest stosowany.
źródło
Dziedzicznik klasy podstawowej powinien mieć relację „jest”. Interfejs reprezentuje An „implementuje” relację. Dlatego używaj klasy podstawowej tylko wtedy, gdy spadkobiercy utrzymają relację.
źródło
Użyj Interfejsów, aby wyegzekwować umowę ACROSS rodzin niezwiązanych klas. Na przykład możesz mieć wspólne metody dostępu dla klas reprezentujących kolekcje, ale zawierające radykalnie różne dane, tj. Jedna klasa może reprezentować zestaw wyników z zapytania, a druga może reprezentować obrazy w galerii. Ponadto można zaimplementować wiele interfejsów, co pozwala łączyć (i oznaczać) możliwości klasy.
Użyj Dziedziczenia, gdy klasy mają wspólny związek, a zatem mają podobną sygnaturę strukturalną i behawioralną, tj. Samochód, Motocykl, Ciężarówka i SUV to wszystkie rodzaje pojazdów drogowych, które mogą zawierać wiele kół, maksymalną prędkość
źródło