Das Fliegengewicht wird eingesetzt, wenn eine große Anzahl gleichartiger Objekte benötigt wird, die variable Informationen teilen. Eine klassische Implementierung würde unverhältnismäßig viel Speicherkapazität erfordern[1] und zu Problemen während der Laufzeit führen. Ein Teil des Zustands dieser Objekte kann in den Kontext ausgelagert werden, und zwar auf eine extrinsische Art, von außen her angeregt. Nach der Entfernung des Zustands reduziert sich die Anzahl verschiedener Objekte auf ein überschaubares Maß.
Dieses Diagramm in der Unified Modeling Language (UML) zeigt die Beziehung von einem Kunden (Klient) über die „Fabrik“ bis zum Fliegengewicht.
Die einzelnen Elemente sind:
Der Klient (client) verwaltet Referenzen auf Fliegengewichte und den extrinsischen Zustand der Fliegengewichte.
Die Fliegengewicht-Fabrik (flyweight factory) erzeugt und verwaltet Fliegengewichte. Sie stellt die korrekte Benutzung der gemeinsam benutzten Objekte sicher.
Das Fliegengewicht ist eine Abstrakte Klasse und definiert die Schnittstelle für Objekte, die einen von außen sichtbaren Zustand empfangen und verarbeiten.
Das Konkrete-Fliegengewicht (concrete flyweight) implementiert die Fliegengewichtschnittstelle. Bei Bedarf wird ein innerer Zustand ergänzt. Exemplare von KonkretesFliegengewicht oder abgeleiteten Klassen werden gemeinsam genutzt. Der intrinsische Zustand muss unabhängig vom Kontext sein.
Das Getrennt-Genutzte-Konkrete-Fliegengewicht (unshared concrete flyweight) implementiert diese Schnittstelle ebenfalls, enthält allerdings den kompletten Zustand. Das bedeutet, dass diese Objekte nicht gemeinsam genutzt werden. Hierbei handelt es sich nicht mehr im engeren Sinne um Fliegengewichte. Es können sich sogar echte „Schwergewichte“ dahinter verbergen. Es zeigt vielmehr die Stelle, an der „normale“ Objekte ihren Platz in dem Muster finden.
Vor- und Nachteile
Das Verfahren reduziert Speicherkosten proportional zur Größe des ausgelagerten Zustands und zur Anzahl der Fliegengewichte. Die Speicherkosten sinken weiter, wenn der ausgelagerte Zustand nicht gespeichert werden muss, sondern berechnet werden kann.
Anderseits steigt die Komplexität relativ stark,[2] insbesondere bei Designs, die Fliegengewicht gemeinsam mit Kompositum nutzen. Eine saubere Dokumentation der Verantwortlichkeiten ist ein Muss. Die Laufzeitkosten steigen an, da der ausgelagerte Zustand wieder aufgefunden und dem Fliegengewicht beim Methodenaufruf übergeben werden muss. Sie steigen weiter, wenn der Zustand berechnet wird.
Beispiele
Ein Beispiel ist die grafische Darstellung eines Textdokumentes, das leicht aus Hunderttausenden oder gar Millionen von Zeichen und damit Zeichenobjekten bestehen kann. Jedes Byte im Zeichenobjekt wird unter Umständen zu einem Megabyte. Es ist inakzeptabel, alle Informationen, die das Zeichenobjekt benötigt, wirklich im Objekt zu speichern.
Das Zeichenobjekt befindet sich in einem Zeilenobjekt (Kompositum). Die Zeilennummer und die Y-Koordinate auf dem Bildschirm sind für alle Zeichen der Zeile identisch. Sie werden in das Zeilenobjekt verlagert. Die Spaltennummer und die X-Koordinate ergeben sich aus der Position in der Zeile. Das Zeilenobjekt ist verantwortlich, diese zu berechnen. Schriftattribute sind meist für benachbarte Zeichen identisch. Sie werden ebenfalls ausgelagert.
Übrig bleibt alleine der Code des Zeichens. Somit gibt es am Ende lediglich einige hundert unterschiedlicher Zeichenobjekte, zumindest bei Alphabetschriften.
Der typische Code in der Programmiersprache Java für die Abrechnung an einer Kaffeebar enthält folgende Elemente:
// Flyweight object interfacepublicinterfaceCoffeeOrder{voidserveCoffee(CoffeeOrderContextcontext);}// ConcreteFlyweight object that creates ConcreteFlyweightpublicclassCoffeeFlavorimplementsCoffeeOrder{privateStringflavor;publicCoffeeFlavor(StringnewFlavor){this.flavor=newFlavor;}publicStringgetFlavor(){returnthis.flavor;}publicvoidserveCoffee(CoffeeOrderContextcontext){System.out.println("Serving Coffee flavor "+flavor+" to table number "+context.getTable());}}publicclassCoffeeOrderContext{privateinttableNumber;publicCoffeeOrderContext(inttableNumber){this.tableNumber=tableNumber;}publicintgetTable(){returnthis.tableNumber;}}importjava.util.HashMap;importjava.util.Map;//FlyweightFactory objectpublicclassCoffeeFlavorFactory{privateMap<String,CoffeeFlavor>flavors=newHashMap<String,CoffeeFlavor>();publicCoffeeFlavorgetCoffeeFlavor(StringflavorName){CoffeeFlavorflavor=flavors.get(flavorName);if(flavor==null){flavor=newCoffeeFlavor(flavorName);flavors.put(flavorName,flavor);}returnflavor;}publicintgetTotalCoffeeFlavorsMade(){returnflavors.size();}}publicclassTestFlyweight{/** The flavors ordered. */privatestaticCoffeeFlavor[]flavors=newCoffeeFlavor[100];/** The tables for the orders. */privatestaticCoffeeOrderContext[]tables=newCoffeeOrderContext[100];privatestaticintordersMade=0;privatestaticCoffeeFlavorFactoryflavorFactory;publicstaticvoidtakeOrders(StringflavorIn,inttable){flavors[ordersMade]=flavorFactory.getCoffeeFlavor(flavorIn);tables[ordersMade++]=newCoffeeOrderContext(table);}publicstaticvoidmain(String[]args){flavorFactory=newCoffeeFlavorFactory();/** Durch Zwischenspeicherung der Geschmäcker in einer HashMap in der Factory wird jeweils nur ein Objekt des gleichen Geschmacks erzeugt und damit Speicherplatz gespart. */takeOrders("Cappuccino",2);takeOrders("Cappuccino",2);takeOrders("Frappe",1);takeOrders("Frappe",1);takeOrders("Xpresso",1);takeOrders("Frappe",897);takeOrders("Cappuccino",97);takeOrders("Cappuccino",97);takeOrders("Frappe",3);takeOrders("Xpresso",3);takeOrders("Cappuccino",3);takeOrders("Xpresso",96);takeOrders("Frappe",552);takeOrders("Cappuccino",121);takeOrders("Xpresso",121);for(inti=0;i<ordersMade;++i){flavors[i].serveCoffee(tables[i]);}System.out.println(" ");System.out.println("total CoffeeFlavor objects made: "+flavorFactory.getTotalCoffeeFlavorsMade());}}
Anwendung in der Analyse
Das Fliegengewicht ist ein reines Design-Muster, da seine Anwendung vor allem vom Designaspekt Speicherplatz getrieben wird, daneben auch vom Aspekt zentraler Update eines sehr globalen Zustands. Die Verwendung von Fliegengewicht in der Analyse ist in aller Regel ein Code-Smell (deutsch ‚schlechter Geruch‘), der eine Überarbeitung des schlecht strukturierten Programm-Quelltextes nahelegt.
Das Kompositum bietet sich an, um Fliegengewichte zu hierarchischen Strukturen zusammenzufügen, z. B. Zeichen, Zeile, Absatz. Eine Fabrikmethode wird benötigt, um die Fliegengewichte zu erzeugen.
Auch für die Zustands- und
Strategie-Objekte ist das Fliegengewichtsmuster vorteilhaft. Das Idiom „immutable object“ ist eng verwandt mit dem Fliegengewicht. Fliegengewichte sollten immer als „immutable objects“ designt werden.