Fliegengewicht (Entwurfsmuster)

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

Das Flyweight, auch Fliegengewicht genannt (engl. flyweight pattern), ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung und gehört zu der Kategorie der Strukturmuster (engl. structural patterns). Das Fliegengewicht ist ein Entwurfsmuster der so genannten GoF-Muster.

Verwendung[Bearbeiten]

Das Fliegengewicht wird verwendet, wenn eine große Anzahl von Objekten benötigt wird, die sich bestimmte variable Informationen teilen und eine herkömmliche Implementierung unverhältnismäßig viele Ressourcen erfordern[1] und alleine die Anzahl zu Problemen führen würde. Ein Teil des Zustandes dieser Objekte kann in den Kontext ausgelagert werden (extrinsisch). Nach der Entfernung des Zustandes reduziert sich die Anzahl verschiedener Objekte auf ein überschaubares Maß.

UML-Diagramm[Bearbeiten]

Klassendiagramm

Akteure[Bearbeiten]

Das Fliegengewicht ist abstrakt 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.

Die Fliegengewicht-Fabrik (flyweight factory) erzeugt und verwaltet Fliegengewichte. Sie stellt auch die korrekte Benutzung der gemeinsam benutzten Objekte sicher. Der Klient (client) verwaltet Referenzen auf Fliegengewichte und den extrinsischen Zustand der Fliegengewichte.

Vorteile[Bearbeiten]

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.

Nachteile[Bearbeiten]

Die Komplexität steigt relativ stark, insbesondere bei Designs, die Fliegengewicht gemeinsam mit Kompositum nutzen. Eine saubere Dokumentation der Verantwortlichkeiten ist ein Muss. Die Laufzeitkosten steigen möglicherweise an, da der ausgelagerte Zustand wieder aufgefunden und dem Fliegengewicht beim Methodenaufruf übergeben werden muss. Sie steigen weiter, wenn der Zustand berechnet wird.

Beispiel[Bearbeiten]

Die grafische Darstellung eines Textdokumentes.

Es kann leicht aus Hunderttausenden oder gar Millionen von Zeichen und damit Zeichenobjekten bestehen. Jedes Byte, das man im Zeichenobjekt speichert, wird so unter Umständen zu einem Megabyte. Es ist leicht ersichtlich, dass es inakzeptabel ist, 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).

Beispiel

Beispielcode in Java[Bearbeiten]

// Flyweight object interface
public interface CoffeeOrder {
    public void serveCoffee(CoffeeOrderContext context);
}
 
// ConcreteFlyweight object that creates ConcreteFlyweight 
public class CoffeeFlavor implements CoffeeOrder {
    private String flavor;
 
    public CoffeeFlavor(String newFlavor) {
        this.flavor = newFlavor;
    }
 
    public String getFlavor() {
        return this.flavor;
    }
 
    public void serveCoffee(CoffeeOrderContext context) {
        System.out.println("Serving Coffee flavor " + flavor + " to table number " + context.getTable());
    }
}
 
public class CoffeeOrderContext {
   private int tableNumber;
 
   public CoffeeOrderContext(int tableNumber) {
       this.tableNumber = tableNumber;
   }
 
   public int getTable() {
       return this.tableNumber;
   }
}
 
import java.util.HashMap;
import java.util.Map;
 
//FlyweightFactory object
public class CoffeeFlavorFactory {
    private Map<String, CoffeeFlavor> flavors = new HashMap<String, CoffeeFlavor>();
 
    public CoffeeFlavor getCoffeeFlavor(String flavorName) {
        CoffeeFlavor flavor = flavors.get(flavorName);
        if (flavor == null) {
            flavor = new CoffeeFlavor(flavorName);
            flavors.put(flavorName, flavor);
        }
        return flavor;
    }
 
    public int getTotalCoffeeFlavorsMade() {
        return flavors.size();
    }
}
 
public class TestFlyweight {
   /** The flavors ordered. */
   private static CoffeeFlavor[] flavors = new CoffeeFlavor[100];
   /** The tables for the orders. */
   private static CoffeeOrderContext[] tables = new CoffeeOrderContext[100];
   private static int ordersMade = 0;
   private static CoffeeFlavorFactory flavorFactory;
 
   public static void takeOrders(String flavorIn, int table) {
       flavors[ordersMade] = flavorFactory.getCoffeeFlavor(flavorIn);
       tables[ordersMade++] = new CoffeeOrderContext(table);
   }
 
   public static void main(String[] args) {
       flavorFactory = new CoffeeFlavorFactory();
      /** 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 (int i = 0; i < ordersMade; ++i) {
           flavors[i].serveCoffee(tables[i]);
       }
       System.out.println(" ");
       System.out.println("total CoffeeFlavor objects made: " +  flavorFactory.getTotalCoffeeFlavorsMade());
   }
}

Verwendung in der Analyse[Bearbeiten]

Fliegengewicht ist ein reines Design-Muster, da seine Anwendung vor allem vom Designaspekt Speicherplatz getrieben wird (daneben u. U. vom Aspekt zentraler Update eines sehr globalen Zustands). Die Verwendung von Fliegengewicht in der Analyse ist daher in aller Regel ein Code-Smell.

Verwandte Entwurfsmuster[Bearbeiten]

Das Kompositum bietet sich an, um Fliegengewichte zu hierarchischen Strukturen zusammenzufügen (z. B. Zeichen, Zeile, Absatz, etc.)

Eine Fabrikmethode wird benötigt, um die Fliegengewichte zu erzeugen.

Auch für 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.

Einzelnachweis[Bearbeiten]

  1.  Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. 5 Auflage. Addison-Wesley, 1996, ISBN 3-8273-1862-9, S. 223.