Delegation versus Inheritance in grafischen Oberflächen

In diesem Artikel werden wir den Unterschied zwischen den Vererbungs- und Delegierungskonzepten untersuchen. Oder, um es besser auszudrücken, warum ich die Delegation bevorzuge und warum ich diese selten verwendete Funktion in Java hervorheben möchte.


Diesen Artikel gibt es auch als Youtube Video auf meinem Kanal und ist zu finden unter

Youtube – German – 4k


Die Herausforderung, vor der wir heute stehen, ist im Bereich grafischer Benutzeroberflächen wie Desktop- oder Web-Apps weit verbreitet. Java wird häufig als Entwicklungssprache für beide Welten verwendet, und es spielt keine Rolle, ob wir uns im klassischen Swing, JavaFX oder im Bereich von Web-Frameworks wie Vaadin befinden. Ich habe mich explizit für ein Pseudoklassenmodell in Java entschieden, da ich die Entwurfsmuster hier ohne technische Details betrachten möchte.

Ziel ist es, eine benutzerdefinierte Komponente zu erstellen, die aus einem Texteingabefeld und einer Schaltfläche besteht. Beide Elemente sollten nebeneinander angezeigt werden, d.h. in einem horizontalen Layout. Die jeweiligen Komponenten haben in diesem Beispiel keine Funktion. Ich möchte hier ausschließlich die Unterschiede zwischen Vererbung und Delegation herausarbeiten.

Das Basisklassenmodell

Meist gibt es die wesentlichen Grund-Komponenten in einem Framework. In unserem Fall handelt es sich um ein TextField, eine Schaltfläche (Button) und ein horizontales oder vertikales Layout. Alle diese Komponenten sind jedoch in eine Vererbungsstruktur eingebettet. In unserem Fall habe ich die folgende Konstruktion gewählt: Jede Komponente entspricht der Komponentenschnittstelle, für die es eine abstrakte Implementierung namens AbstractComponent gibt.

Die Klasse AbstractComponent enthält Framework-spezifische und technologisch basierte Implementierungen. Der Button sowie das TextField erweitern die Klasse AbstractComponent. Layouts sind normalerweise separat und daher eine spezielle Gruppe von Komponenten, die in unserem Fall zu einer abstrakten Klasse mit dem Namen Layout führt, die von der Klasse AbstractComponent erbt.

In dieser abstrakten Klasse gibt es layout-spezifische Implementierungen, die für alle Arten von Layouts gleich sind. Die Implementierungen mit dem Namen HorizontalLayout und VerticalLayout basieren genau darauf. Insgesamt ist dies bereits ein recht komplexes Ausgangsmodell.

Vererbung – Erste Version

In der ersten Version zeige ich eine Lösung, die ich oft in Projekten gesehen habe. Als Basis für eine benutzerdefinierte Komponente wird eine Basiskomponente aus dem Framework als übergeordnetes Element verwendet. Die direkte Vererbung von einem Layout wird häufig verwendet, um alle anderen intern untergeordneten Komponenten auf dem Bildschirm zu strukturieren. Innerhalb des Konstruktors werden die intern erforderlichen Elemente generiert und der geerbten Layoutstruktur hinzugefügt.

Bei genauer Betrachtung, wie sich die Komponente bei späterer Verwendung verhält, wird deutlich, dass eine Ableitung von einer grundlegenden Komponente ihre Fallstricke mit sich bringt.

Was genau ist hier passiert? Wenn jetzt eine Instanz der benutzerdefinierten Komponente InputComponent verwendet wird, kann sie als Layout angezeigt werden. Das ist hier aber nicht mehr der Fall. Im Gegenteil, es ist sogar falsch. Alle von der Layout-Implementierung geerbten Methoden sind mit dieser Komponente ebenfalls öffentlich verfügbar. Aber Du wolltest etwas anderes erreichen. Zunächst wollten wir den vorhandenen Code, der in der Komponentenimplementierung HorizontalLayout enthalten ist, wiederverwenden.

Andererseits möchtest Du eine Komponente, die nur die Methoden für die erforderliche Interaktion, die für das benutzerdefinierte Verhalten erforderlich sind, extern delegiert sehen. In diesem Fall werden die öffentlichen Methoden aus Button und TextField symbolisch verwendet. Außerdem ist diese Komponente an ein visuelles Design gebunden, das zu möglichen Interaktionen führt, die nicht Teil des domänenspezifischen Verhaltens dieser Komponente sind. Diese technische Verschuldung sollte so weit wie möglich vermieden werden.

In einfachen Worten formuliert; es werden allgemeine Methoden der Implementierung des HorizontalLayout nach außen sichtbar gemacht. Wenn jemand genau diese Methoden verwendet und das übergeordnete Element später zu einem VerticalLayout wird, kann der Quellcode nicht ohne weitere Korrekturen kompiliert werden.

Vererbung – Zweite Version

