Sonntag, 11. August 2013

Das Factory Pattern – Glaube und Wirklichkeit – Teil 1

Praktisches Beispiel 2.1.1

Artikelübersicht
1. Teil Das Factory Pattern – Glaube und Wirklichkeit – Teil 1
2. Teil Das Factory Pattern – Glaube und Wirklichkeit – Teil 2.
3. Teil Das Factory Pattern – Glaube und Wirklichkeit – eine konkrete Nachlese.


Ein Entwicklungsteam, welches auf der Höhe der Zeit ist, verwendet Entwurfsmuster (Design Pattern). Programmierer stehen oft vor wiederkehrenden Problemen, für die schon lange exzellente Lösungen gefunden wurden. Diese Lösungen wurden in Lösungsschablonen niedergeschrieben und allen Programmierern zum Abschreiben freigegeben. Man muss das Rad also nicht zweimal erfinden. Trotz der durchklingenden, unbestrittenen Vorteile, kann der Gebrauch von Entwurfsmustern sehr problematisch sein. Weil man Entwurfsmuster anwenden soll, wendet man sie überall an, auch da, wo sie nicht hingehören. Weil man sie nicht richtig verstanden hat, wendet man sie falsch an. Einige haben sie verstanden und andere nicht. Die Anderen nimmt man auf dem Weg zum guten Code nicht mit und stellt sie in einer dunklen Ecke ab. Am Ende führt dieses teamfeindliche Verhalten zum Stillstand und reißt die Produktivität und die Laune in den Keller.

In einem Team sollten die verwendeten Technologien verstanden worden sein. Da genügt nicht das einfache Abfragen: Kannst Du es? Wer gibt schon gerne zu: Ich kann es nicht! Doch das wäre notwendig. Deshalb ist die Vermittlung von Technologien und die Herstellung einer gemeinsamen Wissensbasis lebensnotwendig für Effektivität und Effizienz. Die Erläuterung der Factory-Entwurfsmuster ist in diesem Beitrag nur ein Beispiel, um das Ganze praktisch zu erläutern und weil wir die verschiedenen Typen von Factory Entwurfsmustern kürzlich wirklich so diskutiert haben.

Bei der Beschäftigung mit einem ganz anderen Gebiet (Softwaremodularisierung), stieß ich beim Lesen des Buches "Software modular bauen" [FIELD12] auf die Beschreibung des Factory Patterns. Diese Erklärung wollte ich im Teammeeting nutzen, in dem wir uns über den Inhalt des entsprechenden Entwurfsmuster verständigen wollten. Dabei wurde über eine statische Methode ein Objekt instanziiert und zurückgegeben.

NewObject newObject = 
          MyNewObjectFactory.getInstance(objectKinds.getType());


Dabei ist NewObject ein Interface, welches von mehreren konkreten Klassen implementiert wird. Diese werden in der statischen Methode instanziiert.

