So leicht wie eine Fliege

Hallo Spaß-Coder.

Weiter geht es mit unserer Reihe über Entwurfsmuster, heute mit einem weiteren Muster aus der Kategorie der Strukturmuster. Wir schauen uns in diesem Artikel den Fliegengewicht (engl. Flyweight) an, der auch aus der Sammlung der Gang of Four stammt.

Da fliege ich voll drauf

Das Entwurfsmuster Fliegengewicht ist im heutigen Alltag weiter verbreitet, als möglicherweise viele vermuten. Bei diesem Muster geht es darum, ein Objekt mit hohem Ressourcenbedarf in zwei Teile zu gliedern. Ein Teil enthält dabei unveränderliche Daten, welche von allen konkreten Ausprägungen des zweiten Teils genutzt werden. Der zweite Teil enthält die spezifischen veränderlichen Daten eines konkreten Kontextes.

Wie funktioniert das Fliegengewicht?

Stellen wir uns eine Webseite vor, auf der viele Bilder angezeigt werden und dabei auch einige Bilder mehrfach dargestellt werden. Wenn ein Bild – sagen wir – 5 mal auf der Seite angezeigt wird, wird es dieses Bild dann 5 mal runter geladen und im Speicher vorrätig gehalten? Nein, das läuft hier viel leichter. Jedes Bild, egal wie oft auf der Webseite dargestellt, wird nur einmal von der Quelle heruntergeladen und ist nur einmal im Speicher. Dies sind die unveränderlichen Daten. Veränderlich hingegen ist die Position, an der das Bild angezeigt wird.

Ein Beispiel zur Verdeutlichung

Schauen wir uns wie immer eine Beispielimplementierung an. Zunächst ohne das Fliegengewicht Muster und anschließend wenden wir dieses Muster an und prüfen die daraus resultierende Veränderung. Messen werden wir die Veränderung anhand des Speicherbedarfs der Testanwendung.

Ein Schwergewicht

Erstellen wir eine Klasse für ein Bild mit Positionsangaben anhand von X- und Y-Koordinaten.

Bei diesen POJOs oder Entitäten bietet sich wieder einmal die Verwendung von Projekt Lombok zur Generierung der üblichen Methoden an. Mehr dazu findet ihr im Artikel http://invidit.de/blog/lombok-macht-das-schon/.

Das Bild muss von der Platte geladen werden womit sich die Erstellung von Objekten der Klasse PositionedImage aufwendiger gestaltet. Aus diesen Grund bauen wir eine Fabrik, welche uns fertige Objekte baut.

Wir übergeben den Pfad zur Bilddatei und die beiden Koordinaten für die Positionierung. Das Bild wird geladen und das fertige Objekt zurückgegeben. Konnte das Bild nicht geladen werden, bleibt es Null, was in diesem Beispiel völlig in Ordnung ist.

Zur Überprüfung der Speichernutzung benötigen wir noch einen Berichterstatter. Dieser sieht wie folgt aus:

Dieser bekommt eine Methode, welche einen String erwartet und gibt den aktuellen Speicherbedarf dort aus. Der Bedarf wird anhand der Runtime ermittelt und in MB umgerechnet.

In einem Testprogramm für die Kommandozeile stecken wir alle Einzelteile zusammen und laden 100 Bilder in eine Liste. Vorher und nachher geben wir den Speicherbedarf auf die Konsole aus.

So weit zum Schwergewicht.

Und jetzt mit Fliegengewicht

Zu Beginn trennen wir den Kontext vom Bild, welches in unserem Beispiel immer gleich bleibt. Dazu erstellen wir die Klasse ImagePosition.

Das zukünftige Fliegenwicht, also unser Bild, wird üblicherweise gegen eine abstrakte Klasse oder eine Schnittstelle implementiert und erhält den jeweiligen Kontext. Wir erstellen dazu eine Schnittstelle FlyweightImage.

Unser konkretes Fliegengewicht implementiert nun dieses Interface und enthält das Bild.

Auch in diesem Fall lassen wir eine Fabrik wieder die Arbeit zum Erstellen von Objekten der Klasse PositionedFlyweightImage übernehmen.

Jetzt könnte die Frage aufkommen „Warum heißt die Klasse denn PositionedFlyweightImageFactory? Es wird doch keine Position gesetzt!“. Genau, die Position wird nicht direkt gesetzt, kommt aber später als Kontext des Bildes dazu. Da die Methode print() der Klasse PositionedFlyweightImage eben genau diesen Kontext erwartet.

Die Besonderheit in dieser Fabrik ist eine HashMap mit bisher geladenen Bildern. Der Schlüssel ist dabei der vollständige Dateipfad. Somit wird gewährleistet, dass jedes Bild einmalig geladen wird und nur ein einziges Objekt mit diesem Bild im Speicher existiert.

Die Klasse für den Test des Fliegengewichts sieht sehr ähnlich zu der ersten Testklasse aus. Die Abweichung resultiert hierbei aus der Trennung der Position und des Bildes. Beide Informationen werden nun separat erzeugt und dann über die Methode print() kombiniert.

Und was bringt die Trennung nun tatsächlich?

Ergebnisauswertung

Lasen wir die beiden Main-Klassen laufen und ein paar Objekte erzeugen. Ohne Fliegengewicht sehen wir auf der Konsole diese Speichernutzung:

Wie ihr bei der Ausführung sicherlich bemerkt, dauert das Laden der 100 Bilder recht lange. Da nun 100 Bilder im Speicher gehalten werden ist auch die Speichernutzung entsprechend hoch.

Werfen wir einen Blick auf die Speichernutzung mit dem Fliegengewicht.

Nicht nur die Ausführung geht deutlich schneller, sondern auch der Speicherverbrauch ist signifikant geringer. Warum? Durch die Entkopplung von geladenem Bild (internem Zustand) und der Position (externer Kontext) muss das Bild nur einmalig geladen werden. Wir kombinieren dies dann zur Laufzeit mit den verschiedenen Positionen. Großartig!

Zusammenfassung

Wir haben uns das Entwurfsmuster Fliegengewicht angeschaut, mit dessen Hilfe der Ressourcenverbrauch von Software gesenkt werden kann. Die unveränderlichen Bestandteile (interner Zustand) wird von den veränderlichen Daten (externer Kontext) getrennt und kann dann wiederverwendet werden. Dazu werden beide Teile zur Laufzeit kombiniert.

Das Fliegengewicht Muster ist in gewisser Weise speziell. Wo seht ihr noch Möglichkeiten zum Einsatz dieses Musters?

Eure Spaß-Coder

Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:

Code-Beispiele auf Github:

  • https://github.com/invidit/CodeQuality.git

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert