Czy interfejs to coś więcej niż tylko właściwe metody


159

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?


2
Pamiętaj, że interfejsy nie są specyficzne dla języka Java. Wszystkie języki OOP mają je w takiej czy innej formie, chociaż nie zawsze są one tak jawnie zdefiniowane jak Java.
Herms

2
Technicznie rzecz biorąc, wszystkie języki OOP z silną typizacją mają je w takiej czy innej formie. Języki bez typu lub pisane kaczką nie mają podobnej koncepcji.
Jared

1
@Jared Czy nie mylisz silnego pisania z wpisywaniem statycznym i "bez typu" z typowanym dynamicznie?
eljenso

Polimorfizm można również osiągnąć za pomocą interfejsów. Sprawdź ostatnią sekcję tej strony codenuggets.com/2014/06/20/java-interface
Jeff

Odpowiedzi:


143

Interfejsy to sposób na uelastycznienie kodu. To co robisz to:

Ibox myBox=new Rectangle();

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:

Ibox myBox=new OtherKindOfBox();

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:

myBox.close()

(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.


54
W tej odpowiedzi nie ma nic, co dotyczyłoby wyłącznie interfejsów Java . To samo odnosi się równie dobrze do klas abstrakcyjnych, a nawet konkretnych. Spodziewałbym się dobrej odpowiedzi, aby wspomnieć o możliwości implementacji wielu interfejsów i kiedy / dlaczego byłoby to przydatne.
Rogério

16
W jaki sposób została wybrana jako odpowiedź? Jest to krótki opis, dlaczego polimorfizm jest przydatny, ale jak powiedział powyższy plakat, spodziewałbym się lepszego wyjaśnienia wielu interfejsów, a co ważniejsze, kiedy właściwe jest użycie interfejsu, a nie klasy abstrakcyjnej.
trevorkavanaugh

2
Ma to bardzo niewiele wspólnego z wyjaśnianiem interfejsów, a wszystko, co ma związek z podstawami polimorfizmu
A-Developer-Has-No-Name

123

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.Collectionsktóry dostarcza wszelkiego rodzaju użytecznych metod, które działają wyłącznie na interfejsach, takich jak sort()lub reverse()dla List. Chodzi o to, że ten kod może być teraz używany do sortowania lub odwracania dowolnej klasy, która implementuje Listinterfejsy - nie tylko ArrayListi LinkedList, ale także klas, które sam piszesz, co może być zaimplementowane w sposób, którego ludzie, którzy pisali, java.util.Collectionsnigdy 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.


9
To całkiem dobra odpowiedź, podoba mi się, gdy podałeś przykłady z klas pakietów java ...
Owais Qureshi

1
Podobało mi sięyou can write code that operates on well-known interfaces, or interfaces you define
Manish Kumar,

Chwileczkę ... To, co sprawia, że ​​interfejsy są przydatne, to nie [możliwość użycia dowolnej implementacji, którą lubisz], ale raczej [możliwość użycia dowolnej implementacji, którą lubisz]? Wspomnienie o odwrotnym przypadku jest jednak dobrym punktem.
Powerslave

4
@Powerslave: parafrazuj to bardziej jak To, co sprawia, że ​​interfejsy są użyteczne, to nie [możliwość pisania kodu, w którym musisz zmienić tylko jedną linię podczas zmiany impementacji], ale raczej [możliwość pisania kodu, w którym nawet nie określasz implementacji w wszystko].
Michael Borgwardt

@MichaelBorgwardt To brzmi o wiele lepiej. :) Dzięki za wytłumaczenie!
Powerslave

119

Jednym z wielu zastosowań, które przeczytałem, jest sytuacja, w której jest to trudne bez interfejsów wielokrotnego dziedziczenia w Javie:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

Teraz wyobraź sobie przypadek, w którym:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

ale,

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

Lepszy projekt byłby:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Zwierzę nie ma metody chew () i zamiast tego jest umieszczane w interfejsie jako:

interface Chewable {
void chew();
}