public static NewObject getInstance(String type) {
 if(type.equals(NewObjectTypes.A) {
  return new A();
 }
 if(type.equals(NewObjectTypes.B) {
  return new B();
 }
}


Um die Cyclomatic Complexity bei künftigen Erweiterungen nicht ins Uferlose steigen zu lassen, wurde dann eine Decision Map verwendet.

public class MyNewObjectFactory {
 private static Map<String, NewObject> decisionMap 
   = new HashMap<String, NewObject>();
 public static final String A = "a";
 public static final String B = "b";
 static {
  decisionMap.put(MyNewObjectFactory.A, new A());
  decisionMap.put(MyNewObjectFactory.B, new B());
 }
 private MyNewObjectFactory() { }
 public static NewObject getInstance(String type) {
  if(!decisionMap.containsKey(type)) {
   throw new IllegalArgumentException();
  }
  return decisionMap.get(type);
 }
}


Übersehen hatte ich eine Kleinigkeit, weil ich bis dahin in dem Glauben schwebte, ich hätte das Factory Pattern verwendet. Allerdings ist diese einfache Factory nur eine statische Factory-Methode. Es ist nicht das Factory Method Pattern der GoF. Es gab übrigens noch andere, die dieser Irrlehre anhingen. Doch beim genauerem gemeinsamen Studium mussten wir uns eines Besseren belehren lassen. Am Ende hatten wir alle das gleiche Verständnis. Und das war unser wichtigstes Ergebnis. Wenn zwei der Meinung sind, Rechts ist Links, werden sie bei Richtungsangaben immer in dieselbe Richtung fahren. Es müssen aber alle Links und Rechts umdefinieren, damit das funktioniert. Natürlich plädiere ich hier für ein gemeinsames Verständnis, dass auch im größeren Rahmen anwendbar ist, und dann sollte Rechts Rechts bleiben und Links Links.

Jetzt aber das Factory Methode Pattern der GoF.


In unserem Beispiel ist das NewObject das Product und A und B die ConcreteProducts. Als Creator erstellen wir ein Interface NewObjectCreator und leiten davon ACreator und BCreator ab. Über das Interface Creator definiere ich die Methode getInstance und die konkreten Creator ACreator und BCreator sind so gezwungen, diese zu implementieren.

public class ACreator implements Creator {
 public NewObject getInstance() {
  return new A();
 }
}


Wenn wir jetzt die Decision Map ändern, haben wir das GoF Factory Method Pattern und die Static Factory Method miteinander verbunden.

public class MyNewObjectFactory {
 private static Map<String, NewObject> decisionMap 
  = new HashMap<String, NewObject>();
 public static final String A = "a";
 public static final String B = "b";
 static {
  decisionMap.put(MyNewObjectFactory.A, new ACreator());
  decisionMap.put(MyNewObjectFactory.B, new BCreator());
 }
 private MyNewObjectFactory() { }
 public static NewObject getInstance(String type) {
  if(!decisionMap.containsKey(type)) {
   throw new IllegalArgumentException();
  }
  return decisionMap.get(type).getInstance();
 }
}


In [FILDE12] erfolgte dieser Schritt im Stillen, wahrscheinlich in der Annahme, dass jeder ihn implizit mitgedacht hat. Leider kann man das nicht voraussetzen. Eine wirkliche Erläuterung und Diskussion stellt in einem Team erst die Sicherheit her, dass mit den genannten Technologien auch wirklich gearbeitet wird. Im zweiten Post dieser Reihe werde ich auf die Diskussion eingehen, in der das Abstract Factory Pattern diskutiert wurde.

  • [FIELD12]: Ulf Fieldebrandt: "Software modular bauen", 1. Auflage, dpunkt.verlag GmbH, Heidelberg, 2012


folgender Post dieses Themas


Print Friendly Version of this page Print Get a PDF version of this webpage PDF

Kommentare:

  1. Ich habe mal einen schönen Beitrag über Weasel Words gelesen. Es ist eigentlich dafür gedacht, schlecht benannte Klassen mit dem Suffix Weasel zu kennzeichnen (dass es eigentlich umbenannt werden müsste), aber ich denke, wenn man eine Klasse nach einem Pattern benennt und dann dieses Pattern nicht implementiert, könnte man es auch:

    MyNewObjectFactoryWeasel

    nennen.

    Mein erster Reflex wäre gewesen MyNewObjectFactory einfach in MyNewObjectProvider umzubennen, so dass kein Pattern im Klassenname referenziert wird. Aber Provider ist ja quasi auch schon wieder ein Weasel-Word.

    AntwortenLöschen
  2. Die Diskussion um Namen ist eine sehr wichtige. Bei meinen Namen bin ich immer auf die Hilfe meiner Kollegen angewiesen :-) So hoffe ich hier, Ihr habt einen guten Namen für so eine Klasse. Die hieß im Buch übrigens auch Factory. Das NewObject kommt von mir, auch A und B. Das sind alles nur Platzhalter für wirkliche Objektnamen. Außerdem ist mir noch ein großes Malheur passiert. Ich schrieb Map und nicht Map<String, NewObject>. So konntet Ihr die Typisierung der Map nicht mehr sehen Entschuldigung.

    AntwortenLöschen
  3. Und noch was GAAAANZ Wichtiges!!! Es heißt "decision"! ;)

    AntwortenLöschen
  4. Wichtig finde ich auch, dass i.d.R. das Instantiieren der Creator-Objekte "preiswerter" als das der Product-Objekte ist. Und je Produkt nur ein spezifisches Creator-, jedoch n Product-Objekte benötigt werden.

    Mit der oben skizzierten zweiten "richtigen" Implementierung kann das Instantiieren der Produkt-Objekte so lange verzögert werden (lazy), bis diese tatsächlich benötigt werden (im Gegensatz zum ersten Ansatz, in dem alle(!) Produkte bereits instantiiert in der DecisionMap vorliegen - ob diese jemals benötigt werden oder nicht).

    just my 2 cents.

    CU
    Boeffi

    Wäre eine "final"-Deklaration für die Map und den formalen getInstance-Parameter vielleicht sinnvoll?

    AntwortenLöschen
  5. Immer noch gtraue Schrift auf grauem Untergrund ... ihr Leben ist wohl sehr farblos?

    AntwortenLöschen
  6. Es gibt inzwischen eine Umfrage zur Schrift- und Hintergrundfarbe. Beteiligen Sie sich und vielleicht gewinnt Schwarz gegen Weiß!? ;-)

    AntwortenLöschen
  7. Eine Falle lauert in der ersten Variante der MyNewObjectFactory: dort wird bei getInstance() nicht ein neues Objekt zurückgegeben, sondern (abhängig von type) immer dasselbe (was im statischen Bereich konstruierte wurde). Das ist semantisch anders als das Codebeispiel davor und die zweite Variante der MyNewObjectFactory, und könnte leicht zu Fehlern führen.

    Ich bin nicht sicher, ob das Factory Pattern vorsieht, dass tatsächlich immer neue Objekte erzeugt werden; die Beispiele, die ich dazu bei einer kurzen Suche gefunden habe, legen das aber nahe (dort heißt es dann auch eher "newInstance()" statt "getInstance()").
    Dagegen darf eine "Factory Method" durchaus beides: eine neue oder eine "gebrauchte" Instanz zurückgeben. Neben dem Kapseln von Objektkonstruktion und dem Verbergen von Klassenhierarchien ist das ein weiterer wichtiger Grund für deren Einsatz.

    AntwortenLöschen
  8. +1

    > "Factory Method" durchaus beides:
    > eine neue oder eine "gebrauchte" Instanz zurückgeben.
    > Neben dem Kapseln von Objektkonstruktion und
    > dem Verbergen von Klassenhierarchien ist das
    > ein weiterer wichtiger Grund für deren Einsatz.

    AntwortenLöschen
  9. Wer benutzt schon noch das Factory Pattern? Ist ein Pattern aus dem vorigen Jahrtausend und macht den Code schlecht testbar...

    AntwortenLöschen
  10. Warum Strings als Argument? eine Funktion von Interface nach Implementation kann ich ja noch nachvollziehen aber Strings? Das Interface muss dem Nutzer der Factory ja eh bekannt sein.

    AntwortenLöschen