Generierte Builder für Vertrags- und Produktteilklassen

Policy- und ProductBuilder ermöglichen dem User schnell und unkompliziert neue Instanzen von Policen und Produkten zu erstellen. Damit kann ausgehend von einem Faktor-IPS Modell die gesamte Vertrags- und Produktstruktur instanziiert werden. Dies vereinfacht Setup-Code sowohl in Produktivsystemen als auch beim Testen erheblich.

Beispiel

builderDemoUML
// Police erstellen
IHausratVertrag hausrateVertrag = IHausratVertrag.NEW.builder()
    .zahlweise(12).plz("123456") //
    .add() // Referenzierte Objekte hinzufügen
    .deckung(IHausratGrunddeckung.NEW.builder() // (1)
        .wohnfläche(120) //
        .add()
        .zuNa()) // ZuNa an neue Deckung hinzufügen, die Klammer von (1) wird erst hier geschlossen!
    .add() // weiteres Objekt hinzufügen
    .deckung(IFahrraddiebstahlDeckung.NEW.builder());

Getting started

Installation

Dieses Projekt ist direkt in faktorips.base integriert. Die Generierung von Builder-Klassen kann in Eclipse an und ausgeschalten werden:

  1. Rechtsklick auf das Projekt in Package Explorer

  2. Wähle Eigenschaften (Properties)

  3. Wähle links Faktor-IPS Code Generator

  4. Den Wert von Generate Builder Classes ändern (Optionen: Policies only, Products only, All und None)

toggleBuilderSwitch1
toggleBuilderSwitch2

Features

Policy- und ProductBuilder generierieren zu jeder Vertrags-/Produktklasse ModellObjekt eine Builderklasse ModellObjektBuilder. Diese stellt alle Methoden bereit, die notwendig sind, um eine Instanz der entsprechenden Modellklasse, sowie deren Assoziationen/Kompositionen, zu erstellen und zu bearbeiten. Die zugehörige Builderklasse für eine Modellklasse Vertrag wird VertragBuilder genannt.

PolicyBuilder werden verwendet, um Vertragsinstanzen zu erstellen, oder zu bearbeiten. Dies ist sowohl für Produktivsysteme als auch für Tests sinnvoll. ProductBuilder werden verwendet um Produktmodelle zu erstellen. Hierfür wird ein InMemoryRuntimeRepository verwendet, das alle Produktdaten im Speicher hält. Dadurch, dass Modelle programmatisch erstellt werden können, ergeben sich mehrere Vorteile:

  • Es wird kein spezielles Test-Produkt mit entsprechendem Projekt-Setup und Sourcecode Verwaltung benötigt

  • Modelle können flexibel pro Anwendungsfall/Testfall erstellt werden

ProductBuilder sind daher speziell für Tests sinnvoll.

Der Großteil der Features existiert sowohl für Policy- als auch für ProductBuilder. Im Folgenden werden primär die Features von PolicyBuilder vorgestellt. Spezifische Funktionen für ProductBuilder werden im jeweiligen Abschnitt angemerkt.

Neue Instanzen erstellen

IHausratVertrag hausratVertrag = IHausratVertrag.NEW.builder().getResult();

  • Eine Factory für die entsprechende Builder-Klasse wird als Konstante (NEW) im Published Interface hinterlegt

  • Die Method builder() erstellt einen Builder mit einer neuen Vertragsinstanz

  • getResult() gibt die gebaute Instanz zurück

Falls keine Published Interfaces generiert werden, können neue Instanzen folgendermaßen erstellt werden:
HausratVertrag hausratVertrag = HausratVertrag.builder().getResult();

Ebenso können auch konfigurierte Vertragsinstanzen mit Hilfe eines existierenden Produktbausteins erzeugt werden.
IFahrradDiebstahlDeckung fahrradDiebstahlDeckung = IFahrradDiebstahlDeckung.NEW.builder(runtimeRepository, "beispiel.Fahrrad 2015-06").getResult();

  • Mit builder(runtimeRepository) kann zusätzlich ein IRuntimeRepository gespeichert werden, in dem Produktbausteine liegen

  • Für konfigurierte Klassen werden builder(productCmpt) und builder(runtimeRepository, productCmptId) angeboten

Für Produktklassen muss ein Repository angegeben werden. Es gibt 3 Möglichkeiten, eine neue Produktinstanz zu erstellen:

IHausratDeckungstyp hausratDeckungstyp = IHausratDeckungstyp.NEW
    .builder(repo, "beispiel.", "Basic", "2016-05").getResult();

IHausratDeckungstyp hausratDeckungstyp = IHausratDeckungstyp.NEW
    .builder(repo, "beispiel.", "Basic", "2016-05",
    new DateTime(2015, 7, 1)).getResult();

