Interface vs abstract

Bin auf der Ranch grad auf n interessantes topic gestoßen: Wann interface, wann abstract? Gucken wir uns die SE-API an so macht uns diese vor: nicht oder sondern und. Und da auch ich eigentlich ein großer Fan von Interfaces und Factories bin mal die Frage nach dem best-practice. Denn mir würde nur der Unterschied einfallen dass ein interface die öffentliche API nach außen darstellt und die darunter liegende abstract dann meist Basisverhalten bereitstellt - was sich aber mit default-Methoden in Interfaces erübrigt. Auch geht das dann in Richtung polimorphie was ja vorher auf Grund der Einfachvererbung so nicht möglich war.
Um mal komkret zu werden: Was wäre ein mögliches praktisches Einsatzbeispiel in dem sich Polimorphie über default-Methoden in Interfaces sinnvoll nutzen lässt?

Ein recht breit diskutierbares Thema (was auch schon an vielen Stellen recht breit diskutiert wurde ;-)).

Tulach von Main Page - APIDesign geht sogar so weit, die Existenzberechtigung von Abstrakten Klassen allgemein in Frage zu stellen - und das tat er, wohlgemerkt, auch schon vor Java 8. Ich denke, etwas oberflächlich betrachtet, dass das „in line“ ist mit dem Mantra „Composition Over Inheritance“, und dem „I“ aus „SOLID“: Interface segregation. Also, oft war eine Rechtfertigung für eine Abstrakte Klasse einfach nur „Schreibarbeit sparen“, in dem Sinne, dass man irgendwelche gemeinsamen Funktionen in die Implementierungen reinvererbt. Wenn das notwendig ist, kann man sich überlegen, ob diese Funktionalität nicht als ein Interface mit einer (konkreten) Implementierung über Composition in diese (dann ggf. nicht mehr abstrakte) Klasse reingebracht werden kann. Aber da gibt es natürlich unendlich viele Freiheitsgrade und Dinge, die man berücksichtigen muss.

(Ich persönlich verwende Abstrakte Klassen seeeehr selten - am häufigsten höchstens für etwas, was bei vielen Implementierungen von „Modellklassen“ gleich ist, aber nicht ausgelagert werden kann, nämlich die Verwaltung von Listenern - also die ganzen addListener und removeListener Sachen, und protected final (!) fireSomethingChanged-Methoden)

Tatsächlich denke ich aber (auch), dass durch die default-Methoden von Interfaces viele Anwendungsfälle von abstrakten Klassen abgedeckt werden. Ich bin zwar ein bißchen skeptisch, ob man diese default-Methoden wirklich als „poor man’s traits“ ansehen sollte. Eigentlich waren sie vorrangig dafür gedacht, Interface evolution überhaupt zu ermöglichen. Ein Interface nachträglich GAR nicht mehr erweitern zu können ist schon eine harte Einschränkung, und hat einen manchmal schon fast dazu genötigt, öffentliche abstrakte Klassen zu erstellen.

Ich sehe einen der potentiell wichtigsten Unterschiede jetzt nur noch darin, dass Methoden in Interfaces immer public sind, und abstrakte Klassen da mit protected und default-Visibility ein paar mehr Abstufungen bieten.

laber

Ja, konkret werden, das wäre mal was :wink: Ich denke, dass man etwas vergröbert sagen kann: Default-Methoden kann man überall da verwenden, wo man vorher Abstrakte Klassen mit public-Funktionen verwendet hat, die sich NICHT auf den Objektzustand (im Sinne von durch das Interface versteckten Implementierungsdetails) beziehen.

Abstrakte nur Klassen, dann wenn etwas mit Interfaces und Default-Methoden nicht geht:

  • Interner Zustand
  • Non-public Methoden (ggf. mit non-public Interfaces abbildbar)
  • Wunsch nach final-Methoden (ggf. nicht nötig, bei non-public Interfaces)
    Public mache ich die Abstrakten Klassen nie.

ein Interface kann auf verschiedene Weise implementiert werden,
verschiede Gruppen können ihre eigenen Basisklassen haben, die nichts miteinander gemeinsam haben

ein Interface kann in einem Common-Package/ Projekt stehen,
der gemeinsame Code Dinge aus einer API benötigen, die in Common gar nicht verfügbar ist,
nur dort wo das Interface auch implementiert wird

Basisklasse ist nicht wirklich zu ersetzen, jedenfalls nicht durch Default-Methoden in einem einzigen zentralen Interface


[quote=Sen-Mithrarin]Auch geht das dann in Richtung polimorphie was ja vorher auf Grund der Einfachvererbung so nicht möglich war.
Um mal komkret zu werden: Was wäre ein mögliches praktisches Einsatzbeispiel in dem sich Polimorphie über default-Methoden in Interfaces sinnvoll nutzen lässt?[/quote]

nur mit Methoden ohne Zustand ist Übergabe zusätzlichen Verhaltens begrenzt, das vielgerühmte addListener() geht schlecht,
wobei man noch das this-Objekt als Key in einem externen Speicher übergeben könnte, dort Zustand verwaltet,

reichlich unschön statisch,
oder halb unschön Thread-Local (was ja selber ein solches Konzept ist mit dem Thread als Key im externen Speicher),
oder gewisse Anforderung an das Interface, dass ein Link auf eine größere Umwelt (Kontext) im Objekt schon vorhanden ist, nicht implementierte Interface-Methode getContext(),
besser nur innerhalb eigenen Programmes…, und im Context eine Listener-Verwaltung angeboten


Basisklassen mit Context und etwa auch DB-Session verwende ich bei mir flächendeckend :wink:
darauf teils verschiedene Hierarchien vorhanden,
eine Richtung mit vielen DB-Lademethoden, eine andere die auf Zustand aufsetzt und selber nichts lädt, andere Ladeklassen benutzt

wenn in seltenen Fällen aus beiden Richtungen etwas benötigt ist, wäre es für mich durchaus interessant,
die DB-Lademethoden, welche recht zustandslos sind, in eine andere Klasse zu übernehmen…,

soviel geantwortet zum gefragten praktischen Einsatzbeispiel, falls nachvollziehbar :wink:

Ich sehe das so wie @Marco13 und Tulach.

Ich habe in meinen Projekten immer wieder die Situation, dass ich für eine neue Anforderung gerne gerade diese Basisfunktionalität, die in den abstrakten Klassen steht, (Jahre) später gerne Austauschen möchte. Die darauf aufbauende Funktionalität mehrerer Ableitungen will ich eigentlich nicht ändern.

Wenn die gemeinsame Funktonalität mittels eigenem Interface in die Spezialisierungen hineininjiziert wurde ist das ganz einfach. Wenn nicht, muss ich die gesamte Vererbungshirarchie von der abstrakten Klasse an aufwärts nach programmieren oder wenigstens kopieren.

bye
TT

Hmm, dass man in Interfaces nicht den internen Zustand eines implementierenden Objektes ändern kann wusste ich noch nicht - und an Sichtbarkeit habe ich gar nicht gedacht. Sind doch wieder Gründe die für Interface > Abstract > Impl sprechen - hab da so noch nie über den Punkt “non-public” nachgedacht.

Naja, sowas wie

interface Foo {
    void modifyStateOfImplementingObject();

    default void callMeMaybe() {
        modifyStateOfImplementingObject();
    }
}

geht natürlich :wink: Es ging wohl (etwas oberflächlich formuliert) darum, dass man in einem Interface eher seltener nur eine Sammlung von „getter/setter“-Pärchen stehen hat :wink:

implements an interface must provide an implementation of all the methods of that interface.,