Kiedy używać interfejsu zamiast klasy abstrakcyjnej i odwrotnie?


427

To może być ogólne pytanie dotyczące OOP. Chciałem dokonać ogólnego porównania interfejsu z klasą abstrakcyjną na podstawie ich użycia.

Kiedy chciałoby się użyć interfejsu, a kiedy klasy abstrakcyjnej ?



1
Oprócz poniższych odpowiedzi znajduje się krótka lista miejsc, w których możesz preferować interfejsy, a gdzie nie: Kiedy używać interfejsów: msdn.microsoft.com/en-us/library/3b5b8ezk(v=vs.80) .aspx
Anthony

użyj streszczenia, jeśli nie jesteś pewien, co zrobi klasa. użyj interfejsu, jeśli jesteś.
Uğur Gümüşhan

Interesuje mnie, ilu programistów, którzy nie pracują w firmie Microsoft, definiują i używają interfejsów w codziennym rozwoju.
user1451111,

Odpowiedzi:


431

Napisałem o tym artykuł:

Klasy abstrakcyjne i interfejsy

Zreasumowanie:

Kiedy mówimy o klasach abstrakcyjnych, definiujemy cechy typu obiektu; określając, czym jest obiekt .

Kiedy mówimy o interfejsie i definiujemy możliwości, które obiecujemy zapewnić, mówimy o zawarciu umowy o tym, co może zrobić obiekt.


114
To był ery pomocne: Interfaces do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk". Dziękuję
aexl

2
Wygląda na to, że Twój link nie działa.
Kim Ahlstrøm Meyn Mathiassen

Wyjaśnienie Alexa poniżej, dotyczące: różnica między opisywaniem tylko zaimplementowanych funkcji a opisywaniem stanu przechowywanego wydaje się lepszą odpowiedzią na to pytanie, ponieważ różnice nie są jedynie filozoficzne.
Duncan Malashock,

1
Duncan Malashock, niezupełnie. Odpowiedź Jorge jest lepsza. Odpowiedź Alexa koncentruje się na mechanice, a Jorge bardziej na semantyce.
Nazar Merza

8
Podoba mi się oświadczenie przed podaną odpowiedzią:Use abstract classes and inheritance if you can make the statement “A is a B”. Use interfaces if you can make the statement “A is capable of [doing] as”
S1r-Lanzelot

433

Klasa abstrakcyjna może mieć stan współdzielony lub funkcjonalność. Interfejs to tylko obietnica zapewniająca stan lub funkcjonalność. Dobra klasa abstrakcyjna zmniejszy ilość kodu, który trzeba przepisać, ponieważ można udostępnić jego funkcjonalność lub stan. Interfejs nie ma zdefiniowanych informacji do udostępnienia


65
To jest dla mnie najlepsza odpowiedź tutaj i szkoda, że ​​nie głosowano wyżej. Tak, istnieją dwie filozoficzne różnice między tymi dwoma pojęciami, ale podstawową kwestią jest to, że klasy abstrakcyjne zapewniają, że wszyscy potomkowie mają wspólną funkcjonalność / stan, przy czym interfejs zapewnia tylko wspólną więź.
drharris

3
Na przykład abstrakcyjna klasa bazowa jest używana dla wzorca projektowania metody szablonu , podczas gdy interfejs jest używany dla wzorca projektowania strategii .
Raedwald,

1
Myślę, że podsumowanie Jorge wyjaśnia podstawowe istnienie obu z nich, podczas gdy odpowiedź Alexa to różnica w wynikach. Chciałbym oznaczyć obie jako prawidłowe odpowiedzi, ale nadal wolę odpowiedź Jorge.
Chirantan

A tutaj jest przykład z kodem.
shaijut

Dla mnie ta „Dobra klasa abstrakcyjna zmniejszy ilość kodu, który trzeba przepisać, ponieważ można udostępnić jego funkcjonalność lub stan”. stwierdzenie jest rdzeniem odpowiedzi.
Div Tiwari

82

Osobiście prawie nigdy nie muszę pisać klas abstrakcyjnych.