i klasa Reptile zaimplementowała to, a nie Birds (ponieważ ptaki nie mogą żuć):

class Reptile extends Animal implements Chewable { } 

aw przypadku ptaków po prostu:

class Bird extends Animal { }

6
@CHEBURASHKA I złe nazewnictwo. Jeśli 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 interfejsu Predatorbyłoby tutaj bardziej odpowiednie.
Powerslave

6
@Powerslave To prawda imho, gad jest „zdolny do żucia” / „do żucia”. Jastrząb jest drapieżnikiem, ale nadal nie jest w stanie przeżuć ... po prostu czepianie się, ale „żucie” można lepiej zdefiniować w dokumentacji interfejsu.
Madmenyo,

Znakomity .. Bardzo dobrze wyjaśnione. Dziękuję Ci..!
Gurusinghe

1
Nie rozumiem. Wygląda na to, że interfejsy są dobrym sposobem na upewnienie się, że zaimplementujesz te same metody w każdej klasie, która je implementuje (np. Uniemożliwia mi to dokonanie głupiego wyboru klasy Bird z run () i klasą Dog z runn () - wszystkie są takie same). Ale czy zwracając uwagę i ustawiając moje klasy w tych samych formatach / strukturach metod, nie mogłem osiągnąć tego samego? Naprawdę wygląda na to, że interfejsy po prostu upewnij się, że programista nie zapomina. Wydaje się, że interfejsy nie oszczędzają mi czasu; Nadal muszę zdefiniować metodę w każdej klasie, która ją implementuje.
Alex G

@AlexG - powiedziałem jednemu z wielu zastosowań, stary :) Jest ich więcej, ledwo zarysowaliśmy powierzchnię, aby odpowiedzieć na pytanie w prosty sposób!
zirytowany,

47

Celem interfejsów jest polimorfizm , czyli podstawianie typu . Na przykład, biorąc pod uwagę następującą metodę:

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

Podczas wywoływania scalemetody można podać dowolną wartość typu implementującego IBoxinterfejs. Innymi słowy, jeśli Rectanglei Squareoba implementują IBox, możesz podać albo a, Rectanglealbo Squaregdziekolwiek IBoxoczekiwano.


8
Dlaczego celem jest polimorfizm interfejsów, skoro już mogę to osiągnąć w Javie z podklasami i nadpisywaniem metod?
eljenso

1
To to samo, z wyjątkiem tego, że interfejsy muszą pomijać każdą implementację. Dlatego klasy mogą implementować więcej niż jeden interfejs.
Apocalisp

4
Hej, nigdy nie powiedziałem, że Java ma jakąkolwiek integralność koncepcyjną. Podstawianie typów jest celem wszystkich podtypów. Tak się składa, że ​​Java ma więcej niż jeden mechanizm podtypów, z których żaden nie jest szczególnie dobry.
Apocalisp

1
Nigdy też nie powiedziałem nic o integralności konceptualnej. Ale przejdźmy dalej. Jeśli możesz skalować każdy IBox swoją metodą, czy nie powinna to być operacja zadeklarowana w IBox: IBox.scale (int)?
eljenso

1
Nie chcielibyśmy łączyć Integer z IBox, dlatego nie robimy tego metodą na Integer. O liczbie metod w interfejsie decyduje konsekwencja i spójność wyrażanej przez niego abstrakcji, a nie to, jak uciążliwe byłoby jej wdrożenie. W każdym razie dziękuję za twoje odpowiedzi Apo.
eljenso

33

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:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

Ten kod:

  1. Deklaruje zmienną lokalną o nazwie anAnimal (zwróć uwagę, że NIE PODAJEMY TYPU zmiennej - wszystkie zmienne są odniesieniami do obiektu, ni mniej, ni więcej).
  2. Tworzy nową instancję klasy o nazwie „Pig”
  3. Przypisuje to nowe wystąpienie Pig do zmiennej anAnimal.
  4. Wysyła wiadomość makeNoise do świni.
  5. Powtarza całość używając krowy, ale przypisując ją do tej samej zmiennej co Świnia.

