Widziałem to kilka razy i nie jestem pewien, co to znaczy. Kiedy i dlaczego miałbyś to zrobić?
Wiem, co robią interfejsy, ale fakt, że nie mam jasności co do tego, sprawia, że myślę, że nie używam ich poprawnie.
Czy tak jest, jeśli miałbyś zrobić:
IInterface classRef = new ObjectWhatever()
Możesz użyć dowolnej klasy, która implementuje IInterface
? Kiedy musisz to zrobić? Jedyne, o czym myślę, to posiadanie metody i brak pewności, jaki obiekt zostanie przekazany, z wyjątkiem implementacji IInterface
. Nie mogę myśleć, jak często trzeba to robić.
Jak można napisać metodę, która przyjmuje obiekt implementujący interfejs? Czy to jest możliwe?
language-agnostic
oop
interface
Damien
źródło
źródło
Odpowiedzi:
Oto kilka wspaniałych odpowiedzi na te pytania, które zawierają wiele różnych szczegółów na temat interfejsów i luźno sprzężonego kodu, odwrócenia kontroli i tak dalej. Istnieje kilka dość poważnych dyskusji, więc chciałbym skorzystać z okazji, aby trochę rozłożyć na części, aby zrozumieć, dlaczego interfejs jest użyteczny.
Kiedy po raz pierwszy zacząłem być narażony na interfejsy, ja również byłem zdezorientowany co do ich znaczenia. Nie rozumiem, dlaczego ich potrzebujesz. Jeśli używamy języka takiego jak Java lub C #, mamy już dziedziczenie, a ja widziałem interfejsy jako słabszą formę dziedziczenia i pomyślałem: „po co zawracać sobie głowę”? W pewnym sensie miałem rację, interfejsy można traktować jako słabą formę dziedziczenia, ale poza tym w końcu zrozumiałem ich użycie jako konstrukcję językową, myśląc o nich jako o sposobie klasyfikowania wspólnych cech lub zachowań, które przejawiały potencjalnie wiele niepowiązanych klas obiektów.
Na przykład - powiedzmy, że masz grę SIM i masz następujące klasy:
Oczywiście te dwa obiekty nie mają ze sobą nic wspólnego pod względem dziedziczenia bezpośredniego. Ale można powiedzieć, że oba są denerwujące.
Powiedzmy, że nasza gra musi mieć jakąś przypadkową rzecz, która denerwuje gracza podczas zjedzenia obiadu. Może to być
HouseFly
aTelemarketer
lub oba - ale jak zezwolić na jedno i drugie za pomocą jednej funkcji? I w jaki sposób prosisz każdy inny typ obiektu, aby „robił irytującą rzecz” w ten sam sposób?Kluczem do zrozumienia jest to, że zarówno „a”, jak
Telemarketer
i „HouseFly
mają wspólne, luźno interpretowane zachowanie, nawet jeśli nie są one podobne pod względem modelowania. Stwórzmy interfejs, który można wdrożyć:Mamy teraz dwie klasy, z których każda może być denerwująca na swój sposób. I nie muszą wywodzić się z tej samej klasy bazowej i dzielić wspólne nieodłączne cechy - po prostu muszą spełnić umowę
IPest
- ta umowa jest prosta. Musisz tylkoBeAnnoying
. W tym względzie możemy modelować następujące elementy:Tutaj mamy jadalnię, która akceptuje wiele gości i szkodników - zwróć uwagę na użycie interfejsu. Oznacza to, że w naszym małym świecie członkiem
pests
tablicy może byćTelemarketer
obiekt lubHouseFly
obiekt.ServeDinner
Metoda jest wywoływana, gdy kolacja serwowana jest i nasi ludzie w jadalni mają jeść. W naszej małej grze nasze szkodniki wykonują swoją pracę - każdy szkodnik jest instruowany, aby był denerwujący za pośrednictwemIPest
interfejsu. W ten sposób możemy z łatwością mieć jednoTelemarketers
i drugieHouseFlys
irytujące na każdy z ich własnych sposobów - dbamy tylko o to, że mamy wDiningRoom
obiekcie coś, co jest szkodnikiem, tak naprawdę nie dbamy o to, co to jest i nie mogą mieć nic w sobie wspólne z innymi.Ten bardzo wymyślny przykład pseudokodu (który przeciągał się o wiele dłużej, niż się spodziewałem) ma po prostu zilustrować coś, co w końcu zapaliło mnie pod kątem, kiedy możemy użyć interfejsu. Z góry przepraszam za głupotę tego przykładu, ale mam nadzieję, że to pomoże w twoim zrozumieniu. I, oczywiście, inne opublikowane odpowiedzi, które tutaj otrzymałeś, naprawdę obejmują gamę zastosowań interfejsów w dzisiejszych wzorcach projektowych i metodologiach programistycznych.
źródło
BeAnnoying
jako brak możliwości; interfejs ten może istnieć zamiast interfejsu lub jako dodatek do rzeczy, które są denerwujące (jeśli istnieją oba interfejsy, interfejs „rzeczy, które są denerwujące” powinien odziedziczyć po interfejsie „rzeczy, które mogą być denerwujące”). Wadą korzystania z takich interfejsów jest to, że implementacje mogą być obciążone wdrażaniem „irytującej” liczby metod pośredniczących. Zaletą jest to, że ...IPest[]
odwołaniach IPest można wywoływać,BeAnnoying()
ponieważ mają one tę metodę, podczas gdy nie można wywoływać innych metod bez rzutowania. Jednak dla każdego obiektuBeAnnoying()
zostanie wywołana osobna metoda.Konkretnym przykładem, który podałem uczniom, jest to, że powinni pisać
zamiast
Wyglądają dokładnie tak samo w krótkim programie, ale jeśli użyjesz
myList
100 razy w swoim programie, możesz zacząć widzieć różnicę. Pierwsza deklaracja zapewnia, że wywołujesz tylko metodymyList
zdefiniowane przezList
interfejs (więc nie maArrayList
określonych metod). Jeśli tak zaprogramowałeś interfejs, możesz później zdecydować, że naprawdę potrzebujeszi musisz zmienić kod tylko w tym jednym miejscu. Wiesz już, że reszta kodu nie robi nic, co zostanie zepsute przez zmianę implementacji, ponieważ zaprogramowałeś interfejs .
Korzyści są jeszcze bardziej oczywiste (myślę), gdy mówimy o parametrach metody i zwracanych wartościach. Weźmy na przykład:
Ta deklaracja metody wiąże cię z dwoma konkretnymi implementacjami (
ArrayList
iHashMap
). Jak tylko ta metoda zostanie wywołana z innego kodu, wszelkie zmiany tych typów prawdopodobnie oznaczają, że będziesz musiał również zmienić kod wywołujący. Lepiej byłoby zaprogramować interfejsy.Teraz nie ma znaczenia, jaki rodzaj
List
zwrotu powrócisz lub jaki parametrMap
zostanie przekazany jako parametr. Zmiany, które wprowadzasz wdoSomething
metodzie, nie zmuszają cię do zmiany kodu wywołującego.źródło
Programowanie interfejsu mówi: „Potrzebuję tej funkcjonalności i nie dbam o to, skąd ona pochodzi”.
Rozważyć (w Javie), ten
List
interfejs kontraArrayList
iLinkedList
konkretnych zajęć. Jeśli zależy mi tylko na tym, że mam strukturę danych zawierającą wiele elementów danych, do których powinienem uzyskać dostęp poprzez iterację, wybrałbymList
(i to w 99% przypadków). Jeśli wiem, że potrzebuję wstawiania / usuwania w czasie stałym z dowolnego końca listy, mogę wybraćLinkedList
konkretną implementację (lub, co bardziej prawdopodobne, użyć interfejsu kolejki ). Jeśli wiem, że potrzebuję dostępu losowego według indeksu, wybrałbymArrayList
konkretną klasę.źródło
Korzystanie z interfejsów jest kluczowym czynnikiem ułatwiającym testowanie kodu, a także usuwanie niepotrzebnych połączeń między klasami. Tworząc interfejs definiujący operacje na klasie, umożliwiasz klasom, które chcą korzystać z tej funkcji, możliwość korzystania z niej bez bezpośredniego uzależnienia od klasy implementującej. Jeśli później zdecydujesz się zmienić i zastosować inną implementację, wystarczy zmienić tylko część kodu, w której implementacja jest tworzona. Reszta kodu nie musi się zmieniać, ponieważ zależy od interfejsu, a nie od klasy implementującej.
Jest to bardzo przydatne w tworzeniu testów jednostkowych. W testowanej klasie zależy to od interfejsu i wstrzykuje instancję interfejsu do klasy (lub fabryki, która pozwala mu budować instancje interfejsu w razie potrzeby) za pośrednictwem konstruktora lub ustawiacza właściwości. Klasa używa podanego (lub utworzonego) interfejsu w swoich metodach. Kiedy idziesz pisać testy, możesz kpić lub sfałszować interfejs i zapewnić interfejs, który odpowiada danymi skonfigurowanymi w teście jednostkowym. Możesz to zrobić, ponieważ testowana klasa zajmuje się tylko interfejsem, a nie konkretną implementacją. Zrobią to wszystkie klasy implementujące interfejs, w tym twoja próbna lub fałszywa klasa.
EDYCJA: Poniżej znajduje się link do artykułu, w którym Erich Gamma omawia swój cytat: „Program do interfejsu, a nie implementacja”.
http://www.artima.com/lejava/articles/designprinciples.html
źródło
Programowanie interfejsu nie ma absolutnie nic wspólnego z abstrakcyjnymi interfejsami, jak widzimy w Javie lub .NET. To nawet nie jest koncepcja OOP.
Oznacza to, że nie należy się bawić w elementy wewnętrzne obiektu lub struktury danych. Użyj interfejsu programu abstrakcyjnego lub interfejsu API do interakcji z danymi. W Javie lub C # oznacza to używanie publicznych właściwości i metod zamiast surowego dostępu do pola. Dla C oznacza to używanie funkcji zamiast surowych wskaźników.
EDYCJA: A w przypadku baz danych oznacza to używanie widoków i procedur przechowywanych zamiast bezpośredniego dostępu do tabeli.
źródło
Powinieneś spojrzeć na Odwrócenie kontroli:
W takim scenariuszu nie napisałbyś tego:
Napisałbyś coś takiego:
Spowoduje to przejście do konfiguracji opartej na regułach w
container
obiekcie i skonstruowanie dla Ciebie rzeczywistego obiektu, którym może być ObjectWhokolwiek. Ważną rzeczą jest to, że możesz zastąpić tę regułę czymś, co używa zupełnie innego typu obiektu, a Twój kod nadal będzie działał.Jeśli zostawimy IoC poza tabelą, możesz napisać kod, który będzie wiedział, że może komunikować się z obiektem, który robi coś konkretnego , ale nie z jakiego typu obiektu i jak to robi.
Przydałoby się to przy przekazywaniu parametrów.
Jeśli chodzi o twoje nawiasowe pytanie „Jak można napisać metodę, która przyjmuje obiekt, który implementuje interfejs? Czy to możliwe?”, W języku C # wystarczy użyć typu interfejsu dla typu parametru, jak poniżej:
To podłącza się bezpośrednio do „rozmowy z obiektem, który robi coś konkretnego”. Zdefiniowana powyżej metoda wie, czego oczekiwać od obiektu, że implementuje wszystko w IInterface, ale nie dba o to, jaki jest typ obiektu, tylko przestrzega kontraktu, jakim jest interfejs.
Na przykład, prawdopodobnie znasz kalkulatory i prawdopodobnie używałeś sporo w swoich czasach, ale przez większość czasu są one różne. Ty z drugiej strony wiesz, jak powinien działać standardowy kalkulator, więc możesz korzystać z nich wszystkich, nawet jeśli nie możesz korzystać z określonych funkcji, które ma każdy kalkulator, których nie ma żaden inny.
To jest piękno interfejsów. Możesz napisać fragment kodu, który wie, że otrzyma obiekty, od których może oczekiwać określonego zachowania. Nie przejmuje się jednym hukiem, jaki to obiekt, tylko że obsługuje potrzebne zachowanie.
Dam ci konkretny przykład.
Mamy niestandardowy system tłumaczenia formularzy Windows. Ten system zapętla formanty w formularzu i tłumaczy tekst w każdym z nich. System wie, jak obsługiwać podstawowe elementy sterujące, takie jak właściwość-type-of-control-that-has-a-Text i podobne podstawowe rzeczy, ale w przypadku wszystkiego podstawowego jest on niewystarczający.
Ponieważ kontrole dziedziczą po predefiniowanych klasach, nad którymi nie mamy kontroli, moglibyśmy zrobić jedną z trzech rzeczy:
Więc zrobiliśmy nr. 3. Wszystkie nasze kontrolki implementują ILocalizable, który jest interfejsem, który daje nam jedną metodę, możliwość przetłumaczenia „samego” na pojemnik tekstu / reguł tłumaczenia. W związku z tym formularz nie musi wiedzieć, jaki rodzaj kontroli znalazł, tylko że implementuje konkretny interfejs i wie, że istnieje metoda, w której można go wywołać w celu zlokalizowania kontroli.
źródło
Kod interfejsu Nie Implementacja nie ma nic wspólnego z Javą ani jej konstrukcją interfejsu.
Ta koncepcja została wyeksponowana w książkach Patterns / Gang of Four, ale najprawdopodobniej była wcześniej na długo. Koncepcja z pewnością istniała na długo przed powstaniem Javy.
Konstrukcja interfejsu Java została stworzona, aby pomóc w tym pomyśle (między innymi), a ludzie zbyt skupili się na konstrukcji jako centrum znaczenia, a nie pierwotnych zamiarach. Jest to jednak powód, dla którego mamy publiczne i prywatne metody i atrybuty w Javie, C ++, C # itp.
Oznacza to po prostu interakcję z publicznym interfejsem obiektu lub systemu. Nie martw się ani nawet nie przewiduj, w jaki sposób robi to, co robi wewnętrznie. Nie martw się o to, jak to jest realizowane. W kodzie zorientowanym obiektowo właśnie dlatego mamy publiczne / prywatne metody / atrybuty. Mamy zamiar korzystać z metod publicznych, ponieważ metody prywatne są dostępne tylko do użytku wewnętrznego w klasie. Składają się na implementację klasy i mogą być zmieniane zgodnie z wymaganiami bez zmiany interfejsu publicznego. Załóżmy, że jeśli chodzi o funkcjonalność, metoda na klasie wykona tę samą operację z takim samym oczekiwanym rezultatem za każdym razem, gdy wywołasz ją z tymi samymi parametrami. Pozwala autorowi zmienić sposób działania klasy, jej implementację, bez przerywania interakcji z nią.
I możesz programować do interfejsu, a nie implementacji bez użycia konstrukcji interfejsu. Możesz zaprogramować interfejs, a nie implementację w C ++, który nie ma konstrukcji interfejsu. Możesz zintegrować dwa potężne systemy korporacyjne o wiele solidniej, pod warunkiem, że współdziałają one za pośrednictwem publicznych interfejsów (kontraktów), zamiast wywoływać metody na obiektach wewnętrznych systemów. Interfejsy powinny zawsze reagować w ten sam oczekiwany sposób, biorąc pod uwagę te same parametry wejściowe; jeśli zaimplementowane do interfejsu, a nie implementacji. Koncepcja działa w wielu miejscach.
Pomiń myśl, że interfejsy Java mają cokolwiek wspólnego z koncepcją „Program do interfejsu, a nie implementacja”. Mogą one pomóc zastosować koncepcję, ale są one nie pojęcie.
źródło
Wygląda na to, że rozumiesz, jak działają interfejsy, ale nie masz pewności, kiedy ich używać i jakie oferują one zalety. Oto kilka przykładów, kiedy interfejs miałby sens:
potem mógłbym utworzyć GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider itp.
następnie utwórz JpegImageLoader, GifImageLoader, PngImageLoader itp.
Większość dodatków i systemów wtyczek działa bez interfejsów.
Innym popularnym zastosowaniem jest wzorzec repozytorium. Powiedz, że chcę załadować listę kodów pocztowych z różnych źródeł
następnie mógłbym utworzyć XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository itp. Dla moich aplikacji internetowych często tworzę repozytoria XML wcześnie, aby móc coś uruchomić i uruchomić, zanim baza danych SQL będzie gotowa. Gdy baza danych jest gotowa, piszę SQLRepository, aby zastąpić wersję XML. Reszta mojego kodu pozostaje niezmieniona, ponieważ działa wyłącznie poza interfejsami.
Metody mogą akceptować interfejsy, takie jak:
źródło
To sprawia, że twój kod jest znacznie bardziej rozszerzalny i łatwiejszy w utrzymaniu, gdy masz zestawy podobnych klas. Jestem młodszym programistą, więc nie jestem ekspertem, ale właśnie skończyłem projekt, który wymagał czegoś podobnego.
Pracuję na oprogramowaniu po stronie klienta, które komunikuje się z serwerem z urządzeniem medycznym. Pracujemy nad nową wersją tego urządzenia, która zawiera nowe komponenty, które klient musi czasem skonfigurować. Istnieją dwa rodzaje nowych komponentów i są one różne, ale są również bardzo podobne. Zasadniczo musiałem utworzyć dwie formularze konfiguracji, dwie listy klas, dwa wszystkiego.
Zdecydowałem, że najlepiej będzie utworzyć abstrakcyjną klasę podstawową dla każdego typu kontrolnego, która zawierałaby prawie całą prawdziwą logikę, a następnie wyprowadzić typy, aby zająć się różnicami między tymi dwoma składnikami. Jednak klasy podstawowe nie byłyby w stanie wykonywać operacji na tych komponentach, gdybym musiał cały czas martwić się typami (no cóż, mogłyby to mieć, ale w każdej metodzie byłaby instrukcja „if” lub przełącznik) .
Zdefiniowałem prosty interfejs dla tych komponentów i wszystkie klasy podstawowe rozmawiają z tym interfejsem. Teraz, kiedy coś zmieniam, prawie „po prostu działa” wszędzie i nie mam duplikacji kodu.
źródło
Istnieje wiele wyjaśnień, ale dla uproszczenia. Weźmy na przykład a
List
. Można zaimplementować listę jako:Budując do interfejsu, powiedz a
List
. Podajesz tylko definicję Listy lub coList
w rzeczywistości oznacza.Możesz użyć dowolnego rodzaju wewnętrznej implementacji, powiedz
array
implementację. Ale załóżmy, że chcesz zmienić implementację z jakiegoś powodu, powiedz błąd lub wydajność. Następnie wystarczy zmienić deklaracjęList<String> ls = new ArrayList<String>()
naList<String> ls = new LinkedList<String>()
.Nigdzie indziej w kodzie nie będziesz musiał niczego zmieniać; Ponieważ wszystko inne zostało zbudowane na podstawie definicji
List
.źródło
Jeśli programujesz w Javie, JDBC jest dobrym przykładem. JDBC definiuje zestaw interfejsów, ale nic nie mówi o implementacji. Twoje aplikacje mogą być napisane w oparciu o ten zestaw interfejsów. Teoretycznie wybierasz sterownik JDBC, a aplikacja po prostu działa. Jeśli odkryjesz, że istnieje szybszy lub „lepszy” lub tańszy sterownik JDBC lub z jakiegokolwiek innego powodu, możesz ponownie teoretycznie ponownie skonfigurować plik właściwości i bez konieczności wprowadzania zmian w aplikacji, aplikacja nadal działałaby.
źródło
Programowanie interfejsów jest niesamowite, promuje luźne sprzężenie. Jak wspomniano @lassevk, Inversion of Control jest świetnym zastosowaniem tego.
Ponadto spójrz na zasady SOLID . oto seria filmów
Przechodzi przez mocno zakodowany (silnie sprzężony przykład), a następnie przegląda interfejsy, w końcu przechodzi do narzędzia IoC / DI (NInject)
źródło
Jestem spóźniony na to pytanie, ale chcę tutaj wspomnieć, że wiersz „Program do interfejsu, a nie implementacja” miał dobrą dyskusję w książce Wzory projektowe GoF (Gang of Four).
Stwierdzono na str. 18:
a ponadto zaczęło się od:
Innymi słowy, nie pisz tego swoim klasom, aby zawierała
quack()
metodę dla kaczek, a następniebark()
metodę dla psów, ponieważ są one zbyt specyficzne dla konkretnej implementacji klasy (lub podklasy). Zamiast tego napisz metodę, używając nazw, które są na tyle ogólne, że można ich używać w klasie podstawowej, takich jakgiveSound()
lubmove()
, aby można było ich używać w przypadku kaczek, psów, a nawet samochodów, a wtedy klient twoich klas może po prostu powiedzieć,.giveSound()
a nie zastanawiając się, czy użyć,quack()
czybark()
nawet określić typ, przed wydaniem poprawnej wiadomości, która zostanie wysłana do obiektu.źródło
Oprócz już wybranej odpowiedzi (i różnych postów informacyjnych tutaj), zdecydowanie polecam pobranie kopii Wzorów pierwszego projektu . Jest to bardzo łatwa lektura i bezpośrednio odpowie na twoje pytanie, wyjaśni, dlaczego jest to ważne i pokaże wiele wzorców programowania, których możesz użyć, aby skorzystać z tej zasady (i innych).
źródło
Aby dodać do istniejących postów, czasami kodowanie interfejsów pomaga w dużych projektach, gdy programiści pracują jednocześnie nad oddzielnymi komponentami. Wystarczy zdefiniować interfejsy z góry i napisać do nich kod, podczas gdy inni programiści piszą kod do wdrażanego interfejsu.
źródło
Jest również dobry do testowania jednostkowego, możesz wstrzyknąć własne klasy (które spełniają wymagania interfejsu) do klasy, która jest od niego zależna
źródło
Korzystne może być programowanie interfejsów, nawet jeśli nie jesteśmy zależni od abstrakcji.
Programowanie interfejsów zmusza nas do użycia odpowiedniego kontekstowo podzbioru obiektu . To pomaga, ponieważ:
Na przykład, rozważmy
Person
klasy implementującejFriend
iEmployee
interfejs.W kontekście urodzin danej osoby programujemy
Friend
interfejs, aby zapobiec traktowaniu tej osoby jak kogośEmployee
.W kontekście pracy osoby programujemy
Employee
interfejs, aby zapobiec rozmyciu granic miejsca pracy.Świetny. Zachowaliśmy się odpowiednio w różnych kontekstach, a nasze oprogramowanie działa dobrze.
W dalekiej przyszłości, jeśli nasza firma zmieni się na pracę z psami, możemy dość łatwo zmienić oprogramowanie. Najpierw tworzymy
Dog
klasę, która implementuje zarównoFriend
iEmployee
. Następnie bezpiecznie zmieniamynew Person()
nanew Dog()
. Nawet jeśli obie funkcje mają tysiące wierszy kodu, ta prosta edycja będzie działać, ponieważ wiemy, że następujące są prawdziwe:party
używa tylkoFriend
podzbioruPerson
.workplace
używa tylkoEmployee
podzbioruPerson
.Dog
implementuje zarówno interfejsy, jakFriend
iEmployee
interfejsy.Z drugiej strony, gdyby którykolwiek
party
lubworkplace
miałby się programować przeciwkoPerson
, istniałoby ryzyko, że oba będą miałyPerson
kod -specyficzny. Zmiana zPerson
naDog
wymagałaby od nas przeczesania kodu w celu wytępienia dowolnegoPerson
kodu specyficznego dla tegoDog
.Morał : programowanie interfejsów pomaga naszemu kodowi zachowywać się odpowiednio i być gotowym na zmiany. Przygotowuje również nasz kod do zależności od abstrakcji, co daje jeszcze więcej korzyści.
źródło
Jeśli piszę nową klasę,
Swimmer
aby dodać funkcjonalnośćswim()
i muszę użyć obiektu klasy powiedzDog
, a taDog
klasa implementuje interfejs,Animal
który deklarujeswim()
.Na górze hierarchii (
Animal
) jest bardzo abstrakcyjna, a na dole (Dog
) bardzo konkretna. Sposób, w jaki myślę o „programowaniu interfejsów”, polega na tym, że piszącSwimmer
klasę, chcę pisać mój kod na interfejsie znajdującym się tak daleko od hierarchii, która w tym przypadku jestAnimal
obiektem. Interfejs jest wolny od szczegółów implementacyjnych, przez co kod jest luźno powiązany.Szczegóły implementacji można zmieniać z czasem, jednak nie wpłynie to na pozostały kod, ponieważ interakcja odbywa się wyłącznie z interfejsem, a nie z implementacją. Nie obchodzi cię, jak wygląda implementacja ... wiesz tylko, że będzie klasa, która implementuje interfejs.
źródło
Tak więc, aby to zrobić poprawnie, zaletą interfejsu jest to, że mogę oddzielić wywołanie metody od dowolnej konkretnej klasy. Zamiast tego tworzy instancję interfejsu, w której implementacja jest podana z dowolnej klasy, którą wybiorę, która implementuje ten interfejs. Dzięki temu mogę mieć wiele klas, które mają podobną, ale nieco inną funkcjonalność, aw niektórych przypadkach (przypadki związane z intencją interfejsu) nie dbam o to, który to obiekt.
Na przykład mógłbym mieć interfejs ruchu. Metoda, która sprawia, że coś „porusza się” i dowolny obiekt (Osoba, Samochód, Kot), który implementuje interfejs ruchu, może zostać przekazana i nakazana ruch. Bez tej metody wszyscy znają rodzaj klasy.
źródło
Wyobraź sobie, że masz produkt o nazwie „Zebra”, który można rozszerzyć za pomocą wtyczek. Znajduje wtyczki, wyszukując biblioteki DLL w jakimś katalogu. Ładuje wszystkie te biblioteki DLL i używa refleksji, aby znaleźć wszystkie implementujące klasy
IZebraPlugin
, a następnie wywołuje metody tego interfejsu w celu komunikacji z wtyczkami.To czyni go całkowicie niezależnym od jakiejkolwiek konkretnej klasy wtyczek - nie ma znaczenia, jakie to klasy. Dba tylko o to, aby spełniały specyfikację interfejsu.
Interfejsy są sposobem definiowania takich punktów rozszerzalności. Kod komunikujący się z interfejsem jest luźniej sprzężony - w rzeczywistości nie jest w ogóle sprzężony z żadnym innym konkretnym kodem. Może współpracować z wtyczkami napisanymi wiele lat później przez osoby, które nigdy nie spotkały oryginalnego programisty.
Zamiast tego możesz użyć klasy podstawowej z funkcjami wirtualnymi - wszystkie wtyczki zostałyby wyprowadzone z klasy podstawowej. Jest to jednak znacznie bardziej ograniczające, ponieważ klasa może mieć tylko jedną klasę bazową, podczas gdy może implementować dowolną liczbę interfejsów.
źródło
Objaśnienie C ++.
Pomyśl o interfejsie jako metodach publicznych swoich klas.
Następnie możesz utworzyć szablon, który „zależy” od tych publicznych metod w celu wykonania własnej funkcji (sprawia, że wywołania funkcji są zdefiniowane w interfejsie publicznym klas). Powiedzmy, że ten szablon jest kontenerem, takim jak klasa Vector, a interfejs, od którego zależy, jest algorytmem wyszukiwania.
Każda klasa algorytmów, która definiuje funkcje / interfejs Vector, do którego wywołuje Vector, spełnia „kontrakt” (jak ktoś wyjaśnił w oryginalnej odpowiedzi). Algorytmy nie muszą nawet należeć do tej samej klasy podstawowej; jedynym wymaganiem jest zdefiniowanie funkcji / metod, od których Vector zależy (interfejs), w twoim algorytmie.
Chodzi o to, że można podać dowolny inny algorytm / klasę wyszukiwania, o ile zapewnia on interfejs, od którego Vector jest zależny (wyszukiwanie bąbelkowe, wyszukiwanie sekwencyjne, szybkie wyszukiwanie).
Możesz także zaprojektować inne kontenery (listy, kolejki), które wykorzystywałyby ten sam algorytm wyszukiwania, co Vector, dzięki temu, że spełniają interfejs / kontrakt, od którego zależą twoje algorytmy wyszukiwania.
To oszczędza czas (zasada OOP „ponowne użycie kodu”), ponieważ możesz napisać algorytm raz za razem, specyficzny dla każdego nowego obiektu, który tworzysz, bez nadmiernego komplikowania problemu z przerośniętym drzewem dziedziczenia.
Co do „pominięcia” sposobu działania; big-time (przynajmniej w C ++), ponieważ tak działa większość frameworku Standardowej Biblioteki TEMPLATE.
Oczywiście przy stosowaniu klas dziedziczenia i klas abstrakcyjnych zmienia się metodologia programowania interfejsu; ale zasada jest taka sama, twoje publiczne funkcje / metody są interfejsem klas.
To ogromny temat i jedna z podstawowych zasad wzorców projektowych.
źródło
W Javie te konkretne klasy implementują interfejs CharSequence:
Te konkretne klasy nie mają wspólnej klasy nadrzędnej innej niż Object, więc nie ma nic, co by je łączyło, poza faktem, że każda z nich ma coś wspólnego z tablicami znaków, reprezentującymi takie lub manipulującymi nimi. Na przykład znaków String nie można zmienić po utworzeniu instancji obiektu String, natomiast można edytować znaki StringBuffer lub StringBuilder.
Jednak każda z tych klas jest w stanie odpowiednio zaimplementować metody interfejsu CharSequence:
W niektórych przypadkach klasy bibliotek klas Java, które kiedyś akceptowały String, zostały zmienione, aby teraz akceptowały interfejs CharSequence. Jeśli więc masz instancję StringBuilder, zamiast wyodrębnić obiekt String (co oznacza utworzenie instancji nowej instancji obiektu), zamiast tego możesz po prostu przekazać samą StringBuilder, ponieważ implementuje on interfejs CharSequence.
Interfejs Appendable wdrażany przez niektóre klasy ma tę samą korzyść w każdej sytuacji, w której znaki mogą być dołączane do instancji podstawowej instancji konkretnego obiektu klasy. Wszystkie te konkretne klasy implementują interfejs Appendable:
źródło
CharSequence
są tak anemiczne. Chciałbym, aby Java i .NET zezwoliły interfejsom na domyślną implementację, aby ludzie nie ograniczali interfejsów wyłącznie w celu zminimalizowania kodu podstawowego. Biorąc pod uwagę każdą prawidłowąCharSequence
implementację, można emulować większość funkcjiString
przy użyciu tylko powyższych czterech metod, ale wiele implementacji może wykonywać te funkcje znacznie wydajniej na inne sposoby. Niestety, nawet jeśli określona implementacjaCharSequence
zawiera wszystko w jednymchar[]
i może wykonać wiele ...indexOf
szybko, nie ma mowy, żeby osoba dzwoniąca, która nie zna konkretnej implementacji,CharSequence
mogła o to poprosić, zamiast musiećcharAt
sprawdzać każdą indywidualną postać.Krótka historia: Listonosz proszony jest o powrót do domu po domu i otrzymanie okładek zawierających (listy, dokumenty, czeki, karty podarunkowe, podanie, list miłosny) z adresem podanym na doręczeniu.
Załóżmy, że nie ma osłony i poproś listonosza, aby poszedł do domu po domu i odebrał wszystkie rzeczy i dostarczył innym ludziom, listonosz może się pomylić.
Lepiej więc zawiń go w osłonę (w naszej historii jest to interfejs), wtedy wykona swoją pracę dobrze.
Teraz zadaniem listonosza jest przyjmowanie i dostarczanie tylko okładek (nie przejmowałby się tym, co jest w okładce).
Utwórz typ
interface
niefaktyczny, ale zaimplementuj go z typem faktycznym.Tworzenie do interfejsu oznacza, że otrzymujesz komponenty dopasowują się do reszty kodu
Dam ci przykład.
masz interfejs AirPlane, jak poniżej.
Załóżmy, że masz metody w swojej klasie samolotów typu Controller
i
zaimplementowane w twoim programie. Nie spowoduje to przerwania twojego kodu. To znaczy, nie musi się zmieniać, dopóki akceptuje argumenty jako
AirPlane
.Ponieważ będzie akceptować każdego samolotu pomimo rzeczywistego typu,
flyer
,highflyr
,fighter
, itd.Ponadto w kolekcji:
List<Airplane> plane;
// Zabierze wszystkie twoje samoloty.Poniższy przykład wyjaśni twoje zrozumienie.
Masz samolot myśliwski, który go implementuje, więc
To samo dotyczy HighFlyer i innych klases:
Teraz pomyśl, że twoje klasy kontrolerów używają
AirPlane
kilka razy,Załóżmy, że twoja klasa Controller to ControlPlane jak poniżej,
Tutaj pojawia się magia, ponieważ możesz tworzyć
AirPlane
instancje nowego typu tyle, ile chcesz i nie zmieniasz koduControlPlane
klasy.Możesz dodać instancję ...
Możesz także usunąć instancje wcześniej utworzonych typów.
źródło
Interfejs jest jak kontrakt, w którym klasa implementacji ma implementować metody zapisane w kontrakcie (interfejsie). Ponieważ Java nie zapewnia wielokrotnego dziedziczenia, „programowanie do interfejsu” jest dobrym sposobem na uzyskanie wielokrotnego dziedziczenia.
Jeśli masz klasę A, która już rozszerza niektóre inne klasy B, ale chcesz, aby klasa A również postępowała zgodnie z pewnymi wytycznymi lub realizowała określoną umowę, możesz to zrobić, stosując strategię „programowania do interfejsu”.
źródło
Uwaga: nie mogliśmy utworzyć instancji interfejsu niezaimplementowanego przez klasę - prawda.
Uwaga: Teraz mogliśmy zrozumieć, co się stanie, jeśli Bclass i Cclass zaimplementują ten sam Dintf.
Co mamy: Te same prototypy interfejsów (nazwy funkcji w interfejsie) i wywołują różne implementacje.
Bibliografia: Prototypy - wikipedia
źródło
Program do interfejsu pozwala płynnie zmieniać implementację umowy zdefiniowanej przez interfejs. Umożliwia luźne powiązanie między kontraktem a konkretnymi implementacjami.
Spójrz na to pytanie SE na dobry przykład.
Dlaczego interfejs klasy Java powinien być preferowany?
Tak. Będzie miał niewielki narzut wydajności w ciągu kilku sekund. Ale jeśli Twoja aplikacja wymaga dynamicznej zmiany implementacji interfejsu, nie martw się o wpływ na wydajność.
Nie próbuj unikać wielu implementacji interfejsu, jeśli Twoja aplikacja ich potrzebuje. W przypadku braku ścisłego powiązania interfejsu z jedną konkretną implementacją może być konieczne wdrożenie poprawki w celu zmiany jednej implementacji na inną implementację.
Jeden dobry przykład zastosowania: wdrożenie wzorca strategii:
Przykład wzorca strategii w świecie rzeczywistym
źródło
program do interfejsu to termin z książki GOF. nie powiedziałbym bezpośrednio, że ma to związek z interfejsem Java, ale raczej prawdziwymi interfejsami. aby osiągnąć czystą separację warstw, musisz na przykład utworzyć separację między systemami: Załóżmy, że masz konkretną bazę danych, której chcesz użyć, nigdy nie „programowałbyś bazy danych”, zamiast tego „programowałbyś interfejs pamięci”. Podobnie nigdy nie „programowałbyś się do usługi sieci Web”, ale programowałbyś się w „interfejsie klienta”. dzięki temu możesz łatwo wymieniać rzeczy.
uważam, że te zasady pomogą mi:
1 . używamy interfejsu Java, gdy mamy wiele typów obiektów. jeśli mam tylko jeden obiekt, nie widzę sensu. jeśli są co najmniej dwie konkretne implementacje jakiegoś pomysłu, wtedy użyłbym interfejsu Java.
2 . jeśli, jak powiedziałem powyżej, chcesz przenieść oddzielenie od systemu zewnętrznego (system pamięci masowej) do własnego systemu (lokalna baza danych), użyj również interfejsu.
zwróć uwagę, że istnieją dwa sposoby rozważenia, kiedy z nich korzystać. mam nadzieję że to pomoże.
źródło
Widzę też wiele dobrych i wyjaśniających odpowiedzi, więc chcę tu przedstawić swój punkt widzenia, w tym dodatkowe informacje, które zauważyłem podczas korzystania z tej metody.
Testów jednostkowych
Przez ostatnie dwa lata napisałem projekt hobby i nie napisałem dla niego testów jednostkowych. Po napisaniu około 50 000 wierszy odkryłem, że naprawdę konieczne będzie napisanie testów jednostkowych. Nie korzystałem z interfejsów (lub bardzo oszczędnie) ... i kiedy wykonałem swój pierwszy test jednostkowy, okazało się, że jest to skomplikowane. Dlaczego?
Ponieważ musiałem wykonać wiele instancji klas, używanych do wprowadzania danych jako zmiennych klas i / lub parametrów. Testy wyglądają więc bardziej jak testy integracyjne (konieczność stworzenia kompletnego „frameworka” klas, ponieważ wszystko było powiązane).
Strach przed interfejsami Więc postanowiłem użyć interfejsów. Obawiałem się, że musiałem zaimplementować całą funkcjonalność wszędzie (we wszystkich używanych klasach) wiele razy. W pewnym sensie jest to jednak prawdą, ale dzięki dziedziczeniu można je znacznie zmniejszyć.
Połączenie interfejsów i dziedziczenia Dowiedziałem się, że kombinacja jest bardzo dobra do użycia. Daję bardzo prosty przykład.
W ten sposób kopiowanie kodu nie jest konieczne, przy jednoczesnym korzystaniu z samochodu jako interfejsu (ICar).
źródło
Zacznijmy od kilku definicji:
Interfejs n. Zestaw wszystkich podpisów zdefiniowanych przez operacje obiektu nazywa się interfejsem do obiektu
Wpisz n. Szczególny interfejs
Prostym przykładem interfejs , jak zdefiniowano powyżej, są wszystkie sposoby obiektu PDO, takie jak
query()
,commit()
,close()
itd., Jako całości, a nie oddzielnie. Te metody, tj. Interfejs definiują pełny zestaw komunikatów, żądań, które mogą być wysyłane do obiektu.Typu , jak określono powyżej jest szczególnie interfejsu. Użyję-up wykonany interfejs kształt wykazać:
draw()
,getArea()
,getPerimeter()
etc ..Jeśli obiekt jest typu danych mamy na myśli, że przyjmuje wiadomości / wnioski interfejsu bazy danych
query()
,commit()
itd .. Obiekty mogą być różnego rodzaju. Możesz mieć obiekt bazy danych typu kształtu, o ile implementuje on interfejs, w takim przypadku byłoby to podtypowanie .Wiele obiektów może mieć wiele różnych interfejsów / typów i implementować ten interfejs w różny sposób. To pozwala nam zastępować obiekty, pozwalając nam wybrać, którego z nich użyć. Znany również jako polimorfizm.
Klient będzie wiedział tylko o interfejsie, a nie o implementacji.
Tak więc w istocie do programowania interfejsu wiązałoby co jakiś rodzaj klasy abstrakcyjnej takie jak
Shape
z interfejsem tylko określony tjdraw()
,getCoordinates()
,getArea()
etc .. A potem mają różne klasy konkretne wdrożenia tych interfejsów, takich jak koło, kwadrat klasy klasy, klasy Triangle. Stąd program do interfejsu, a nie implementacja.źródło
„Program do interfejsu” oznacza, że nie zapewniasz poprawnego kodu, co oznacza, że Twój kod powinien zostać rozszerzony bez zerwania z poprzednią funkcjonalnością. Tylko rozszerzenia, a nie edycja poprzedniego kodu.
źródło