Najczęściej widzę, że klasy abstrakcyjne są używane (źle), ponieważ autor klasy abstrakcyjnej używa wzorca „Metoda szablonu”.

Problem z „metodą szablonów” polega na tym, że prawie zawsze jest ona w pewnym stopniu ponownie uruchamiana - klasa „pochodna” wie nie tylko o „abstrakcyjnej” metodzie swojej klasy bazowej, którą implementuje, ale także o metodach publicznych klasy podstawowej , mimo że w większości przypadków nie trzeba do nich dzwonić.

(Zbyt uproszczony) przykład:

abstract class QuickSorter
{
    public void Sort(object[] items)
    {
        // implementation code that somewhere along the way calls:
        bool less = compare(x,y);
        // ... more implementation code
    }
    abstract bool compare(object lhs, object rhs);
}

Tak więc tutaj autor tej klasy napisał ogólny algorytm i chce, aby ludzie go używali, „specjalizując się”, zapewniając własne „zaczepy” - w tym przypadku metodę „porównania”.

Tak więc zamierzone użycie jest mniej więcej takie:

class NameSorter : QuickSorter
{
    public bool compare(object lhs, object rhs)
    {
        // etc.
    }
}

Problem polega na tym, że nadmiernie połączyłeś dwie koncepcje:

  1. Sposób porównywania dwóch przedmiotów (który przedmiot powinien być pierwszy)
  2. Metoda sortowania przedmiotów (tj. Sortowanie szybkie i scalanie itp.)

W powyższym kodzie teoretycznie autor metody „porównaj” może ponownie wywołać metodę superklasy „Sortuj” ... nawet jeśli w praktyce nigdy nie będzie tego chciał ani nie musiał.

Cena, którą płacisz za to niepotrzebne sprzężenie, polega na tym, że trudno jest zmienić nadklasę, aw większości języków OO nie można jej zmienić w czasie wykonywania.

Alternatywną metodą jest użycie zamiast tego wzorca projektowego „Strategia”:

interface IComparator
{
    bool compare(object lhs, object rhs);
}

class QuickSorter
{
    private readonly IComparator comparator;
    public QuickSorter(IComparator comparator)
    {
        this.comparator = comparator;
    }

    public void Sort(object[] items)
    {
        // usual code but call comparator.Compare();
    }
}

class NameComparator : IComparator
{
    bool compare(object lhs, object rhs)
    {
        // same code as before;
    }
}

Więc zauważ teraz: wszystko, co mamy, to interfejsy i konkretne implementacje tych interfejsów. W praktyce tak naprawdę nie potrzebujesz niczego więcej, aby wykonać projekt OO na wysokim poziomie.

Aby „ukryć” fakt, że zaimplementowaliśmy „sortowanie nazw” za pomocą klasy „QuickSort” i „NameComparator”, możemy gdzieś napisać metodę fabryczną:

ISorter CreateNameSorter()
{
    return new QuickSorter(new NameComparator());
}

Za każdym razem, gdy masz klasę abstrakcyjną, możesz to zrobić ... nawet jeśli istnieje naturalna relacja ponownego wejścia między klasą podstawową i pochodną, ​​zwykle opłaca się je wyrazić.

Ostatnia myśl: wszystko, co zrobiliśmy powyżej, to „skomponowanie” funkcji „NameSorting” za pomocą funkcji „QuickSort” i funkcji „NameComparison” ... w funkcjonalnym języku programowania ten styl programowania staje się jeszcze bardziej naturalny, z mniejszym kodem.


5
To, że możesz użyć klas abstrakcyjnych lub wzorca metody szablonu, nie oznacza, że ​​musisz ich unikać. Wzorzec strategii to inny wzorzec dla innej sytuacji, jak w tym przykładzie, ale istnieje wiele przykładów, w których wzorzec szablonu jest znacznie bardziej odpowiedni niż strategia.
Jorge Córdoba

5
Cóż, z mojego doświadczenia nigdy nie spotkałem ich (sytuacje, w których preferowana jest metoda szablonów) ... lub rzadko. I to wszystko „abstrakcyjne” to - obsługa języka dla wzorca projektowego „metoda szablonu”.
Paul Hollingsworth