IHausratDeckungstyp hausratDeckungstyp = IHausratDeckungstyp.NEW
    .builder(repo, "beispiel.Fahrrad 2015-07").getResult();
  • builder(inMemoryRuntimeRepository, id, kindId, versionId) und builder(inMemoryRuntimeRepository, id, kindId, versionId) erstellen einen Builder mit einer neuen Produktinstanz mit den gegebenen IDs.

  • builder(inMemoryRuntimeRepository, productCmptId) erstellt einen Builder mit einer neuen Instanz von einem Produkt, wovon ein Produktbaustein bereits im Repository existiert.

Bestehende Instanzen bearbeiten

hausratVertrag.modify()…
hausratVertrag.modify(repository)…

  • Existierende Instanzen können mit der Objektmethode modify() bearbeitet werden. Diese Methode gibt einen Builder von dem Vertrag zurück.

  • Auch hier kann ein IRuntimeRepository explizit angegeben werden.

Attribute setzen und bearbeiten

hausratVertrag.modify().plz("12345") //
    .zahlweise(12); //

//oder

IHausratVertrag hausratVertrag = IHausratVertrag.NEW.builder()//
    .plz("12345") //
    .zahlweise(12) //
    .getResult();
  • Attribute können mit einer gleichnamigen Methode gesetzt werden

  • Setter für Attribute geben wieder den Builder zurück, damit weitere Attribute gesetzt, oder die Struktur weitergebaut werden kann

Assoziationen/Kompositionen hinzufügen

PolicyBuilder bietet hierfür die Methode add() an. Dadurch wird ein AssociationsBuilder zurück gegeben an dem für alle Beziehungen Methoden mit den entsprechenden Rollennamen vorhanden sind.

// Hinzufügen einer Deckung,nur möglich, wenn das Ziel nicht abstrakt ist
hausratVertrag.modify().add().deckung();

// Eine spezielle Deckungsklasse verwenden
hausratVertrag.modify().add().deckung(IHausratGrunddeckung.NEW.builder().wohnflaeche(120));
hausratVertrag.modify().add().deckung(IFahrradDiebstahlDeckung.NEW.builder(runtimeRepository, "beispiel.Fahrrad 2015-06"));

// Die neue Instanz gleich weiter bearbeiten (der Deckung eine ZuNa hinzufügen)
hausratVertrag.modify().add().deckung(IHausratGrunddeckung.NEW.builder().add().zuNa());

// ProductCmptID benutzen
hausratVertrag.modify(runtimeRepository).add().deckung("beispiel.Fahrrad 2015-06");
  • Mit add() wird damit begonnen, eine verknüpfte Instanz zu erstellen

  • Setter für Assoziationen haben den gleichen Namen wie der Rollenname (im Singular)

  • Die Rollen-Methode (z.B. deckung()) erstellt eine neue Instanz der Zielklasse. Es wird der ursprüngliche Builder (also z.B. HausratVertragBuilder) zurück gegeben.

  • Mit den Rollen-Methode (rolle(targetBuilder)) kann man eine Instanz einer Subklasse verknüpfen

  • Wenn das neue, verknüpfte Objekt weiter eingestellt werden soll, kann der targetBuilder weiter verwendet werden (die Beziehungs-Methode bekommt also ggf. ein mehrzeiliges Argument)

  • Falls der Builder ein RuntimeRepository kennt, kann die Zielinstanz auch mit Hilfe des ProductCmptIDs erstellt werden (rolle(productCmptID))

Anpassungsstufen

ProductBuilder generiert bei der Erstellung einer neuen Produktinstanz automatisch eine neue Anpassungsstufe. Diese Anpassungsstufe wird implizit für Attribute und Assoziationen verwendet, die sich über die Zeit ändern. Es kann aber auch jederzeit neue Anpassungstufen erstellt, und in Bearbeitung gesetzt werden:
hausratDeckungstyp.modify().anpStufe(2015, 3, 2).add().zuNaTyp("beispiel.", "ZuNa", "2015-03");

Nun wird die Assoziation zu der Anpassungstufe mit der Gültigkeitsdatum 2015-03-02 hinzugefügt. ProductBuilder bietet auch Getter-Methoden an, die Anpassungsstufen zurückgeben:
hausratDeckungstyp.modify().anpStufe(2013,4,2).getCurrentGeneration();
hausratDeckungstyp.modify().getLatestAnpStufe();

  • getCurrentGeneration() gibt die Anpassungsstufe zurück, die sich gerade in Bearbeitung befindet.

  • getLatestAnpStufe() gibt die zeitlich aktuellste Anpassungsstufe zurück.

Die Methodennamen sind abhängig von der Faktor IPS-Einstellung zur Namensgebung von Anpassungsstufen/Generationen:

  • getLatestAnpStufe() heißt analog getLatestGen()

  • anpStufe() heißt analog gen()

Cheat Sheet

