Flyweight Pattern: Objekt-Instanzen auf Halde. Und: WeakReference Sample. Rev.1

Diesen Blogpost hatte ich gestern Nacht schon eingestellt, ihn heute morgen aber zwischenzeitlich wieder entfernt, weil sich ein Fehler eingeschlichen hatte. Hier ist die revidierte Version.

Weihnachten ist vorbei; endlich hatte ich mal wieder Zeit für eines meiner Steckenpferde: Design Patterns.
Heute war das Flyweight Pattern dran. Dafür habe ich mir einen Code von DoFactory (Gof) vorgeknöpft.

   1: // Flyweight pattern -- Real World example
   2:  
   3: using System;
   4: using System.Collections.Generic;
   5:  
   6: namespace DoFactory.GangOfFour.Flyweight.RealWorld
   7: {
   8:    /// <summary>
   9:    /// MainApp startup class for Real-World
  10:    /// Flyweight Design Pattern.
  11:    /// </summary>
  12:    internal class MainApp
  13:    {
  14:       /// <summary>
  15:       /// Entry point into console application.
  16:       /// </summary>
  17:       private static void Main()
  18:       {
  19:          // Build a document with text
  20:          string document = "AAZZBBZB";
  21:          char[] chars = document.ToCharArray();
  22:          CharacterFactory factory = new CharacterFactory();
  23:          // extrinsic state
  24:          int pointSize = 10;
  25:          // For each character use a flyweight object
  26:          foreach (char c in chars)
  27:          {
  28:             pointSize++;
  29:             Character character = factory.GetCharacter(c);
  30:             character.Display(pointSize);
  31:          }
  32:  
  33:          // Wait for user
  34:          Console.ReadKey();
  35:       }
  36:    }
  37:  
  38:  
  39:    /// <summary>
  40:    /// The 'FlyweightFactory' class
  41:    /// </summary>
  42:    internal class CharacterFactory
  43:    {
  44:       private Dictionary<char, Character> _characters =
  45:          new Dictionary<char, Character>();
  46:  
  47:       public Character GetCharacter(char key)
  48:       {
  49:          // Uses "lazy initialization"
  50:          Character character = null;
  51:          if (_characters.ContainsKey(key))
  52:          {
  53:             character = _characters[key];
  54:          }
  55:          else
  56:          {
  57:             switch (key)
  58:             {
  59:                case 'A':
  60:                   character = new CharacterA();
  61:                   break;
  62:                case 'B':
  63:                   character = new CharacterB();
  64:                   break;
  65:  
  66:                   //...
  67:  
  68:                case 'Z':
  69:                   character = new CharacterZ();
  70:                   break;
  71:             }
  72:             _characters.Add(key, character);
  73:          }
  74:          return character;
  75:       }
  76:    }
  77:  
  78:    /// <summary>
  79:    /// The 'Flyweight' abstract class
  80:    /// </summary>
  81:    internal abstract class Character
  82:    {
  83:       protected char symbol;
  84:       protected int width;
  85:       protected int height;
  86:       protected int ascent;
  87:       protected int descent;
  88:       protected int pointSize;
  89:  
  90:       public abstract void Display(int pointSize);
  91:    }
  92:    
  93:    /// <summary>
  94:    /// A 'ConcreteFlyweight' class
  95:    /// </summary>
  96:    internal class CharacterA : Character
  97:    {
  98:       // Constructor
  99:       public CharacterA()
 100:       {
 101:          this.symbol = 'A';
 102:          this.height = 100;
 103:          this.width = 120;
 104:          this.ascent = 70;
 105:          this.descent = 0;
 106:       }
 107:  
 108:  
 109:       public override void Display(int pointSize)
 110:       {
 111:          this.pointSize = pointSize;
 112:          Console.WriteLine(this.symbol +
 113:                            " (pointsize " + this.pointSize + ")");
 114:       }
 115:    }
 116:  
 117:    /// <summary>
 118:    /// A 'ConcreteFlyweight' class
 119:    /// </summary>
 120:    internal class CharacterB : Character
 121:    {
 122:       // Constructor
 123:       public CharacterB()
 124:       {
 125:          this.symbol = 'B';
 126:          this.height = 100;
 127:          this.width = 140;
 128:          this.ascent = 72;
 129:          this.descent = 0;
 130:       }
 131:  
 132:  
 133:       public override void Display(int pointSize)
 134:       {
 135:          this.pointSize = pointSize;
 136:          Console.WriteLine(this.symbol +
 137:                            " (pointsize " + this.pointSize + ")");
 138:       }
 139:    }
 140:  
 141:    // ... C, D, E, etc.
 142:  
 143:    /// <summary>
 144:    /// A 'ConcreteFlyweight' class
 145:    /// </summary>
 146:    internal class CharacterZ : Character
 147:    {
 148:       // Constructor
 149:  
 150:       public CharacterZ()
 151:       {
 152:          this.symbol = 'Z';
 153:          this.height = 100;
 154:          this.width = 100;
 155:          this.ascent = 68;
 156:          this.descent = 0;
 157:       }
 158:  
 159:  
 160:       public override void Display(int pointSize)
 161:       {
 162:          this.pointSize = pointSize;
 163:          Console.WriteLine(this.symbol +
 164:                            " (pointsize " + this.pointSize + ")");
 165:       }
 166:    }
 167: }