Ok, użyłem go raz dla systemu eksperckiego, w którym proces był podobny: zdobądź 1. FillTheParameters, 2. Stwórz iloczyn wektorowy między nimi, 3. Dla każdej pary oblicz wynik, 4. Dołącz do wyników, w których kroki 1 i 3 gdzie delegowane i 2 i 4 zaimplementowane w klasie podstawowej.
Jorge Córdoba

10
Uważam, że prawie każde użycie klas abstrakcyjnych jest trudniejsze do zrozumienia. Myślenie w kategoriach skrzynek, które komunikują się ze sobą zamiast relacji dziedziczenia, jest łatwiejsze (dla mnie) ... Ale zgadzam się również, że obecne języki OO wymuszają zbyt wiele bojlera ... Funkcjonalne będzie sposób na przejście przez OO
Paul Hollingsworth

4
Przykład niewłaściwego użycia jest dość trywialny. Rzadko sprowadza się do tak ładnej, pozbawionej funkcjonalności funkcjonalności, jak porównywanie. Znacznie bardziej powszechne są sytuacje, w których istnieje pewna domyślna funkcjonalność, którą klasy pochodne zastępują lub rozszerzają (w tym drugim przypadku jest całkowicie poprawne wywołanie funkcji klasy bazowej). W twoim przykładzie nie ma domyślnej funkcjonalności, więc użycie klasy abstrakcyjnej nie ma uzasadnienia.
SomeWittyUsername

41

Jeśli patrzysz na java jako język OOP,

Interfejs nie zapewnia implementacji metody ” nie jest już ważny w przypadku uruchomienia Java 8. Teraz Java zapewnia implementację interfejsu dla metod domyślnych.

Mówiąc najprościej, chciałbym użyć

interfejs: Aby wdrożyć umowę przez wiele niepowiązanych obiektów. Zapewnia funkcję „ HAS A ”.

Klasa abstrakcyjna: Aby zaimplementować to samo lub inne zachowanie wśród wielu powiązanych obiektów. Ustanawiarelację„ JEST A ”.

Oracle strona dostarcza kluczowych różnic pomiędzy interfacei abstractklasę.

Rozważ użycie klas abstrakcyjnych, jeśli:

  1. Chcesz dzielić kod między kilkoma blisko spokrewnionymi klasami.
  2. Oczekujesz, że klasy, które rozszerzają twoją klasę abstrakcyjną, mają wiele wspólnych metod lub pól lub wymagają modyfikatorów dostępu innych niż publiczne (takie jak chronione i prywatne).
  3. Chcesz zadeklarować pola niestatyczne lub nie-końcowe.

Rozważ użycie interfejsów, jeśli:

  1. Oczekujesz, że niepowiązane klasy zaimplementują Twój interfejs. Na przykład wiele niepowiązanych obiektów może implementować Serializableinterfejs.
  2. Chcesz określić zachowanie określonego typu danych, ale nie przejmuj się tym, kto wdraża to zachowanie.
  3. Chcesz skorzystać z wielokrotnego dziedziczenia typu.

Przykład:

Klasa abstrakcyjna ( relacja IS A )

Reader to klasa abstrakcyjna.

BufferedReader toReader

FileReader toReader

FileReaderi BufferedReadersą wykorzystywane do wspólnego celu: Odczytywanie danych i są one powiązane poprzez Readerklasę.

Interfejs ( funkcja HAS A )

Serializable to interfejs.

Załóżmy, że masz dwie klasy aplikacji, które implementują Serializableinterfejs

Employee implements Serializable

Game implements Serializable

Tutaj nie można ustanowić żadnej relacji poprzez Serializableinterfejs między Employeei Game, które są przeznaczone do różnych celów. Oba są w stanie szeregować stan, a porównanie kończy się na tym.

Spójrz na te posty:

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


40

OK, po tym, jak sam to „wymamrotałem” - tutaj jest to w kategoriach laika (możesz mnie poprawić, jeśli się mylę) - Wiem, że ten temat jest przestarzały, ale ktoś inny może się na niego natknąć pewnego dnia ...