Die benutzerdefinierte Komponente muss in die bereits vorhandene Komponentenhierarchie passen. Innerhalb der Vererbung muss ein Platz gefunden werden, von dem aus man beginnen kann. Andernfalls kann die benutzerdefinierte Komponente nicht verwendet werden. Gleichzeitig möchten wir jedoch keine spezifischen Implementierungsdetails besitzen und auch nicht die Aufwände haben, grundlegende technische Anforderungen basierend auf den Framework-Anforderungen zu implementieren. Der Punkt, von dem aus die Vererbung beginnt, muss mit Bedacht verwendet werden.

Nehmen wir einmal an, dass die Klasse AbstractComponent das ist, wonach wir als Ausgangspunkt suchen.

Wenn Du deine Klasse daraus ableiten wirst, hast Du sicherlich die wesentlichen Funktionen, die Du als Benutzer des Frameworks haben möchtest. Diese Abstraktion ist jedoch meist damit verbunden, dass auch frameworkspezifische Dinge zu berücksichtigen sind. Diese abstrakte Klasse ist ein intern verwendetes, grundlegendes Element. Das Verwenden dieser internen abstrakten Klasse führt sehr wahrscheinlich dazu, dass interne und technische Methoden implementiert werden müssen. Als Beispiel wurde hier die Methodensignatur mit dem Namen doFrameworkSpecificThings () nur mit einer Protokollnachricht erstellt und implementiert.

Im Einsatz ist eine solche Komponente bereits etwas weniger gefährlich.

Mit dieser Lösung bin ich aber noch nicht zufrieden. Sehr oft sind auf technischer Seite keine neuen Basis-Komponenten erforderlich. Stattdessen handelt es sich um Kompositionen bereits vorhandener wesentlicher Elemente, die in einem professionellen, domänenspezifischen Kontext komponiert werden.

Komposition – Mein Favorit

Was können wir an dieser Stelle tun? Das Schöne an dieser Lösung ist, dass damit bereits vorhandene Komponenten, die durch Vererbung generiert wurden, mit einem Wrapper versehen können. Eine Lösung könnte darin bestehen, eine Klasse mit dem Namen Composite vom Typ T zu erstellen. Composite <T erweitert AbstractComponent>

Diese Klasse dient als Umschlag für die Zusammensetzung der erforderlichen Komponenten. Diese Klasse kann dann sogar selbst von der Schnittstellenkomponente erben, sodass diese technischen Methoden der abstrakten Implementierung nicht wiederholt oder nach außen freigegeben werden. Der Typ T selbst ist der Typ, der als externe Komponente in der Zusammensetzung verwendet werden soll. In unserem Fall ist es das horizontale Layout. Mit der Methode getComponent () kann bei Bedarf auf diese Instanz zugegriffen werden.

So gesehen handelt es sich um eine neutrale Hülle, die sich jedoch nach außen hin als minimale Komponente verhält, da der Mindestvertrag über die Komponentenschnittstelle eingehalten ist. Auch hier werden nur die fachlichen Methoden durch Delegation nach außen sichtbar gemacht, die dafür ausdrücklich vorgesehen sind. Die Verwendung ist daher reduziert auf den UseCase.

Fazit

An diesem Punkt haben wir gesehen, wie eine robustere Variante einer Komposition durch das Delegierung und nicht durch Vererbung erzielt werden. Du kannst dieses auch verwenden, wenn Du in älteren Quellcodes mit diesem Anti-Pattern konfrontiert wirst. Es ist nicht immer möglich, alles zu bereinigen oder bis ins letzte Detail zu ändern. Ich hoffe jedoch, dass dies einen Anreiz gegeben hat, sich damit auseinanderzusetzen.


Wenn Du mehr zu dem Thema DevSecOps oder Java und Kotlin erfahren möchtest, werfe doch einen Blick auf meinen Youtube Kanal. youtube.com/svenruppert. Ich würde mich freuen Dich als meinen neuen Abonnenten begrüßen zu dürfen.

 

Cheers Sven Ruppert

Sven Ruppert

Sven Ruppert entwickelt seit 1996 in Java an Industrieprojekten. Er war über 15 Jahre als Berater weltweit in Branchen wie Automobil, Raumfahrt, Versicherungen, Bankwesen, UNO und WorldBank tätig.

Sven ist Groundbreaker Ambassador (ehem. Oracle Developer Champion) und arbeitet als Developer-Advocate für JFrog. Er spricht regelmäßig auf Konferenzen weltweit und schreibt für IT-Zeitschriften sowie Tech-Portalen.

Neben seinem Hauptthema DevSecOps und den Evergreen-Themen Core-Java und Kotlin arbeitet er an Mutationstests von Web-Apps und Distributed UnitTesting.

Sven Ruppert


Sven Ruppert has been coding Java since 1996 in industrial projects, is working as Developer Advocate for JFrog and Groundbreaker Ambassador (former Oracle Developer Champion). He is regularly speaking at Conferences worldwide and contributes to IT periodicals, as well as tech portals. He was working over 15 years as a consultant worldwide in industries like Automotive, Space, Insurance, Banking, UN and WorldBank. Additional to his main topic DevSecOps he is working on Mutation Testing of Web apps and Distributed UnitTesting besides his evergreen topics Core Java and Kotlin.

Leave a Reply