Was hier passiert, lässt sich in ein paar kurzen Sätzen verallgemeinern:
Eine Applikation macht wiederholten Gebrauch von vielen Objekten.

  • Ein Container für Objekte wird angelegt (hier ein Dictionary) (Zeilen 44-45).
  • Wird ein Objekt benötigt, wird erst im Container nachgeschaut ob es dort schon vorhanden ist (Zeile 51).
  • Per Lazy Initialization wird ein Objekt erst dann instanziiert wenn es gebraucht wird (Zeilen 55-71).
  • Diese Instanz wird in den Container gepackt (Zeile 72).
  • Wird das gleiche Objekt noch einmal benötigt, kann es aus dem Container geholt werden (Zeile 53).

Tolle Sache – wirklich? Objekte werden für den schnellen und wiederholten Zugriff auf Halde gelegt. Die andere Seite der Medaille ist ein Überhang von eventuell nicht mehr benötigten Objekten. Die liegen da herum und verplempern Speicherplatz. Natürlich denke ich dabei nicht an dieses Demo, was pro Objekt nur ein paar Bytes verbraucht. Ein denkbares Einsatzgebiet für das Flyweight Pattern wäre z.B. eine Komponentenarchitektur, in der schon etwas größere und/oder sehr viele Datenmengen hin und her geschoben werden.
Man muß also abwägen was wichtiger ist: Zeitersparnis oder Ressourcenschonung.

Es gibt aber auch einen Mittelweg. Wie wäre es, wenn wir den Code etwas aufbohren und mit einer WeakReference versehen? Die WeakReference-Klasse im .net Framework ist ein intelligenter Container, in dem das eigentliche Objekt gekapselt wird. Der Code ist schnell geändert (bei der Gelegenheit habe ich auch gleich Refaktorisiert):

   1: public Character GetCharacter(char key)
   2:       {
   3:          // Uses "lazy initialization"
   4:          Character character;
   5:          if(characters.ContainsKey(key))
   6:          {
   7:             // Wenn WeakReference das Objekt disposed hat, existiert der key weiterhin!
   8:             CheckForDisposedObjektAndRecreateIfNecessary(key);
   9:             character = characters[key].Target as Character;
  10:          }
  11:          else
  12:          {
  13:             character = CreateCharacter(key);
  14:             AddCharacterToDictionary(key, character);
  15:          }
  16:          return character;
  17:       }
  18:  
  19:       private void CheckForDisposedObjektAndRecreateIfNecessary(char key)
  20:       {
  21:          if(characters[key].IsAlive)
  22:             return;
  23:          var character = CreateCharacter(key);
  24:          characters[key] = new WeakReference(character, false);
  25:       }
  26:  
  27:       private static Character CreateCharacter(char key)
  28:       {
  29:          Character character = default(Character);
  30:          switch(key)
  31:          {
  32:             case 'A':
  33:                character = new CharacterA();
  34:                Console.WriteLine("A created");
  35:                break;
  36:             case 'B':
  37:                character = new CharacterB();
  38:                Console.WriteLine("B created");
  39:                break;
  40:             //... 
  41:             case 'Z':
  42:                character = new CharacterZ();
  43:                Console.WriteLine("Z created");
  44:                break;
  45:          }
  46:          return character;
  47:       }
  48:  
  49:       private void AddCharacterToDictionary(char key, Character character)
  50:       {
  51:          characters.Add(key, new WeakReference(character, false));
  52:       }

Wenn man das Demo laufen lässt, wird man keinen Unterschied feststellen. Das liegt an der internen Arbeitslogik der WeakReference-Klasse. Erst wenn ein bestimmtes Minimum an Speicher verbraucht wird, werden Objekte zerstört. Dieses Minimum wird hier nicht erreicht.
Das Demo zeigt, wie einfach eine WeakReference in bereits vorhandenen Code eingestrickt werden kann. Man muß dabei allerdings beachten daß nur das Objekt zerstört wird (Value), aber nicht der Key. Das macht eine weitere Prüfung erforderlich (siehe Code).

Eine weitere und wahrscheinlich auch bessere Lösung wäre, statt dem Flyweight Pattern eine intelligente Cache-Komponente einzusetzen, die je nach Anforderung konfigurierbar ist (Expiration Time, Speicherüberwachung usw). Das aber ist ein Thema für einen anderen Blog-Post.

DotNetKicks-DE Image
Published Montag, 28. Dezember 2009 12:17 von Rainer Hilmer

Kommentare

Keine Kommentare

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)