Klasy abstrakcyjne umożliwiają tworzenie schematu i pozwalają dodatkowo KONSTRUKOWAĆ (implementować) właściwości i metody, które mają WSZYSTKIE potomkowie.

Z drugiej strony interfejs pozwala tylko zadeklarować, że chcesz, aby właściwości i / lub metody o danej nazwie istniały we wszystkich klasach, które je implementują - ale nie określa, w jaki sposób powinieneś je wdrożyć. Ponadto klasa może implementować WIELE interfejsów, ale może rozszerzyć tylko JEDNĄ klasę abstrakcyjną. Interfejs to bardziej narzędzie architektoniczne wysokiego poziomu (które staje się bardziej zrozumiałe, jeśli zaczniesz rozumieć wzorce projektowe) - Streszczenie ma stopę w obu obozach i może również wykonywać niektóre brudne prace.

Po co używać jednego na drugim? Pierwszy pozwala na bardziej konkretną definicję potomków - drugi pozwala na większy polimorfizm . Ten ostatni punkt jest ważny dla końcowego użytkownika / kodera, który może wykorzystać te informacje do implementacji AP I (nterface) w różnych kombinacjach / kształtach w zależności od potrzeb.

Myślę, że był to dla mnie moment „żarówka” - myślę o interfejsach mniej z perspektywy autora, a więcej z koderem pochodzącym z późniejszego łańcucha, który dodaje implementację do projektu lub rozszerza API.


na tej podstawie: Obiekt implementujący interfejs przyjmuje TYP. To jest kluczowe. Możesz więc przekazać różne warianty interfejsu do klasy, ale odwołać się do nich (i ich metod) Z NAZWĄ TYPU INTERFEJSU. W ten sposób eliminujesz potrzebę przełączania lub pętli if / else. Wypróbuj ten samouczek na ten temat - demonstruje on użycie interfejsu poprzez wzorzec strategii. phpfreaks.com/tutorial/design-patterns---strategy-and-bridge/…
sunwukung

Całkowicie zgadzam się z twoim momentem żarówki: „API (nterface) w różnych kombinacjach / kształtach w zależności od ich potrzeb”! Bardzo, bardzo dobry punkt do zrobienia.
Trevor Boyd Smith

39

Moje dwa centy:

Interfejs zasadniczo definiuje umowę, do której musi się stosować każda klasa implementująca (implementująca elementy interfejsu). Nie zawiera żadnego kodu.

Z drugiej strony klasa abstrakcyjna może zawierać kod i mogą istnieć pewne metody oznaczone jako abstrakcyjne, które klasa dziedzicząca musi zaimplementować.

Rzadkie sytuacje, w których korzystałem z klas abstrakcyjnych, polegają na tym, że mam domyślną funkcjonalność, której dziedziczenie może nie być interesujące w przypadku przesłonięcia, powiedzmy abstrakcyjnej klasy bazowej, z której dziedziczą niektóre klasy specjalistyczne.

Przykład (bardzo prymitywny jeden!): Rozważmy klasy bazowej o nazwie klienta, który ma abstrakcyjnych metod, takich jak CalculatePayment(), CalculateRewardPoints()i pewne metody abstrakcyjne, takie jak GetName(), SavePaymentDetails().

Wyspecjalizowane klasy, takie jak RegularCustomer i GoldCustomerodziedziczą po Customerklasie podstawowej i implementują własną logikę CalculatePayment()i CalculateRewardPoints()metody, ale ponownie wykorzystują metody GetName()i SavePaymentDetails().

Możesz dodać więcej funkcji do klasy abstrakcyjnej (czyli metod nieabstrakcyjnych) bez wpływu na klasy potomne, które korzystały ze starszej wersji. Podczas gdy dodawanie metod do interfejsu wpłynęłoby na wszystkie klasy implementujące go, ponieważ musiałyby one teraz implementować nowo dodane elementy interfejsu.

Klasa abstrakcyjna ze wszystkimi elementami abstrakcyjnymi byłaby podobna do interfejsu.


1
+1 dla „Możesz dodać więcej funkcji do klasy abstrakcyjnej (czyli metod nieabstrakcyjnych) bez wpływu na klasy potomne, które korzystały ze starszej wersji. Dodanie metod do interfejsu wpłynęłoby na wszystkie klasy implementujące go, ponieważ będą musiały teraz zaimplementować nowo dodani członkowie interfejsu. ”
Div Tiwari

