Klasa abstrakcyjna a interfejs w Javie

87

Zadano mi pytanie, chciałem tutaj sprawdzić swoją odpowiedź.

P: W którym scenariuszu lepiej jest rozszerzyć klasę abstrakcyjną niż implementować interfejs (y)?

O: Jeśli używamy wzorca projektowego metody szablonowej.

Mam rację ?

Przepraszam, że nie mogłem jasno sformułować pytania.
Znam podstawową różnicę między klasą abstrakcyjną a interfejsem.

1) używaj klasy abstrakcyjnej, gdy wymaganie jest takie, że musimy zaimplementować tę samą funkcjonalność w każdej podklasie dla określonej operacji (zaimplementuj metodę) i inną funkcjonalność dla niektórych innych operacji (tylko sygnatury metod)

2) użyj interfejsu, jeśli chcesz złożyć podpis, aby był taki sam (i inna implementacja), abyś mógł zachować zgodność z implementacją interfejsu

3) możemy rozszerzyć maksymalnie jedną klasę abstrakcyjną, ale możemy zaimplementować więcej niż jeden interfejs

Powtarzając pytanie: czy są jakieś inne scenariusze poza wymienionymi powyżej, w których konkretnie wymagamy użycia klasy abstrakcyjnej (widać, czy wzorzec projektowania metody szablonowej jest koncepcyjnie oparty tylko na tym)?

Interfejs a klasa abstrakcyjna

Wybór między tymi dwoma naprawdę zależy od tego, co chcesz zrobić, ale na szczęście dla nas Erich Gamma może nam trochę pomóc.

Jak zawsze istnieje kompromis, interfejs daje swobodę w odniesieniu do klasy bazowej, klasa abstrakcyjna daje swobodę dodawania nowych metod później . - Erich Gamma

Nie możesz przejść i zmienić interfejsu bez konieczności zmiany wielu innych rzeczy w kodzie, więc jedynym sposobem na uniknięcie tego byłoby utworzenie zupełnie nowego interfejsu, co nie zawsze może być dobrą rzeczą.

Abstract classespowinien być używany przede wszystkim dla obiektów, które są blisko spokrewnione. Interfaceslepiej zapewniają wspólną funkcjonalność dla niepowiązanych klas.

Uczeń
źródło
możliwy duplikat klasy Interface vs Abstract (ogólne OO)
bezmax
To nie jest duplikat. OP chce wiedzieć, kiedy rozszerzyć klasę abstrakcyjną, zamiast implementować interfejs. Nie chce wiedzieć, kiedy napisać abstrakcyjną klasę lub interfejs. Jego abstrakcyjna klasa i interfejs są już napisane. Hd chce wiedzieć, czy rozszerzyć, czy wdrożyć.
Shiplu Mokaddim
1
@ shiplu.mokadd.im To jest różnica bez różnicy. Nie możesz użyć klasy abstrakcyjnej bez jej rozszerzenia. Twoje czepianie się tutaj wydaje się całkowicie bezcelowe.
Markiz Lorne

Odpowiedzi:

86

Kiedy używać interfejsów

Interfejs pozwala komuś zacząć od zera, aby zaimplementować Twój interfejs lub zaimplementować go w innym kodzie, którego pierwotne lub podstawowe przeznaczenie różniło się od interfejsu. Dla nich twój interfejs jest tylko przypadkowy, coś, co musi dodać do ich kodu, aby móc używać twojego pakietu. Wadą jest to, że każda metoda w interfejsie musi być publiczna. Możesz nie chcieć ujawniać wszystkiego.

Kiedy używać klas abstrakcyjnych

Natomiast klasa abstrakcyjna zapewnia większą strukturę. Zwykle definiuje pewne domyślne implementacje i dostarcza narzędzi przydatnych do pełnej implementacji. Haczyk polega na tym, że wykorzystujący go kod musi używać Twojej klasy jako podstawy. Może to być bardzo niewygodne, jeśli inni programiści, którzy chcą używać twojego pakietu, już samodzielnie opracowali własną hierarchię klas. W Javie klasa może dziedziczyć tylko z jednej klasy bazowej.

