B) Abstract Factory

Eine Abstract Factory (deutsch: Abstrakte Fabrik, auch bekannt als Kit) ist ein objektbasiertes Erzeugermuster, welches Schnittstellen bereitstellt, um verwandte oder voneinander abhängige Objekte zu erzeugen, ohne ihre konkreten Klassen zu verwenden.

Abstract Factory

Dieses Pattern wird eingesetzt, wenn bestimmte Objekte in einem Programm ohne großen Aufwand ausgetauscht werden sollen. Es wird eine abstrakte Klasse (AbstrakteFabrik) erstellt, die ein oder mehrere weitere abstrakte Klassen (AbstraktesProdukt) erzeugen kann. Die Methoden zum Erzeugen der abstrakten Produkte bleiben dabei auch abstrakt, werden also nicht ausprogrammiert (siehe Kapitel 04.09 Abstrakte Klassen). Von diesen abstrakten Klassen erben anschließend konkrete Implementierungen der Fabrik (KonkreteFabrik1, KonkreteFabrik2, …) und der Produkte (Produkt1A, Produkt1B, Produkt2A, …).

Je nach Einsatzzweck können auch Interfaces anstelle abstrakter Klassen verwendet werden.

Die konkreten Fabriken überschreiben die abstrakten Methoden der abstrakten Fabrik zum Erzeugen von Produkten. Die erzeugten Produkte werden in der Methode als ein konkretes Produkt initialisiert, aber als abstraktes Produkt zurückgegeben. Z. B. könnte KonkreteFabrikA die Produkte Produkt1A und Produkt2A, welche von AbstraktesProdukt erben, erzeugen.

Der Klient, also die Klasse, die die Produkte der Fabrik benötigt, kennt nur die abstrakte Fabrik und die abstrakten Produkte. An einer einzigen Stelle im Programm wird die abstrakte Fabrik konkretisiert, indem sie als KonkreteFabrik initialisiert wird. Mit dieser Fabrik und den von ihr erzeugten Produkten wird der Rest des Programms aufgebaut. Sollen die Produkte (bzw. Objekte) ausgetauscht werden, ist es jetzt nur noch nötig an einer Stelle des Programms (bzw. zur Laufzeit) Code zu verändern: bei der Initialisierung der konkreten Fabrik.

Betrachten wir den Sachverhalt als Diagramm:

Abstract Factory Diagramm

Durch diese Implementierung ist es um ein vielfaches einfacher eine Gruppe verwandter oder ähnlicher Objekte auszutauschen. Hierzu genügt die Veränderung von einer einzigen Codezeile, nämlich der Austausch der konkreten Fabrik. Würden Sie dieses Pattern nicht einsetzen, sondern stattdessen in Ihrem Code direkt Objekte von Produkt1A und Produkt2A erzeugen, müssten Sie – wenn nicht mehr die Produktionsfamilie „A“ sondern „B“ verwendet werden soll – an jeder Stelle in Ihrem Code, an dem Produkte der A-Familie verwendet/erzeugt werden, diese durch Produkte der B-Familie (in diesem Diagramm Produkt1B und Produkt2B) ersetzen. Die abstrakten Produkte und Fabriken dienen also als Schnittstellen um ähnliche Produkte zu vereinheitlichen. Außerdem wird der Ansatz verfolgt: Programmiere gegen eine Schnittstelle, und nicht gegen eine konkrete Implementierung

Implementierung

Betrachten wir nun, wie wir das vorher abgebildete Diagramm in Java implementieren könnten. Um die Funktionalität zu testen, erweitern wir aber unsere beiden abstrakten Produkte jeweils um eine Methode. Zur Implementierung benötigen wir zuerst drei abstrakte Klassen: AbstrakeFabrik, AbstraktesProdukt1 und AbstraktesProdukt2. AbstrakteFabrik stellt zwei abstrakte Methoden zur Verfügung, die jeweils ein anderes, abstraktes Produkt erzeugen. Die abstrakten Produkte implementieren jeweils irgendeine abstrakte und von Produkt zu Produkt unterschiedliche Methode.

package de.jbb.abstrfact;

public abstract class AbstrakteFabrik {