interfejsy mogą mieć „domyślne” metody, więc brak metody impl w interfejsach jest złym pomysłem. Kluczem jest tutaj relacja „rodzic-dziecko” IS-A. Także „Wspólne atrybuty” a „Wspólne właściwości”. np. Pies iS-A Animal. Ale pies może także „chodzić”
ha9u63ar

30

Kiedy robić to, co jest bardzo proste, jeśli masz jasną koncepcję.

Klasy abstrakcyjne można uzyskać, natomiast interfejsy można zaimplementować. Istnieje pewna różnica między nimi. Kiedy wyprowadzasz klasę abstrakcyjną, związek między klasą pochodną a klasą podstawową jest relacją „to”. np. pies to zwierzę, owca to zwierzę, co oznacza, że ​​klasa pochodna dziedziczy niektóre właściwości z klasy podstawowej.

Natomiast w przypadku implementacji interfejsów relacja jest „może być”. np. Pies może być psem szpiegowskim. Pies może być psem cyrkowym. Pies może być psem wyścigowym. Co oznacza, że ​​wdrażasz określone metody uzyskania czegoś.

Mam nadzieję, że wszystko jasne.


11

1. Jeśli tworzysz coś, co zapewnia wspólną funkcjonalność niepowiązanym klasom, użyj interfejsu.

2. Jeśli tworzysz coś dla obiektów blisko powiązanych w hierarchii, użyj klasy abstrakcyjnej.



7

Myślę, że najbardziej zwięzły sposób to:

Wspólne właściwości => klasa abstrakcyjna.
Wspólna funkcjonalność => interfejs.

I mówiąc mniej zwięźle ...

Przykład klasy abstrakcyjnej:

public abstract class BaseAnimal
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }
}

public class Dog : BaseAnimal
{
    public Dog() : base(4) { }
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }
}

Ponieważ zwierzęta mają wspólną właściwość - w tym przypadku liczbę nóg - warto stworzyć klasę abstrakcyjną zawierającą tę wspólną właściwość. Pozwala nam to również napisać wspólny kod, który działa na tej właściwości. Na przykład:

public static int CountAllLegs(List<BaseAnimal> animals)
{
    int legCount = 0;
    foreach (BaseAnimal animal in animals)
    {
        legCount += animal.NumberOfLegs;
    }
    return legCount;
}

Przykład interfejsu:

public interface IMakeSound
{
    void MakeSound();
}

public class Car : IMakeSound
{
    public void MakeSound() => Console.WriteLine("Vroom!");
}

public class Vuvuzela : IMakeSound
{
    public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");        
}

Zauważ, że Vuvuzelas i Samochody to zupełnie inne rzeczy, ale mają wspólną funkcjonalność: wydawanie dźwięków. Dlatego interfejs ma tutaj sens. Ponadto pozwoli programistom grupować rzeczy, które wydają dźwięki razem, we wspólnym interfejsie - IMakeSoundw tym przypadku. Za pomocą tego projektu możesz napisać następujący kod:

List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());

foreach (IMakeSound soundMaker in soundMakers)
{
    soundMaker.MakeSound();
}

Czy możesz powiedzieć, co by to dało?

Na koniec możesz połączyć te dwa elementy.

Połączony przykład:

public interface IMakeSound
{
    void MakeSound();
}

public abstract class BaseAnimal : IMakeSound
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }

    public abstract void MakeSound();
}

public class Cat : BaseAnimal
{
    public Cat() : base(4) { }

    public override void MakeSound() => Console.WriteLine("Meow!");
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }

    public override void MakeSound() => Console.WriteLine("Hello, world!");
}

Tutaj wymagamy, aby wszyscy BaseAnimalwydawali dźwięki, ale nie znamy jeszcze jego implementacji. W takim przypadku możemy wyodrębnić implementację interfejsu i przekazać jej implementację do jego podklas.