Ten sam kod Java wyglądałby mniej więcej tak (przy założeniu, że Duck i Cow są podklasami klasy Animal:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

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:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

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ć:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

I pozwala nam to robić, wydawać odgłosy zwierząt:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

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:

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

Języki z typami statycznymi zapewniają znacznie lepsze „programowanie na podstawie kontraktu”, ponieważ w czasie kompilacji wychwytują poniższe dwa rodzaje błędów:

// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();  
farmObject makeNoise();

-

// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest(); 

A więc ... podsumowując:

  1. Implementacja interfejsu pozwala określić, jakie rodzaje rzeczy mogą robić obiekty (interakcja), a dziedziczenie klas pozwala określić, jak mają być wykonywane (implementacja).

  2. Interfejsy dają nam wiele korzyści z „prawdziwego” polimorfizmu, bez poświęcania sprawdzania typu kompilatora.


2
Oto treść mojej odpowiedzi na inne pytanie: stackoverflow.com/questions/379282/… . Ale są to powiązane odpowiedzi.
Jared

2
Mogę więc zapytać, w jaki sposób język pisany na maszynie typu kaczka rozróżnia między Animal.water () (co, pruderyjny rolnik zwykł mawiać, że trzeba przeciekać) i Plant.water (), którego używa do podlewania roślin. Niejednoznaczność jest wrogiem. IMO dopuszcza jakąkolwiek szczegółowość niezbędną do przezwyciężenia niejasności.
Bill K

1
Tak… dwuznaczność to nazwa gry z językami wpisywanymi w kaczki. Podczas profesjonalnej pracy w języku typu kaczego często zdarza się, że elementy członkowskie (metody i zmienne) mają nazwy o długości od 50 do 100 znaków.
Jared

1
Inną wielką wadą języków typu kaczego jest niemożność wykonania refaktoryzacji programowej opartej na analizie statycznej - spróbuj poprosić obraz Smalltalka o listę wszystkich wywołań Twojej metody printString ... otrzymasz listę wszystkich wywołań wszystkich metod printString. ...
Jared

... ponieważ wywołującego Automobile # printString nie można programowo odróżnić od obiektu wywołującego NearEarthOrbit # printString.
Jared

9

Zwykle Interfejsy definiują interfejs, którego powinieneś używać (jak mówi nazwa ;-)). Próba


public void foo(List l) {
   ... do something
}

Teraz twoja funkcja fooakceptuje ArrayLists, LinkedLists, ... 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:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
jest możliwe, ale

class Test extends Foo, Bar, Buz {
...
}
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 z Rectangle.


1
Czy „Lista” ma być elementem interfejsu?
Kliknij „Za głosowaniem”

1
Lista to interfejs w bibliotece kolekcji Java.
rmeador

Lista to interfejs w standardowej bibliotece Java ( java.sun.com/javase/6/docs/api/java/util/List.html ). Używa go tylko do zilustrowania swojego punktu widzenia.
Michael Myers

6

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.


6

mógłbyś

Ibox myBox = new Rectangle();

w ten sposób używasz tego obiektu jako Iboxa i nie obchodzi cię, że jest naprawdę Rectangle.


To znaczy, że moglibyśmy tak pisać ?! > Rectangle inst = new Rectangle ();
Dr Jacky

@ Mr.Hyde Jeśli chcesz później dodać, Squarebędziesz miał problem ... jeśli spróbujesz to zrobić bez interfejsów, nie możesz tego zagwarantować Squarei Rectanglemasz te same metody ... może to spowodować koszmar, gdy masz większa baza kodu ... Pamiętaj, interfejsy definiują szablon.
Kanion Kolob

6

DLACZEGO INTERFEJS ??????

Zaczyna się od psa. W szczególności mops .

Mops ma różne zachowania:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

I masz labradora, który również ma zestaw zachowań.

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

