( Dla celów tego pytania, kiedy mówię „interfejs”, mam na myśli konstrukcję językowąinterface
, a nie „interfejs” w innym znaczeniu tego słowa, tj. Publiczne metody, które klasa oferuje światu zewnętrznemu w celu komunikowania się i manipuluj nim. )
Luźne sprzężenie można uzyskać poprzez uzależnienie obiektu od abstrakcji zamiast rodzaju betonu.
Pozwala to na luźne sprzężenie z dwóch głównych powodów: 1- abstrakcje rzadziej się zmieniają niż konkretne typy, co oznacza, że kod zależny jest mniej podatny na uszkodzenie. W czasie wykonywania można stosować 2 różne typy betonu, ponieważ wszystkie one pasują do abstrakcji. Nowe typy betonu można również dodać później, bez potrzeby zmiany istniejącego kodu zależnego.
Rozważmy na przykład klasę Car
i dwie podklasy Volvo
oraz Mazda
.
Jeśli twój kod zależy od a Car
, może używać albo a, Volvo
albo Mazda
podczas działania. Później można również dodać dodatkowe podklasy bez potrzeby zmiany kodu zależnego.
Ponadto Car
- co jest abstrakcją - jest mniej prawdopodobne, że się zmieni niż Volvo
lub Mazda
. Samochody były ogólnie takie same od dłuższego czasu, ale Volvos i Mazdas są znacznie bardziej skłonni do zmiany. Tj. Abstrakcje są bardziej stabilne niż typy konkretne.
Wszystko po to, aby pokazać, że rozumiem, czym jest luźne połączenie i jak można to osiągnąć, polegając na abstrakcjach, a nie konkrecjach. (Jeśli napisałem coś niedokładnego, proszę to powiedzieć).
Nie rozumiem tego:
Abstrakcje mogą być nadklasami lub interfejsami.
Jeśli tak, to dlaczego interfejsy są szczególnie chwalone za ich zdolność do swobodnego łączenia? Nie rozumiem, czym różni się od używania nadklasy.
Jedyne różnice, jakie widzę, to: 1- Interfejsy nie są ograniczone przez pojedyncze dziedziczenie, ale nie ma to wiele wspólnego z tematem luźnego łączenia. 2- Interfejsy są bardziej „abstrakcyjne”, ponieważ w ogóle nie mają logiki implementacji. Ale nadal nie rozumiem, dlaczego to robi tak wielką różnicę.
Proszę wyjaśnić mi, dlaczego mówi się, że interfejsy wspaniale pozwalają na luźne sprzężenie, podczas gdy proste superklasy nie są.
źródło
interfaces are essential for single-inheritance languages like Java and C# because that's the only way in which you can aggregate different behaviors into a single class
(co prowadzi mnie do porównania z C ++, gdzie interfejsy to tylko klasy z czystymi funkcjami wirtualnymi).Odpowiedzi:
Terminologia: będę odnosił się do konstrukcji języka
interface
jako interfejsu , a do interfejsu typu lub obiektu jako powierzchni (z powodu braku lepszego terminu).Poprawny.
Niezupełnie poprawne. Obecne języki na ogół nie przewidują zmiany abstrakcji (choć istnieją pewne wzorce projektowe do obsługi tego). Oddzielanie specyfiki od rzeczy ogólnych jest abstrakcją. Zwykle odbywa się to za pomocą jakiejś warstwy abstrakcji . Warstwę tę można zmienić na inną specyfikę bez łamania kodu opartego na tej abstrakcji - uzyskuje się luźne sprzężenie. Przykład inny niż OOP:
sort
Procedura może zostać zmieniona z Quicksort w wersji 1 na Tim Sort w wersji 2. Kod, który zależy tylko od sortowanego wyniku (tj. Opiera się nasort
abstrakcji), jest zatem oddzielony od rzeczywistej implementacji sortowania.To, co nazwałem powierzchnią powyżej, jest ogólną częścią abstrakcji. Obecnie w OOP zdarza się, że jeden obiekt musi czasami obsługiwać wiele abstrakcji. Niezupełnie optymalny przykład: Java
java.util.LinkedList
obsługuje zarównoList
interfejs, który dotyczy abstrakcji „uporządkowanej, indeksowalnej kolekcji”, i obsługujeQueue
interfejs, który (w przybliżeniu) dotyczy abstrakcji „FIFO”.W jaki sposób obiekt może obsługiwać wiele abstrakcji?
C ++ nie ma interfejsów, ale ma wiele elementów dziedziczenia, metod wirtualnych i klas abstrakcyjnych. Abstrakcję można następnie zdefiniować jako klasę abstrakcyjną (tj. Klasę, której nie można natychmiast utworzyć instancji), która deklaruje, ale nie definiuje metod wirtualnych. Klasy, które implementują specyfikę abstrakcji, mogą następnie odziedziczyć po tej klasie abstrakcyjnej i zaimplementować wymagane metody wirtualne.
Problem polega na tym, że wielokrotne dziedziczenie może prowadzić do problemu diamentowego , w którym kolejność wyszukiwania klas w celu implementacji metody (MRO: kolejność rozwiązywania metod) może prowadzić do „sprzeczności”. Istnieją dwie odpowiedzi na to:
Zdefiniuj rozsądny porządek i odrzuć te zamówienia, których nie można rozsądnie zlinearyzować. C3 MRO jest dość rozsądne i działa dobrze. Zostało opublikowane w 1996 roku.
Wybierz łatwą trasę i odrzuć wielokrotne dziedzictwo.
Java wybrała tę drugą opcję i wybrała pojedyncze dziedzictwo behawioralne. Nadal jednak potrzebujemy zdolności obiektu do obsługi wielu abstrakcji. Dlatego należy stosować interfejsy, które nie obsługują definicji metod, a jedynie deklaracje.
W rezultacie MRO jest oczywiste (wystarczy spojrzeć na każdą nadklasę w kolejności) i że nasz obiekt może mieć wiele powierzchni dla dowolnej liczby abstrakcji.
To okazuje się raczej niezadowalające, ponieważ dość często zachowanie jest częścią powierzchni. Rozważ
Comparable
interfejs:Jest to bardzo przyjazne dla użytkownika (ładne API z wieloma wygodnymi metodami), ale żmudne do wdrożenia. Chcielibyśmy, aby interfejs zawierał
cmp
i implementował inne metody automatycznie pod względem tej jednej wymaganej metody. Mieszanki , ale przede wszystkim cechy [ 1 ], [ 2 ] rozwiązują ten problem bez wpadania w pułapki wielokrotnego dziedziczenia.Odbywa się to poprzez zdefiniowanie składu cechy, aby cechy ostatecznie nie brały udziału w MRO - zamiast tego zdefiniowane metody są komponowane w klasę implementującą.
Comparable
Interfejs może być wyrażona w Scala jakoKiedy klasa używa tej cechy, inne metody zostają dodane do definicji klasy:
Tak
Inty(4) cmp Inty(6)
byłoby-2
iInty(4) lt Inty(6)
będzietrue
.Wiele języków ma pewne wsparcie dla cech, a każdy język, który ma „Metaobject Protocol (MOP)”, może mieć dodane cechy. Ostatnia aktualizacja Java 8 dodała domyślne metody, które są podobne do cech (metody w interfejsach mogą mieć implementacje awaryjne, dzięki czemu implementacja tych metod jest opcjonalna).
Niestety cechy są dość nowym wynalazkiem (2002), a zatem są dość rzadkie w większych językach głównego nurtu.
źródło
Po pierwsze, podtyp i abstrakcja to dwie różne rzeczy. Subtyping oznacza po prostu, że mogę zastąpić wartości jednego typu wartościami innego typu - żaden typ nie musi być abstrakcyjny.
Co ważniejsze, podklasy są bezpośrednio zależne od szczegółów implementacji ich nadklasy. To najsilniejszy rodzaj sprzężenia, jaki istnieje. W rzeczywistości, jeśli klasa podstawowa nie jest zaprojektowana z myślą o dziedziczeniu, zmiany w klasie podstawowej, które nie zmieniają jej zachowania, mogą nadal łamać podklasy i nie ma możliwości, aby wiedzieć a priori, czy nastąpi uszkodzenie. Jest to znane jako problem delikatnej klasy bazowej .
Implementacja interfejsu nie wiąże się z niczym innym, niż sam interfejs, który nie zawiera żadnego zachowania.
źródło
you want an object named A to depend on an abstraction named B instead of a concrete implementation of that abstraction named C
że zakładasz, że klasy nie są jakoś abstrakcyjne. Abstrakcja to wszystko, co ukrywa szczegóły implementacji, więc klasa z polami prywatnymi jest tak samo abstrakcyjna jak interfejs z tymi samymi metodami publicznymi.Między klasami nadrzędnymi i podrzędnymi występuje sprzężenie, ponieważ dziecko zależy od rodzica.
Powiedzmy, że mamy klasę A, a klasa B dziedziczy po niej. Jeśli wejdziemy do klasy A i zmienimy rzeczy, klasa B również ulegnie zmianie.
Powiedzmy, że mamy interfejs I, a klasa B go implementuje. Jeśli zmienimy interfejs I, to chociaż klasa B może go już nie implementować, klasa B pozostaje niezmieniona.
źródło