Kiedy używać obu

Możesz zaoferować to, co najlepsze z obu światów, interfejs i klasę abstrakcyjną. Implementatorzy mogą zignorować twoją klasę abstrakcyjną, jeśli zechcą. Jedyną wadą jest wywoływanie metod przez ich nazwy interfejsów, jest nieco wolniejsze niż wywoływanie ich przez ich abstrakcyjną nazwę klasy.

DivineDesert
źródło
Myślę, że OP chce wiedzieć, kiedy rozszerzyć klasę abstrakcyjną, zamiast implementować interfejs
Shiplu Mokaddim
@ shiplu.mokadd.im W rzeczywistości PO zadał bardzo konkretne pytanie, na które odpowiedź brzmi „tak” lub „nie”.
Markiz Lorne
4
Masz rację. Ale w SO odpowiadamy tak / nie z odpowiednim wyjaśnieniem.
Shiplu Mokaddim,
1
@ shiplu.mokadd.im Nie rozumiem, jak to daje ci licencję na błędne sformułowanie jego pytania.
Markiz Lorne
Tylko na podstawie tego jednego stwierdzenia If we are using template method design patternnie możemy powiedziećYES lubNO
DivineDesert
31

powtarzając pytanie: istnieje inny scenariusz poza wymienionymi powyżej, w którym konkretnie wymagamy użycia klasy abstrakcyjnej (widać, że wzorzec projektowania metody szablonowej jest koncepcyjnie oparty tylko na tym)

Tak, jeśli używasz JAXB. Nie lubi interfejsów. Należy użyć klas abstrakcyjnych lub obejść to ograniczenie za pomocą typów ogólnych.

Z osobistego posta na blogu :

Berło:

  1. Klasa może implementować wiele interfejsów
  2. Interfejs nie może w ogóle dostarczyć żadnego kodu
  3. Interfejs może definiować tylko publiczne statyczne stałe końcowe
  4. Interfejs nie może definiować zmiennych instancji
  5. Dodanie nowej metody ma wpływ na implementację klas (utrzymanie projektu)
  6. JAXB nie radzi sobie z interfejsami
  7. Interfejs nie może rozszerzać ani implementować klasy abstrakcyjnej
  8. Wszystkie metody interfejsu są publiczne

Ogólnie rzecz biorąc, interfejsy powinny służyć do definiowania kontraktów (co ma zostać osiągnięte, a nie jak to osiągnąć).

Klasa abstrakcyjna:

  1. Klasa może rozszerzać co najwyżej jedną klasę abstrakcyjną
  2. Klasa abstrakcyjna może zawierać kod
  3. Klasa abstrakcyjna może definiować zarówno stałe statyczne, jak i stałe instancji (wersja ostateczna)
  4. Klasa abstrakcyjna może definiować zmienne instancji
  5. Modyfikacja istniejącego kodu klasy abstrakcyjnej ma wpływ na rozszerzenie klas (utrzymanie implementacji)
  6. Dodanie nowej metody do klasy abstrakcyjnej nie ma wpływu na rozszerzanie klas
  7. Klasa abstrakcyjna może implementować interfejs
  8. Klasy abstrakcyjne mogą implementować metody prywatne i chronione

Do (częściowej) implementacji należy używać klas abstrakcyjnych. Mogą być sposobem na ograniczenie sposobu implementacji kontraktów API.

Jérôme Verstrynge
źródło
3
W Javie 8 dla interfejsu nr 8 możesz też mieć defaulti staticmetody.
Nowicjusz,
9

Jest tu wiele świetnych odpowiedzi, ale często uważam, że używanie OBU interfejsów i klas abstrakcyjnych jest najlepszą drogą. Rozważmy ten wymyślony przykład:

