Powiedzmy, że mam ten interfejs:
public interface IBox
{
public void setSize(int size);
public int getSize();
public int getArea();
//...and so on
}
Mam klasę, która to implementuje:
public class Rectangle implements IBox
{
private int size;
//Methods here
}
Gdybym chciał skorzystać z interfejsu IBox, nie mogę w rzeczywistości utworzyć jego instancji, w ten sposób:
public static void main(String args[])
{
Ibox myBox=new Ibox();
}
dobrze? Więc właściwie musiałbym to zrobić:
public static void main(String args[])
{
Rectangle myBox=new Rectangle();
}
Jeśli to prawda, to jedynym celem interfejsów jest upewnienie się, że klasa implementująca interfejs ma w sobie prawidłowe metody opisane przez interfejs? Czy jest jakieś inne zastosowanie interfejsów?
java
oop
language-features
interface
Kliknij opcję Głos za
źródło
źródło
Odpowiedzi:
Interfejsy to sposób na uelastycznienie kodu. To co robisz to:
Później, jeśli zdecydujesz, że chcesz użyć innego rodzaju pudełka (może jest inna biblioteka, z lepszym rodzajem pudełka), przełączasz swój kod na:
Kiedy już się do tego przyzwyczaisz, przekonasz się, że jest to świetny (właściwie niezbędny) sposób na pracę.
Innym powodem jest na przykład to, że chcesz utworzyć listę skrzynek i wykonać na każdym z nich jakąś operację, ale chcesz, aby lista zawierała różne rodzaje skrzynek. Na każdym pudełku możesz:
(zakładając, że IBox ma metodę close ()), mimo że aktualna klasa myBox zmienia się w zależności od tego, w którym polu się znajdujesz w iteracji.
źródło
To, co sprawia, że interfejsy są użyteczne, to nie fakt, że „możesz zmienić zdanie i użyć innej implementacji później i zmienić tylko jedno miejsce, w którym tworzony jest obiekt”. To nie jest problem.
Prawdziwy punkt jest już w nazwie: definiują interfejs, który każdy może zaimplementować, aby używać całego kodu działającego na tym interfejsie. Najlepszym przykładem jest ten,
java.util.Collections
który dostarcza wszelkiego rodzaju użytecznych metod, które działają wyłącznie na interfejsach, takich jaksort()
lubreverse()
dlaList
. Chodzi o to, że ten kod może być teraz używany do sortowania lub odwracania dowolnej klasy, która implementujeList
interfejsy - nie tylkoArrayList
iLinkedList
, ale także klas, które sam piszesz, co może być zaimplementowane w sposób, którego ludzie, którzy pisali,java.util.Collections
nigdy sobie nie wyobrażali.W ten sam sposób możesz napisać kod działający na dobrze znanych interfejsach lub interfejsach, które zdefiniujesz, a inne osoby mogą używać Twojego kodu bez konieczności proszenia Cię o obsługę ich klas.
Innym powszechnym zastosowaniem interfejsów jest wywołanie zwrotne. Na przykład java.swing.table.TableCellRenderer , który pozwala wpływać na sposób wyświetlania danych w określonej kolumnie w tabeli Swing. Implementujesz ten interfejs, przekazujesz instancję do
JTable
, aw pewnym momencie podczas renderowania tabeli Twój kod zostanie wywołany, aby wykonać swoje zadanie.źródło
you can write code that operates on well-known interfaces, or interfaces you define
Jednym z wielu zastosowań, które przeczytałem, jest sytuacja, w której jest to trudne bez interfejsów wielokrotnego dziedziczenia w Javie:
Teraz wyobraź sobie przypadek, w którym:
ale,
Lepszy projekt byłby:
Zwierzę nie ma metody chew () i zamiast tego jest umieszczane w interfejsie jako:
i klasa Reptile zaimplementowała to, a nie Birds (ponieważ ptaki nie mogą żuć):
aw przypadku ptaków po prostu:
źródło
Reptile
„żuje”, to samo w sobie jest „do żucia”. Konwencja (czasami) nazywania interfejsów Cokolwiek bywa, powinna być stosowana tylko wtedy, gdy ma to sens. Nazwanie interfejsuPredator
byłoby tutaj bardziej odpowiednie.Celem interfejsów jest polimorfizm , czyli podstawianie typu . Na przykład, biorąc pod uwagę następującą metodę:
Podczas wywoływania
scale
metody można podać dowolną wartość typu implementującegoIBox
interfejs. Innymi słowy, jeśliRectangle
iSquare
oba implementująIBox
, możesz podać albo a,Rectangle
alboSquare
gdziekolwiekIBox
oczekiwano.źródło
Interfejsy pozwalają na obsługę polimorfizmu przez języki statyczne. Purysta zorientowany obiektowo nalegałby, aby język zapewniał dziedziczenie, hermetyzację, modułowość i polimorfizm, aby był w pełni funkcjonalnym językiem zorientowanym obiektowo. W językach dynamicznie wpisywanych - lub kaczych - (jak Smalltalk) polimorfizm jest trywialny; jednak w językach statycznie typowanych (takich jak Java czy C #) polimorfizm nie jest trywialny (w rzeczywistości wydaje się, że jest on sprzeczny z pojęciem silnego pisania).
Pozwól, że zademonstruję:
W języku dynamicznie wpisywanym (lub kaczkowym) (takim jak Smalltalk) wszystkie zmienne są odniesieniami do obiektów (nic mniej i nic więcej). Więc w Smalltalk mogę zrobić to:
Ten kod:
makeNoise
do świni.Ten sam kod Java wyglądałby mniej więcej tak (przy założeniu, że Duck i Cow są podklasami klasy Animal:
Wszystko dobrze, dopóki nie wprowadzimy klasy Warzywa. Warzywa zachowują się podobnie jak zwierzęta, ale nie wszystkie. Na przykład zarówno zwierzęta, jak i warzywa mogą rosnąć, ale wyraźnie warzywa nie hałasują, a zwierzęta nie mogą być zbierane.
W Smalltalk możemy napisać to:
Działa to doskonale w Smalltalk, ponieważ jest on wpisywany na klawiaturze typu kaczka (jeśli chodzi jak kaczka, a kwacze jak kaczka - to jest kaczka). W tym przypadku, gdy wiadomość jest wysyłana do obiektu, wyszukiwanie jest wykonywane na lista metod odbiorcy i jeśli zostanie znaleziona pasująca metoda, jest wywoływana. Jeśli nie, generowany jest jakiś wyjątek NoSuchMethodError - ale wszystko odbywa się w czasie wykonywania.
Ale w Javie, języku z typowaniem statycznym, jaki typ możemy przypisać do naszej zmiennej? Kukurydza musi dziedziczyć po Warzywach, aby wspierać wzrost, ale nie może dziedziczyć po Zwierzęciu, ponieważ nie wytwarza hałasu. Krowa musi dziedziczyć po Animal, aby wspierać makeNoise, ale nie może dziedziczyć po Vegetable, ponieważ nie powinna wprowadzać zbiorów. Wygląda na to, że potrzebujemy wielokrotnego dziedziczenia - możliwości dziedziczenia z więcej niż jednej klasy. Ale okazuje się, że jest to dość trudna cecha języka, ponieważ pojawiają się wszystkie skrajne przypadki (co się dzieje, gdy więcej niż jedna równoległa nadklasa implementuje tę samą metodę? Itd.)
Wraz z interfejsami ...
Jeśli tworzymy klasy Animal i Vegetable, z każdym wdrożeniem Growable możemy zadeklarować, że nasza Krowa jest Zwierzęciem, a nasza Kukurydza jest Warzywem. Możemy również zadeklarować, że zarówno zwierzęta, jak i warzywa są uprawne. To pozwala nam napisać to, aby wszystko rozwijać:
I pozwala nam to robić, wydawać odgłosy zwierząt:
Zaletą języka kaczego jest to, że uzyskuje się naprawdę ładny polimorfizm: wszystko, co klasa musi zrobić, aby zapewnić zachowanie, to dostarczyć metodę. Dopóki wszyscy grają dobrze i wysyłają tylko wiadomości pasujące do określonych metod, wszystko jest w porządku. Wadą jest to, że poniższy rodzaj błędu nie jest wychwytywany przed uruchomieniem:
Języki z typami statycznymi zapewniają znacznie lepsze „programowanie na podstawie kontraktu”, ponieważ w czasie kompilacji wychwytują poniższe dwa rodzaje błędów:
-
A więc ... podsumowując:
Implementacja interfejsu pozwala określić, jakie rodzaje rzeczy mogą robić obiekty (interakcja), a dziedziczenie klas pozwala określić, jak mają być wykonywane (implementacja).
Interfejsy dają nam wiele korzyści z „prawdziwego” polimorfizmu, bez poświęcania sprawdzania typu kompilatora.
źródło
Zwykle Interfejsy definiują interfejs, którego powinieneś używać (jak mówi nazwa ;-)). Próba
Teraz twoja funkcja
foo
akceptujeArrayList
s,LinkedList
s, ... nie tylko jeden typ.Najważniejsze w Javie jest to, że możesz zaimplementować wiele interfejsów, ale możesz rozszerzyć tylko JEDNĄ klasę! Próba:
jest możliwe, ale nie jest!Kod powyżej może być również:
IBox myBox = new Rectangle();
. Ważne jest teraz, że myBox zawiera TYLKO metody / pola z IBox, a nie (prawdopodobnie istniejące) inne metody zRectangle
.źródło
Myślę, że rozumiesz wszystko, co robią interfejsy, ale nie wyobrażasz sobie jeszcze sytuacji, w których interfejs jest przydatny.
Jeśli tworzysz instancję, używasz i zwalniasz obiekt w wąskim zakresie (na przykład w ramach jednego wywołania metody), interfejs tak naprawdę niczego nie dodaje. Jak zauważyłeś, konkretna klasa jest znana.
Interfejsy są przydatne, gdy obiekt musi zostać utworzony w jednym miejscu i zwrócony do obiektu wywołującego, który może nie dbać o szczegóły implementacji. Zmieńmy Twój przykład IBox na Shape. Teraz możemy mieć implementacje Shape, takie jak Rectangle, Circle, Triangle itp. Implementacje metod getArea () i getSize () będą zupełnie inne dla każdej konkretnej klasy.
Teraz możesz użyć fabryki z różnymi metodami createShape (params), które zwrócą odpowiedni Shape w zależności od przekazanych parametrów. Oczywiście fabryka będzie wiedzieć, jaki typ Shape jest tworzony, ale wywołujący nie będzie miał dbać o to, czy to okrąg, czy kwadrat, czy tak dalej.
Teraz wyobraź sobie, że masz różne operacje, które musisz wykonać na swoich kształtach. Może musisz posortować je według obszaru, ustawić je wszystkie na nowy rozmiar, a następnie wyświetlić je w interfejsie użytkownika. Wszystkie kształty są tworzone przez fabrykę, a następnie mogą być bardzo łatwo przekazywane do klas Sorter, Sizer i Display. Jeśli chcesz kiedyś dodać klasę sześciokąta, nie musisz zmieniać niczego poza fabryką. Bez interfejsu dodawanie kolejnego kształtu staje się bardzo skomplikowanym procesem.
źródło
mógłbyś
w ten sposób używasz tego obiektu jako Iboxa i nie obchodzi cię, że jest naprawdę
Rectangle
.źródło
Square
będziesz miał problem ... jeśli spróbujesz to zrobić bez interfejsów, nie możesz tego zagwarantowaćSquare
iRectangle
masz te same metody ... może to spowodować koszmar, gdy masz większa baza kodu ... Pamiętaj, interfejsy definiują szablon.DLACZEGO INTERFEJS ??????
Zaczyna się od psa. W szczególności mops .
Mops ma różne zachowania:
I masz labradora, który również ma zestaw zachowań.
Możemy zrobić kilka mopsów i laboratoriów:
I możemy przywołać ich zachowania:
Załóżmy, że prowadzę hodowlę psów i muszę śledzić wszystkie psy, które trzymam. Muszę przechowywać moje mopsy i labradory w oddzielnych tablicach :
Ale to oczywiście nie jest optymalne. Jeśli chcę też pomieścić kilka pudli , muszę zmienić definicję hodowli, aby dodać tablicę pudli . Właściwie potrzebuję osobnej tablicy dla każdego rodzaju psa.
Wgląd: zarówno mopsy, jak i labradory (i pudle) są typami psów i mają ten sam zestaw zachowań. Oznacza to, że możemy powiedzieć (na potrzeby tego przykładu), że wszystkie psy mogą szczekać, mieć imię i mogą mieć lub nie mieć kręconego ogona. Możemy użyć interfejsu, aby zdefiniować, co mogą robić wszystkie psy, ale pozostawić to konkretnym typom psów, aby wdrożyły te konkretne zachowania. Interfejs mówi „oto rzeczy, które mogą robić wszystkie psy”, ale nie mówi, jak wykonywane jest każde zachowanie.
Następnie nieznacznie zmieniam klasy Pug i Lab, aby zaimplementować zachowania Dog. Można powiedzieć, że mops to pies, a laboratorium to pies.
Nadal mogę utworzyć instancję Pugs and Labs, tak jak poprzednio, ale teraz mam również nowy sposób, aby to zrobić:
To mówi, że d1 to nie tylko pies, to konkretnie mops. A d2 to także pies, a konkretnie laboratorium. Możemy wywołać zachowania i działają one jak poprzednio:
Tutaj opłaca się cała dodatkowa praca. Klasa Kennel stała się znacznie prostsza. Potrzebuję tylko jednej tablicy i jednej metody addDog. Oba będą działać z każdym przedmiotem, jakim jest pies; to znaczy obiekty, które implementują interfejs Dog.
Oto jak go używać:
Ostatnia instrukcja wyświetli: Spot Fido
Interfejs daje możliwość określenia zestawu zachowań, które będą wspólne dla wszystkich klas implementujących interfejs. W związku z tym możemy zdefiniować zmienne i kolekcje (takie jak tablice), które nie muszą z góry wiedzieć, jaki rodzaj konkretnego obiektu będą przechowywać, tylko że będą przechowywać obiekty implementujące interfejs.
źródło
Doskonałym przykładem użycia interfejsów jest struktura kolekcji. Jeśli napiszesz funkcję, która przyjmuje a
List
, nie ma znaczenia, czy użytkownik przekaże a,Vector
an,ArrayList
aHashList
lub cokolwiek. Możesz to również przekazaćList
do dowolnej funkcji wymagającej interfejsuCollection
lubIterable
.To sprawia, że takie funkcje są
Collections.sort(List list)
możliwe, niezależnie od tego, w jaki sposóbList
jest zaimplementowany.źródło
To jest powód, dla którego wzorce fabryczne i inne wzorce kreacji są tak popularne w Javie. Masz rację, że bez nich Java nie zapewnia prostego mechanizmu do łatwej abstrakcji instancji. Mimo to uzyskujesz abstrakcję wszędzie tam, gdzie nie tworzysz obiektu w swojej metodzie, co powinno stanowić większość twojego kodu.
Na marginesie, generalnie zachęcam ludzi, aby nie stosowali mechanizmu „IRealname” do nazywania interfejsów. To kwestia Windows / COM, która stawia jedną stopę w grobie węgierskiej notacji i naprawdę nie jest konieczna (Java jest już silnie wpisana na maszynie, a celem posiadania interfejsów jest to, aby były jak najbardziej nie do odróżnienia od typów klas).
źródło
Nie zapominaj, że w późniejszym czasie możesz wziąć istniejącą klasę i wprowadzić ją w życie
IBox
, a wtedy stanie się ona dostępna dla całego kodu uwzględniającego skrzynkę.Staje się to nieco jaśniejsze, jeśli interfejsy są nazwane -able . na przykład
itd. (Schematy nazewnictwa nie zawsze działają, np. nie jestem pewien, czy
Boxable
jest to odpowiednie)źródło
Aktualizuję odpowiedź o nowe funkcje interfejsu, które wprowadziłem w java 8 wersji .
Ze strony dokumentacji oracle w podsumowaniu interfejsu :
Deklaracja interfejsu może zawierać
Jedyne metody, które mają implementacje, to metody domyślne i statyczne.
Zastosowania interfejsu :
Serializable
interfejs mogą, ale nie muszą mieć między sobą żadnego związku z wyjątkiem implementacji tego interfejsuNiektóre powiązane pytania SE dotyczące różnicy między klasą abstrakcyjną a interfejsem oraz przypadków użycia z przykładami roboczymi:
Jaka jest różnica między interfejsem a klasą abstrakcyjną?
Jak powinienem był wyjaśnić różnicę między interfejsem a klasą abstrakcyjną?
Zajrzyj na stronę dokumentacji, aby zrozumieć nowe funkcje dodane w java 8: metody domyślne i metody statyczne .
źródło
Celem interfejsów jest abstrakcja lub oddzielenie od implementacji.
Jeśli wprowadzisz abstrakcję do swojego programu, nie przejmujesz się możliwymi implementacjami. Jesteś zainteresowany tym , co może zrobić, a nie jak , i używasz znaku,
interface
aby wyrazić to w Javie.źródło
Jeśli masz CardboardBox i HtmlBox (oba implementują IBox), możesz przekazać oba z nich do dowolnej metody, która akceptuje IBox. Mimo że obie są bardzo różne i nie są całkowicie wymienne, metody, które nie przejmują się „otwieraniem” lub „zmianą rozmiaru”, mogą nadal używać twoich klas (być może dlatego, że dbają o to, ile pikseli jest potrzebnych do wyświetlenia czegoś na ekranie).
źródło
Interfejsy, w których do języka Java dodano fetature, aby umożliwić wielokrotne dziedziczenie. Twórcy Java jednak / zdali sobie sprawę, że posiadanie wielokrotnego dziedziczenia jest "niebezpieczną" funkcją, dlatego wpadli na pomysł interfejsu.
dziedziczenie wielokrotne jest niebezpieczne, ponieważ możesz mieć klasę podobną do poniższej:
Jaka byłaby metoda, którą należy wywołać, gdy używamy
Wszystkie problemy są rozwiązywane za pomocą interfejsów, ponieważ wiesz, że możesz rozszerzyć interfejsy i że nie będą one miały metod klasowania ... oczywiście kompilator jest fajny i mówi ci, czy nie zaimplementowałeś metod, ale lubię myśleć, że tak jest efekt uboczny ciekawszego pomysłu.
źródło
Oto moje rozumienie przewagi interfejsu. Popraw mnie, jeśli się mylę. Wyobraź sobie, że tworzymy system operacyjny, a inny zespół opracowuje sterowniki dla niektórych urządzeń. Dlatego opracowaliśmy interfejs StorageDevice. Mamy dwie implementacje tego (FDD i HDD) dostarczone przez inny zespół programistów.
Następnie mamy klasę OperatingSystem, która może wywoływać metody interfejsu, takie jak saveData, po prostu przekazując wystąpienie klasy zaimplementowanej w interfejsie StorageDevice.
Zaletą jest to, że nie dbamy o implementację interfejsu. Drugi zespół wykona zadanie, implementując interfejs StorageDevice.
źródło