Ostatni punkt, pamiętasz, jak w przykładzie klasy abstrakcyjnej mogliśmy operować na wspólnych właściwościach różnych obiektów, aw przykładzie interfejsu mogliśmy wywoływać wspólną funkcjonalność różnych obiektów? W tym ostatnim przykładzie moglibyśmy zrobić jedno i drugie.


7

Kiedy preferować klasę abstrakcyjną zamiast interfejsu?

  1. Jeśli planuje się aktualizację klasy podstawowej przez cały czas trwania programu / projektu, najlepiej pozwolić, aby klasa podstawowa była klasą abstrakcyjną
  2. Jeśli ktoś próbuje zbudować szkielet dla obiektów blisko powiązanych w hierarchii, bardzo korzystne jest użycie klasy abstrakcyjnej

Kiedy preferować interfejs zamiast klasy abstrakcyjnej?

  1. Jeśli nie ma się do czynienia z ogromnym hierarchicznym rodzajem frameworka, interfejsy byłyby doskonałym wyborem
  2. Ponieważ wielokrotne dziedziczenie nie jest obsługiwane w przypadku klas abstrakcyjnych (problem z diamentem), interfejsy mogą uratować dzień

Co sprawiło, że pomyślałeś, że prawie dekadowe pytanie wymagało 22. odpowiedzi?
jonrsharpe

3
Ten sam sposób myślenia, który skłonił mnie do znalezienia prostej odpowiedzi na pytanie.
Satya

1
FWIW, bardzo podoba mi się ta odpowiedź.
Brent Rittenhouse

6

Klasy mogą dziedziczyć tylko jedną klasę podstawową, więc jeśli chcesz użyć klas abstrakcyjnych w celu zapewnienia polimorfizmu grupie klas, wszystkie muszą dziedziczyć po tej klasie. Klasy abstrakcyjne mogą również zapewniać członków, które zostały już zaimplementowane. Dlatego możesz zapewnić pewną ilość identycznej funkcjonalności z klasą abstrakcyjną, ale nie możesz z interfejsem.

Oto kilka zaleceń, które pomogą Ci zdecydować, czy użyć interfejsu, czy klasy abstrakcyjnej, aby zapewnić polimorfizm komponentów.

  • Jeśli przewidujesz utworzenie wielu wersji swojego komponentu, utwórz klasę abstrakcyjną. Klasy abstrakcyjne zapewniają prosty i łatwy sposób na aktualizację komponentów. Aktualizując klasę podstawową, wszystkie dziedziczące klasy są automatycznie aktualizowane wraz ze zmianą. Z drugiej strony interfejsów nie można zmienić po utworzeniu w ten sposób. Jeśli wymagana jest nowa wersja interfejsu, musisz utworzyć zupełnie nowy interfejs.
  • Jeśli tworzona funkcjonalność będzie przydatna w wielu różnych obiektach, użyj interfejsu. Klasy abstrakcyjne powinny być używane przede wszystkim dla obiektów blisko powiązanych, podczas gdy interfejsy najlepiej nadają się do zapewniania wspólnej funkcjonalności niezwiązanym klasom.
  • Jeśli projektujesz małe, zwięzłe elementy funkcjonalności, użyj interfejsów. Jeśli projektujesz duże jednostki funkcjonalne, użyj klasy abstrakcyjnej.
  • Jeśli chcesz zapewnić wspólną, zaimplementowaną funkcjonalność we wszystkich implementacjach komponentu, użyj klasy abstrakcyjnej. Klasy abstrakcyjne pozwalają częściowo zaimplementować klasę, podczas gdy interfejsy nie zawierają implementacji dla żadnych członków.

Skopiowano z:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx


W UML nie ma nic, co wykluczałoby dziedziczenie wielu klas. Wielokrotne dziedziczenie jest określane przez język programowania, a nie przez UML. Na przykład dziedziczenie wielu klas nie jest dozwolone w Javie i C #, ale jest dozwolone w C ++.
BobRodes,