Jesteś programistą w banku inwestycyjnym i musisz zbudować system, który będzie składał zamówienia na rynek. Twój interfejs zawiera najbardziej ogólne pojęcie o tym, co robi system transakcyjny ,

1) Trading system places orders
2) Trading system receives acknowledgements

i można je przechwycić w interfejsie, ITradeSystem

public interface ITradeSystem{

     public void placeOrder(IOrder order);
     public void ackOrder(IOrder order);

}

Teraz inżynierowie pracujący w dziale sprzedaży i na innych liniach biznesowych mogą zacząć łączyć się z systemem, aby dodać funkcjonalność składania zamówień do swoich istniejących aplikacji. I jeszcze nawet nie zacząłeś budować! To jest siła interfejsów.

Więc idź dalej i zbuduj system dla inwestorów giełdowych ; słyszeli, że Twój system ma funkcję wyszukiwania tanich akcji i bardzo chętnie ją wypróbowują! Ujmujesz to zachowanie za pomocą metody zwanej findGoodDeals(), ale zdajesz sobie sprawę, że jest wiele niechlujnych rzeczy związanych z łączeniem się z rynkami. Na przykład musisz otworzyć SocketChannel,

public class StockTradeSystem implements ITradeSystem{    

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

Konkretne implementacje będą miały wiele takich niechlujnych metod connectToMarket(), ale na tym findGoodDeals()wszystkim zależy traderom.

Teraz w grę wchodzą klasy abstrakcyjne. Twój szef poinformuje Cię, że traderzy walutowi również chcą korzystać z Twojego systemu. Patrząc na rynki walutowe, widać, że kanalizacja jest prawie identyczna jak na giełdach. W rzeczywistości connectToMarket()może być ponownie użyty dosłownie do łączenia się z rynkami walutowymi. Jednak findGoodDeals()na arenie walutowej jest to zupełnie inna koncepcja. Zanim więc przekażesz kod źródłowy czarodziejowi zajmującemu się handlem walutami po drugiej stronie oceanu, najpierw zmienisz kod na abstractklasę, pozostawiając findGoodDeals()nieskomplikowaną

public abstract class ABCTradeSystem implements ITradeSystem{    