Możemy zrobić kilka mopsów i laboratoriów:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

I możemy przywołać ich zachowania:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

Załóżmy, że prowadzę hodowlę psów i muszę śledzić wszystkie psy, które trzymam. Muszę przechowywać moje mopsy i labradory w oddzielnych tablicach :

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

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.

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

Następnie nieznacznie zmieniam klasy Pug i Lab, aby zaimplementować zachowania Dog. Można powiedzieć, że mops to pies, a laboratorium to pies.

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

Nadal mogę utworzyć instancję Pugs and Labs, tak jak poprzednio, ale teraz mam również nowy sposób, aby to zrobić:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

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:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

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.

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

Oto jak go używać:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

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.


@niranjan kurambhatti Mogę rozszerzyć wszystkie klasy dla psa, ale nadal dlaczego interfejs?
Jeeva

3

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, Vectoran, ArrayLista HashListlub cokolwiek. Możesz to również przekazać Listdo dowolnej funkcji wymagającej interfejsu Collectionlub Iterable.

To sprawia, że ​​takie funkcje są Collections.sort(List list)możliwe, niezależnie od tego, w jaki sposób Listjest zaimplementowany.


3

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).


1
Mylisz silne pisanie z pisaniem statycznym.
eljenso

3

Nie zapominaj, że w późniejszym czasie możesz wziąć istniejącą klasę i wprowadzić ją w życieIBox , 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

public interface Saveable {
....

public interface Printable {
....

itd. (Schematy nazewnictwa nie zawsze działają, np. nie jestem pewien, czy Boxablejest to odpowiednie)


3

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?

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ć

  1. podpisy metod
  2. metody domyślne
  3. metody statyczne
  4. stałe definicje.

Jedyne metody, które mają implementacje, to metody domyślne i statyczne.

Zastosowania interfejsu :

  1. Aby zdefiniować umowę
  2. Łączenie niepowiązanych klas z ma możliwości (np. Klasy implementujące Serializableinterfejs mogą, ale nie muszą mieć między sobą żadnego związku z wyjątkiem implementacji tego interfejsu
  3. Zapewnienie wymiennej implementacji, np. Wzorzec strategii
  4. Metody domyślne umożliwiają dodawanie nowych funkcji do interfejsów bibliotek i zapewniają zgodność binarną z kodem napisanym dla starszych wersji tych interfejsów
  5. Organizuj metody pomocnicze w swoich bibliotekach za pomocą metod statycznych (możesz zachować metody statyczne specyficzne dla interfejsu w tym samym interfejsie, a nie w oddzielnej klasie)

Niektó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 .


Usunąłem znacznik java-8, ponieważ pytanie nie dotyczyło java-8 (a tak naprawdę zostało zadane na długo przed java-8). Tagi służą do pytań, a nie do odpowiedzi.
Tagir Valeev

2

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, interfaceaby wyrazić to w Javie.


Celem wszelkiego programowania strukturalnego jest abstrakcja. Dlaczego miałbyś powiedzieć, że celem interfejsów jest abstrakcja, skoro mogę osiągnąć dokładnie to samo, używając typów ogólnych i kompozycji klas?
Apocalisp

1
Jeśli całe programowanie strukturalne jest abstrakcją (twoje twierdzenie), to interfejsy są abstrakcjami w tej abstrakcji.
eljenso

1

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).


1

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:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

Jaka byłaby metoda, którą należy wywołać, gdy używamy


   FunckyFigure.GetArea(); 

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.


Możesz chcieć odróżnić dziedziczenie wielu implementacji od dziedziczenia wielu interfejsów w swojej odpowiedzi, w przeciwnym razie stanie się to mylące.
eljenso

0

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.

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}

to samo można zrobić z abstrakcyjną klasą StorageDevice oraz klasami FDD i HDD rozszerzającymi klasę StorageDevice. ale jeśli użyjemy klasy abstrakcyjnej, nie możemy skorzystać z wielokrotnego dziedziczenia.
Vladimir Georgiev
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.