@BobRodes: Istnieje wiele funkcji, które struktury obiektowe mogą zapewnić w różnych kombinacjach, ale nie we wszystkich kombinacjach. Uogólnione wielokrotne dziedziczenie wyklucza niektóre inne użyteczne kombinacje funkcji, w tym możliwość rzutowania odwołania bezpośrednio na dowolny typ nadrzędny rzeczywistej instancji lub dowolnego obsługiwanego przez niego typu interfejsu oraz zdolność do samodzielnego kompilowania typów podstawowych i typów pochodnych oraz łączenia ich w czasie wykonywania.
supercat

@supercat Wasz jest dobrym wyjaśnieniem niektórych problemów wynikających z używania wielokrotnego dziedziczenia. Niemniej jednak w UML nie ma nic, co wykluczałoby dziedziczenie wielu klas na diagramie. Odpowiedziałem na powyższe „Klasy mogą odziedziczyć tylko jedną klasę podstawową ...”, co wcale nie jest takie.
BobRodes,

@BobRodes: Pytanie zostało oznaczone jako Java. Java zawiera wskazane funkcje, a zatem ogranicza się do form wielokrotnego dziedziczenia, które nie mogą wytworzyć „śmiertelnego diamentu” (chociaż w rzeczywistości sposób, w jaki zaimplementowali domyślne implementacje interfejsu, umożliwia śmiertelny diament).
supercat

@supercat Oh, ok. Zwykle nie patrzę na tagi Java, więc w tym czasie napisałem, że przynajmniej myślałem, że komentuję odpowiedź UML. W każdym razie zgadzam się z twoim komentarzem.
BobRodes

3

Rozważ użycie klas abstrakcyjnych, jeśli któreś z poniższych stwierdzeń dotyczy Twojej sytuacji:

  1. Chcesz dzielić kod między kilkoma blisko spokrewnionymi klasami.
  2. Oczekujesz, że klasy, które rozszerzają Twoją klasę abstrakcyjną, mają wiele wspólnych metod lub pól lub wymagają modyfikatorów dostępu innych niż publiczny (takich jak chroniony i prywatny).
  3. Chcesz zadeklarować pola niestatyczne lub nie-końcowe. Umożliwia to zdefiniowanie metod, które mogą uzyskiwać dostęp i modyfikować stan obiektu, do którego należą.

Rozważ użycie interfejsów, jeśli któreś z poniższych stwierdzeń dotyczy Twojej sytuacji:

  1. Oczekujesz, że niepowiązane klasy zaimplementują Twój interfejs. Na przykład interfejsy porównywalne i klonowalne są implementowane przez wiele niepowiązanych klas.
  2. Chcesz określić zachowanie określonego typu danych, ale nie przejmuj się tym, kto wdraża to zachowanie.
  3. Chcesz skorzystać z wielu spadków.

Źródło


2

Odpowiedzi różnią się w zależności od języka. Na przykład w Javie klasa może implementować (dziedziczyć) wiele interfejsów, ale dziedziczyć tylko z jednej klasy abstrakcyjnej. Interfejsy zapewniają większą elastyczność. Ale nie jest to prawdą w C ++.


2

Dla mnie w wielu przypadkach korzystałbym z interfejsów. Ale w niektórych przypadkach wolę zajęcia abstrakcyjne.

Klasy w OO zasadniczo odnoszą się do wdrażania. Korzystam z klas abstrakcyjnych, gdy chcę wymusić pewne szczegóły implementacji dla dzieci, w przeciwnym razie korzystam z interfejsów.

Oczywiście klasy abstrakcyjne są przydatne nie tylko w wymuszaniu implementacji, ale także w dzieleniu się pewnymi szczegółowymi szczegółami wśród wielu powiązanych klas.


1

Użyj klasy abstrakcyjnej, jeśli chcesz podać podstawowe implementacje.


1
Dzięki Sebastian. Ale co, jeśli nie muszę mieć podstawowej implementacji? Czy klasa abstrakcyjna i interfejs nie będą takie same, jeśli to jedyna różnica między nimi? Dlaczego jest różnica?
Chirantan

1
Ponieważ niektóre języki nie mają interfejsów - C ++.
jmucchiello

1

w java możesz odziedziczyć po jednej (abstrakcyjnej) klasie, aby „zapewnić” funkcjonalność i możesz zaimplementować wiele interfejsów, aby „zapewnić” funkcjonalność