    public abstract void findGoodDeals();

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

Twój system handlu akcjami wdraża się findGoodDeals()tak, jak już zdefiniowałeś,

public class StockTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

ale teraz dzieciak, który specjalizuje się w FX, może zbudować swój system, po prostu dostarczając implementację findGoodDeals()dla walut; nie musi ponownie implementować połączeń gniazd ani nawet metod interfejsu!

public class CurrencyTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       ccys = <Genius stuff to find undervalued currencies>
       System.out.println("The best FX spot rates are: " + ccys);
    }

Programowanie w interfejsie jest potężne, ale podobne aplikacje często ponownie implementują metody w prawie identyczny sposób. Użycie klasy abstrakcyjnej pozwala uniknąć powtórzeń, przy jednoczesnym zachowaniu mocy interfejsu.

Uwaga: można się zastanawiać, dlaczego findGreatDeals()nie jest częścią interfejsu. Pamiętaj, że interfejs definiuje najbardziej ogólne elementy systemu handlowego. Inny inżynier może opracować ZUPEŁNIE RÓŻNY system transakcyjny, w którym nie zależy mu na znalezieniu dobrych transakcji. Interfejs gwarantuje, że dział sprzedaży może również połączyć się ze swoim systemem, dlatego lepiej nie mieszać interfejsu z koncepcjami aplikacji, takimi jak „świetne okazje”.

Adam Hughes
źródło
6

Których należy używać, klas abstrakcyjnych lub interfejsów?

Rozważ użycie klas abstrakcyjnych, jeśli którekolwiek z poniższych instrukcji mają zastosowanie do Twojego przypadku użycia:

Chcesz udostępnić kod kilku blisko spokrewnionym klasom.

Oczekujesz, że klasy, które rozszerzają twoją klasę abstrakcyjną, mają wiele typowych metod lub pól albo wymagają modyfikatorów dostępu innych niż public (takich jak protected i private).

Chcesz zadeklarować pola niestatyczne lub niekońcowe. Umożliwia to definiowanie metod, które mogą uzyskiwać dostęp i modyfikować stan obiektu, do którego należą.

Rozważ użycie interfejsów, jeśli którekolwiek z poniższych stwierdzeń ma zastosowanie do twojego przypadku użycia:

Oczekujesz, że niepowiązane klasy zaimplementują Twój interfejs. Na przykład interfejsy Comparable i Cloneable są implementowane przez wiele niepowiązanych klas.

Chcesz określić zachowanie określonego typu danych, ale nie musisz przejmować się tym, kto zaimplementuje jego zachowanie.

Chcesz skorzystać z wielokrotnego dziedziczenia typu.

http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html

Santh
źródło
4

W ciągu ostatnich trzech lat wiele się zmieniło dzięki dodaniu nowych możliwości do interfejsu z wydaniem Java 8.

Ze strony dokumentacji Oracle w interfejsie:

Interfejs to typ referencyjny, podobny do klasy, który może zawierać tylko stałe, sygnatury metod, metody domyślne, metody statyczne i typy zagnieżdżone. Treści metod istnieją tylko dla metod domyślnych i metod statycznych.

Jak zacytowałeś w swoim pytaniu, klasa abstrakcyjna najlepiej pasuje do wzorca metody szablonowej, w której musisz utworzyć szkielet. Nie można tutaj użyć interfejsu.

Jeszcze jedna kwestia, aby preferować klasę abstrakcyjną od interfejsu:

Nie masz implementacji w klasie bazowej i tylko podklasy muszą zdefiniować własną implementację. Potrzebujesz klasy abstrakcyjnej zamiast interfejsu, ponieważ chcesz dzielić stan z podklasami.

Klasa abstrakcyjna ustanawia relację „jest” między powiązanymi klasami, a interfejs zapewnia „ma” możliwość między klasami niepowiązanymi .


Odnośnie drugiej części pytania, która jest ważna dla większości języków programowania w tym języku Java przed java-8 wydaniu

Jak zawsze istnieje kompromis, interfejs zapewnia swobodę w odniesieniu do klasy bazowej, a klasa abstrakcyjna daje swobodę późniejszego dodawania nowych metod. - Erich Gamma

Nie możesz przejść i zmienić interfejsu bez konieczności zmiany wielu innych rzeczy w kodzie

Jeśli wolisz, aby klasa abstrakcyjna łączyła się wcześniej z powyższymi dwoma względami, musisz teraz przemyśleć to ponownie, ponieważ metody domyślne dodały potężne możliwości do interfejsów.

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.

Aby wybrać jeden z nich między interfejsem a klasą abstrakcyjną, na stronie dokumentacji Oracle zacytuj:

Klasy abstrakcyjne są podobne do interfejsów. Nie można ich utworzyć i mogą zawierać mieszankę metod zadeklarowanych z implementacją lub bez. Jednak w przypadku klas abstrakcyjnych można zadeklarować pola, które nie są statyczne i ostateczne, oraz zdefiniować metody publiczne, chronione i prywatne.

W przypadku interfejsów wszystkie pola są automatycznie publiczne, statyczne i końcowe, a wszystkie deklarowane lub definiowane metody (jako metody domyślne) są publiczne. Ponadto można rozszerzyć tylko jedną klasę, niezależnie od tego, czy jest ona abstrakcyjna, czy też nie, podczas gdy można zaimplementować dowolną liczbę interfejsów.

Aby uzyskać więcej informacji, zapoznaj się z tymi powiązanymi pytaniami:

Interfejs a klasa abstrakcyjna (ogólne OO)

Jak powinienem był wyjaśnić różnicę między klasą Interface a abstrakcyjną?

Podsumowując: równowaga przechyla się teraz bardziej w kierunku interfejsów .

Czy są jakieś inne scenariusze, poza wymienionymi powyżej, w których konkretnie wymagamy użycia klasy abstrakcyjnej (można zobaczyć, czy wzorzec projektowania metody szablonowej jest koncepcyjnie oparty tylko na tym)?

Niektóre wzorce projektowe używają klas abstrakcyjnych (przez interfejsy) oprócz wzorca metody Template.

Wzorce kreacyjne:

Abstract_factory_pattern

Wzory strukturalne:

Decorator_pattern

Wzorce zachowań:

Mediator_pattern

Ravindra babu
źródło
To: „Klasa abstrakcyjna ustanawia” to „relacja między pokrewnymi klasami, a interfejs zapewnia„ ma „możliwość między niepowiązanymi klasami”.
Gabriel
3

Nie masz racji. Scenariuszy jest wiele. Po prostu nie można sprowadzić go do jednej reguły złożonej z 8 słów.

Markiz Lorne
źródło
1
Chyba że jesteś niejasny; Korzystaj z interfejsu, kiedy tylko możesz;)
Peter Lawrey
@PeterLawrey Yeah, nie pozwól, aby argumenty cyrkularne Cię spowolniły ;-)
Marquis of Lorne,
W końcu jest to „przepełnienie stosu”. ;) Chodzi mi o to, że jeśli możesz użyć prostszego interfejsu, zrób to. W przeciwnym razie nie masz innego wyboru, jak tylko użyć klasy abstrakcyjnej. Nie uważam tego za bardzo skomplikowane.
Peter Lawrey,
myślę, że mógłbyś przedstawić bardziej konstruktywny pomysł. jak porozmawiaj o kilku reprezentatywnych scenariuszach /
Adams.H
3