  public abstract AbstraktesProdukt1 erzeugeProdukt1();
  public abstract AbstraktesProdukt2 erzeugeProdukt2();
}
package de.jbb.abstrfact;

public abstract class AbstraktesProdukt1 {

  public abstract void machWas();
}
package de.jbb.abstrfact;

public abstract class AbstraktesProdukt2 {

  public abstract void machWasMitZahlen(int i, int j);
}

Lassen Sie uns jetzt die abstrakten Produkte konkretisieren, indem wir jeweils zwei gewöhnliche Klassen von jedem abstrakten Produkt erben lassen:

package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstraktesProdukt1;

public class Produkt1A extends AbstraktesProdukt1 {

  @Override
  public void machWas() {
    System.out.println("Ich bin ein Produkt der A-Familie");
  }
}
package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstraktesProdukt2;

public class Produkt2A extends AbstraktesProdukt2 {

  @Override
  public void machWasMitZahlen(int i, int j) {
    System.out.println(i + " + " + j + " = " + (i + j));
  }
}
package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstraktesProdukt1;

public class Produkt1B extends AbstraktesProdukt1 {

  @Override
  public void machWas() {
    System.out.println("Ich bin ein Produkt der B-Familie");
  }
}
package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstraktesProdukt2;

public class Produkt2B extends AbstraktesProdukt2 {

  @Override
  public void machWasMitZahlen(int i, int j) {
    System.out.println(i + " x " + j + " = " + (i * j));
  }
}

Zum Erzeugen der konkreten Produkte benötigen wir für jede Produktfamilie eine konkrete Implementierung der abstrakten Fabrik. Diese könnten z. B. so aussehen:

package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstrakteFabrik;
import de.jbb.abstrfact.AbstraktesProdukt1;
import de.jbb.abstrfact.AbstraktesProdukt2;

public class KonkreteFabrikA extends AbstrakteFabrik {

  @Override
  public AbstraktesProdukt1 erzeugeProdukt1() {
    return new Produkt1A();
  }

  @Override
  public AbstraktesProdukt2 erzeugeProdukt2() {
    return new Produkt2A();
  }
}
package de.jbb.abstrfact.conc;

import de.jbb.abstrfact.AbstrakteFabrik;
import de.jbb.abstrfact.AbstraktesProdukt1;
import de.jbb.abstrfact.AbstraktesProdukt2;

public class KonkreteFabrikB extends AbstrakteFabrik {

  @Override
  public AbstraktesProdukt1 erzeugeProdukt1() {
    return new Produkt1B();
  }

  @Override
  public AbstraktesProdukt2 erzeugeProdukt2() {
    return new Produkt2B();
  }
}

Jetzt fehlt noch der Klient. Der Klient ist in diesem Fall eine Klasse, die eine konkrete Fabrik zugewiesen bekommt und die erzeugten Produkte dieser Fabrik „verarbeitet“.

package de.jbb.abstrfact.norm;

import de.jbb.abstrfact.AbstrakteFabrik;
import de.jbb.abstrfact.AbstraktesProdukt1;
import de.jbb.abstrfact.AbstraktesProdukt2;

public class Klient {

  private AbstrakteFabrik fabrik = null;
  private AbstraktesProdukt1 produkt1 = null;
  private AbstraktesProdukt2 produkt2 = null;

  public void setAbstrakteFabrik(AbstrakteFabrik fabrik) {
    this.fabrik = fabrik;
  }

  public void generiereProdukte() {

    if (this.fabrik != null) {
      this.produkt1 = this.fabrik.erzeugeProdukt1();
      this.produkt2 = this.fabrik.erzeugeProdukt2();
    }
  }

  public void verwendeProdukte() {

    if (this.produkt1 != null && this.produkt2 != null) {
      this.produkt1.machWas();
      this.produkt2.machWasMitZahlen(5, 9);
    }
  }
}

Zum Testen können Sie folgende Main-Methode verwenden:

package de.jbb.abstrfact.norm;

import de.jbb.abstrfact.conc.KonkreteFabrikA;

public class Main {

  public static void main(String[] args) {

    Klient k = new Klient();
    k.setAbstrakteFabrik(new KonkreteFabrikA());
    k.generiereProdukte();
    k.verwendeProdukte();
  }
}