Ziel Methode Anwendungsbeispiel Ausgangsklasse Rückgabewert

Neue Instanz erstellen

NEW.builder()

IVetrag.NEW.builder()

Published Interface

Builder

NEW.builder(runtimeRepo)

IVertrag.NEW.builder(runtimeRepo).add().deckung("beispiel.Deckung 2016-05")

Published Interface

Builder

NEW.builder(runtimeRepo, prodCmptId)

IVertrag.NEW.builder(runtimeRepo, "beispiel.Vertrag 2016-05")

Published Interface

Builder

NEW.builder(runtimeRepo, prodCmptId, calendar)

IVertrag.NEW.builder(runtimeRepo, "beispiel.Vertrag 2016-05", new GregorianCalendar(2016, 5, 16))

Published Interface

Builder

NEW.builder(productCmpt)

Published Interface

Builder

Neue Instanz erstellen (ohne Published Interfaces)

builder()

Vetrag.builder()

Vertragsklasse

Builder

builder(runtimeRepo)

Vertrag.builder(runtimeRepo).add().deckung("beispiel.Deckung 2016-05")

Vertragsklasse

Builder

builder(runtimeRepo, prodCmptId)

Vertrag.builder(runtimeRepo, "beispiel.Vertrag 2016-05").add().deckung("beispiel.Deckung 2016-05")

Vertragsklasse

Builder

builder(runtimeRepo, prodCmptId, calendar)

Vertrag.builder(runtimeRepo, "beispiel.Vertrag 2016-05", new GregorianCalendar(2016, 5, 16))

Vertragsklasse

Builder

builder(productCmpt)

Vertragsklasse

Builder

Gebaute Instanz speichern

getResult()

IVertrag.NEW.builder().getResult()

Builder

Vertrag

getCurrentGeneration()

IProdukt.NEW.builder(runtimeRepo, productCmtId).getCurrentGeneration()

Builder

Anpassungsstufe

getLatestAnpStufe() oder getLatestGen() (je nach Namenskonvention)

IProdukt.NEW.builder(runtimeRepo, productCmtId).getLatestAnpStufe() oder IProdukt.NEW.builder(runtimeRepo, productCmtId).getLatestGen()

Builder

Anpassungsstufe

Instanz bearbeiten

modify()

vertrag.modify()

Vertrag

Builder

modify(runtimeRepo)

vertrag.modify(runtimeRepo)

Vertrag

Builder

Attribute bearbeiten

attributName()

vertrag.modify().plz("12345")

Builder

Builder

Eine Assoziation hinzufügen

add()

vertrag.modify().add()

Builder

AssociationsBuilder

rolle()

vertrag.modify().add().deckung()

AssociationsBuilder

Builder (Quelle, z.B. VertragBuilder)

rolle(zielVertragsBuilder)

vertrag.modify().add().deckung(IGrunddeckung.NEW.builder())

AssociationsBuilder

Builder (Quelle, z.B. VertragBuilder)

Ziel der Assoziation weiter bauen

rolle(zielVertragsBuilder)

vertrag.modify().add().deckung(IGrunddeckung.NEW.builder().plz("12345"))

AssociationsBuilder

Builder (Quelle, z.B. VertragBuilder)

Ziel mit Produktbaustein konfigurieren

rolle(prodCmptId)

vertrag.modify(runtimeRepo).add().deckung("beispiel.Deckung 2016-05")

AssociationsBuilder

Builder (Quelle, z.B. VertragBuilder)

Zu bearbeitende Anpassungsstufe ändern

anpstufe(jahr, monat, tag) oder gen(jahr, monat, tag) (je nach Namenskonvention)

IProdukt.NEW.builder(runtimeRepo, productCmtId).anpStufe(2016,5,1).plz("12345") oder IProdukt.NEW.builder(runtimeRepo, productCmtId).gen(2016,5,1).plz("12345")

Builder

Builder

Vorbelegung für produktkonfiguriertes Vertragsattribut ändern

vertragsAttributnameDefault(neuerStandardwert)

produkt.modify().versicherungssummeDefault(Money.euro(100_000));

Produkt

Builder

Wertebereich für produktkonfiguriertes Vertragsattribut ändern

vertragsAttributnameAllowedValues(neueWertemenge)

produkt.modify().versicherungssummeAllowedValues(new OrderedValueSet(false, Money.NULL, Money.euro(100_000), Money.euro(250_000)));

Produkt

Builder

Tipp

  • In Eclipse kann ein Zeilenumbruch erzwungen werden, indem man „//“ (leerer Kommentar) ans Ende der Zeile schreibt, um so mehrere Änderungen durch einen Builder zu strukturieren. Alternativ können in den Formatter-Einstellungen für Zeilenumbrüche festgelegt werden, dass manuell umgebrochene Zeilen niemals zusammengefügt werden („Never join already wrapped lines“).