Najkrótszą odpowiedzią jest rozszerzenie klasy abstrakcyjnej, gdy niektóre poszukiwane funkcjonalności są już w niej zaimplementowane.

Jeśli implementujesz interfejs, musisz zaimplementować całą metodę. Jednak w przypadku klas abstrakcyjnych liczba metod, które trzeba zaimplementować, może być mniejsza.

We wzorcu projektowym szablonu musi być zdefiniowane zachowanie. To zachowanie zależy od innych metod, które są abstrakcyjne. Tworząc podklasy i definiując te metody, w rzeczywistości definiujesz główne zachowanie. Podstawowe zachowanie nie może występować w interfejsie, ponieważ interfejs niczego nie definiuje, po prostu deklaruje. Dlatego wzorzec projektowy szablonu zawsze zawiera klasę abstrakcyjną. Jeśli chcesz zachować ciągłość zachowania, musisz rozszerzyć klasę abstrakcyjną, ale nie nadpisywać głównego zachowania.

Shiplu Mokaddim
źródło
Dodatkowe odniesienie do Pure Virtual Function doda więcej informacji na temat konwergencji klasy abstrakcyjnej i interfejsu , Pure virtual functions can also be used where the method declarations are being used to define an interface - similar to what the interface keyword in Java explicitly specifies. In such a use, derived classes will supply all implementations. In such a design pattern, the abstract class which serves as an interface will contain only pure virtual functions, but no data members or ordinary methods. część (1/2)
Abhijeet,
Część (2/2) Dywergencja klasy abstrakcyjnej i interfejsu jest wyjaśniona w ostatnim wierszu powyżej no data members or ordinary methods[w klasie abstrakcyjnej].
Abhijeet
3

Moim zdaniem podstawowa różnica polega na tym an interface can't contain non abstract methods while an abstract class can. Więc jeśli podklasy mają wspólne zachowanie, to zachowanie można zaimplementować w superklasie, a tym samym odziedziczyć w podklasach