Beobachten Sie die Änderungen, wenn Sie anstelle der A-Fabrik eine B-Fabrik übergeben.

Abstract Factory in Stichpunkten

In diesem Pattern gibt es folgende Teilnehmer

  • AbstrakteFabrik => stellt die Schnittstellen zum Erzeugen der Produkte dar.
  • AbstraktesProdukt => stellt die Schnittstellen eines konkreten Produkts dar.
  • KonkreteFabrik => implementiert die Schnittstellen der abstrakten Vorlage zur Erzeugung der konkreten Produkte.
  • KonkretesProdukt => implementiert anhand der Schnittstellen der abstrakten Vorlagen ein konkretes Produkt, welches von einer konkreten Fabrik generiert wird.
  • Klient => Verwendung der durch die abstrakten Klassen vorgegebenen Schnittstellen der Fabrik und der Produkte

, deren Implementation bestimmten Grundregeln unterliegt:

  • In der Regel existiert in jedem Programm nur ein Objekt einer konkreten Fabrik.
  • Die Erzeugung von konkreten Produkten/Objekten wird auf die jeweiligen Unterklassen der abstrakten Fabrik ausgelagert.

Das Abstract Factory Pattern kann z. B. bei folgenden Szenarien eingesetzt werden, wenn

  • mehrere Produktfamilien zur Konfiguration eines Systems zur Verfügung stehen
  • nur die Schnittstellen, nicht aber die eigentliche Implementierung einer Library offengelegt werden sollen
  • Ihr Programm unabhängig davon sein soll, wie gewisse Produkte (Objekte) erzeugt, zusammengesetzt und repräsentiert werden
  • bestimmte Produktfamilien gemeinsam verwendet werden sollten

Daraus resultieren Konsequenzen für den weiteren Verlauf:

  • Vererbung von konkreten Klassen für den Klienten. Dieser arbeitet ausschließlich mit den abstrakten Klassen.
  • Produktfamilien können sehr einfach durch den einmaligen Austausch der konkreten Fabrik bewerkstelligt werden.
  • Sicherstellung durch die Fabrik, dass nur Produkte einer Familie verwendet werden.
  • Erschwertes Hinzufügen von nachträglich auftauchenden Produkten. In diesem Fall muss die abstrakte Fabrik und somit auch alle konkreten Fabriken erweitert werden.

In diesem Zusammenhang sollten Sie auch folgende Pattern beachten:

  • Fabric (Fabrik)
  • Prototype (Prototyp)
  • Singleton

3 Replies to “B) Abstract Factory”

  1. Matthias Pueski

    Schön erklärt, ABER hier wird leider der gleiche fundamentale (und ziemlich schlimme) Fehler gemacht, wie in der deutschen Übersetzung von „Design Patterns“ aus dem Addison-Wesley Verlag.

    Es werden Fachbegriffe aus der Softwareentwicklung eingedeutscht. Sieht nicht nur grausig aus, sondern kann Anfänger, die sich vorwiegend aus englischen Quellen informieren, noch zusätzlich verwirren. Ausserdem ist es sehr bedenklich, junge Entwickler dazu zu animieren „auf deutsch“ zu programmieren. Softwareentwicklung ist heute (speziell im Zuge der Open Source Bewegung) fast ausschliesslich international.

    Und wenn schon denn schon : Wenn Sie deutsch wählen, sollte es auch „Entwurfsmuster“ und nicht „Design-Pattern“ heissen.

  2. Stefan Kiesel

    Hallo Matthias,

    vielen Dank für Ihren Kommentar. Ich habe mich bei dem Artikel darum bemüht, möglichst zweisprachig zu sein. Die Fachbegriffe findet man deshalb sowohl in Deutsch, auch als in Englisch.

    Natürlich gebe ich Ihnen recht, wenn Sie sagen, dass man auf Englisch programmieren sollte. In diesem Beispiel sind die Klassennamen jedoch auf Deutsch verfasst, damit sich der Leser besser etwas unter den Begrifflichkeiten vorstellen kann.

    Wenn Sie jedoch konkrete Vorschläge haben, wie wir den Artikel sprachlich verbessern könnten, werde ich diese Änderungen gerne übernehmen.

    Vielen Dank und Grüße
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.