lil 'wskazówka: jeśli chcesz dziedziczyć po klasie abstrakcyjnej i interfejsie, upewnij się, że klasa abstrakcyjna implementuje interfejs
Andreas Niedermair

1

Czysto na podstawie dziedziczenia użyłbyś abstraktu, w którym wyraźnie definiujesz potomka, abstrakcyjne relacje (tj. Zwierzę-> kot) i / lub wymaga dziedziczenia właściwości wirtualnych lub niepublicznych, zwłaszcza stanu wspólnego (którego interfejsy nie mogą obsługiwać ).

Powinieneś starać się faworyzować kompozycję (poprzez wstrzykiwanie zależności) nad dziedziczeniem, o ile możesz, i zauważ, że interfejsy będące umowami wspierają testowanie jednostkowe, rozdzielanie problemów i (w zależności od języka) wielokrotne dziedziczenie w sposób, w jaki Abstrakty nie mogą.


1

Ciekawym miejscem, w którym interfejsy wypadają lepiej niż klasy abstrakcyjne, jest potrzeba dodania dodatkowej funkcjonalności do grupy (powiązanych lub niepowiązanych) obiektów. Jeśli nie możesz dać im podstawowej klasy abstrakcyjnej (np. Są sealedlub mają już element nadrzędny), możesz zamiast tego dać im fikcyjny (pusty) interfejs, a następnie po prostu napisać metody rozszerzenia dla tego interfejsu.


0

Może to być bardzo trudny telefon ...

Jeden wskaźnik, który mogę podać: Obiekt może implementować wiele interfejsów, podczas gdy obiekt może odziedziczyć tylko jedną klasę bazową (w nowoczesnym języku OO, takim jak c #, wiem, że C ++ ma wielokrotne dziedziczenie - ale czy to nie jest obrzydzone?)


Wielokrotne dziedziczenie pozwala na implementację Mixinów pozornie, dobrze napisane Mixiny są łatwe w obsłudze, ale bardzo trudne do zdobycia i trudne do napisania, gdzieś nie brakuje. Mixiny są całkiem fajne jako całość, choć IMO.
Martijn Laarman

W rzeczywistości tak nie było, wielokrotne dziedziczenie jest rzeczywiście pewnym impulsem do debaty między nami, maniakami, i nie widzę absolutnie żadnego powodu, by głosować za nimi. W rzeczywistości głosowałem za odpowiedzią.
Martijn Laarman

Jedyne, co próbowałem zrobić, to to, że możliwe są również sposoby miksowania w językach z pojedynczym dziedziczeniem (C #, PHP, javascript), ale poprzez hacky zachowanie lub tandetną składnię. Uwielbiam Mixina, kiedy działają, ale wciąż nie jestem zdecydowany, czy mam wiele do dziedziczenia, czy nie.
Martijn Laarman

Ta odpowiedź jest bardziej różnicą składniową niż różnicą projektową. Myślę, że prosi o różnicę w projekcie
Pramod Setlur

0

Klasa abstrakcyjna może mieć implementacje.

Interfejs nie ma implementacji, po prostu definiuje rodzaj umowy.

Mogą występować również różnice zależne od języka: na przykład C # nie ma wielokrotnego dziedziczenia, ale wiele interfejsów można zaimplementować w klasie.


Kiedy mówisz „rodzaj umowy”, masz na myśli jak w serwisach internetowych?
Chirantan

Technicznie rzecz biorąc, usługi sieciowe nie działają z interfejsami. Z umową mam na myśli, że użytkownik obiektu wie, jakie metody są obecne na tym obiekcie. Na przykład interfejs myszy będzie miał metodę Przenieś oraz zdarzenie lewego i prawego przycisku myszy.
Gerrie Schenck

-1

Podstawowa reguła kciuka: dla „Rzeczowników” użyj klasy Abstrakcja, a dla „Czasowników” użyj interfejsu

Np .: carjest klasą abstrakcyjną i drivemożemy zrobić z niej interfejs.


5
To nie ma sensu, możemy również umieścić funkcjonalność drivew samochodzie - jest to klasa abstrakcyjna.
Arslan Ali
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.