Zacytowałem również następujący fragment z książki „Wzorce projektowania architektury oprogramowania w java”

„W języku programowania Java nie ma obsługi dziedziczenia wielokrotnego. Oznacza to, że klasa może dziedziczyć tylko z jednej klasy. Dlatego dziedziczenie powinno być używane tylko wtedy, gdy jest to absolutnie konieczne. Jeśli to możliwe, metody określające typowe zachowanie należy zadeklarować w formę interfejsu Java, który ma być implementowany przez różne klasy implementatorów. Jednak interfejsy cierpią z powodu ograniczenia polegającego na tym, że nie mogą zapewnić implementacji metod. Oznacza to, że każdy użytkownik implementujący interfejs musi jawnie implementować wszystkie metody zadeklarowane w interfejsie, nawet jeśli niektóre z nich metody reprezentują niezmienną część funkcjonalności i mają dokładnie taką samą implementację we wszystkich klasach implementujących, co prowadzi do redundantnego kodu.Poniższy przykład ilustruje, jak wzorzec abstrakcyjnej klasy nadrzędnej może być używany w takich przypadkach bez konieczności stosowania redundantnych implementacji metod. "

user2794921
źródło
2

Klasy abstrakcyjne różnią się od interfejsów w dwóch ważnych aspektach

  • zapewniają domyślną implementację dla wybranych metod (na co odpowiada Twoja odpowiedź)
  • klasy abstrakcyjne mogą mieć stan (zmienne instancji) - jest to więc jeszcze jedna sytuacja, w której chcesz ich użyć zamiast interfejsów
Piotr Kochański
źródło
Uznałbym, że interfejsy mogą mieć zmienne, ale są one domyślnie ostateczne.
Tomasz Mularczyk
1

To dobre pytanie. Te dwa elementy nie są podobne, ale można ich użyć z tego samego powodu, na przykład przepisania. Podczas tworzenia najlepiej jest skorzystać z Interface. Jeśli chodzi o zajęcia, jest dobry do debugowania.

sharonda walker
źródło
0

Abstract classes should be extended when you want to some common behavior to get extended. Klasa abstrakcyjna będzie miała wspólne zachowanie i będzie definiować abstrakcyjną metodę / określone zachowanie, które podklasy powinny implementować.

Interfaces allows you to change the implementation anytime allowing the interface to be intact.

Ramesh PVK
źródło
0

To jest moje zrozumienie, mam nadzieję, że to pomoże

Zajęcia abstrakcyjne:

  1. Może mieć zmienne składowe, które są dziedziczone (nie można tego zrobić w interfejsach)
  2. Może mieć konstruktory (interfejsy nie mogą)
  3. Jego metody mogą mieć dowolną widoczność (tj .: prywatne, chronione itp. - podczas gdy wszystkie metody interfejsu są publiczne)
  4. Może mieć zdefiniowane metody (metody z implementacją)

Interfejsy:

  1. Może mieć zmienne, ale wszystkie są publicznymi statycznymi zmiennymi końcowymi
    • wartości stałe, które nigdy się nie zmieniają w zakresie statycznym
    • zmienne niestatyczne wymagają instancji i nie można utworzyć instancji interfejsu
  2. Wszystkie metody są abstrakcyjne (brak kodu w metodach abstrakcyjnych)
    • cały kod musi być faktycznie napisany w klasie implementującej dany interfejs
zozep
źródło
0

Wykorzystanie abstraktu i interfejsu:

Jeden ma „związek”, a drugi „związek”

Właściwości domyślne zostały ustawione w sposób abstrakcyjny, a dodatkowe właściwości można wyrazić za pomocą interfejsu.

Przykład: -> W przypadku istot ludzkich mamy pewne domyślne właściwości, takie jak jedzenie, spanie itp., Ale jeśli ktoś ma jakieś inne zajęcia w programie, takie jak pływanie, zabawa itp., Można je wyrazić za pomocą interfejsu.

user7439667
źródło