Optionals für Methodenketten

It is a mistake to look too far ahead. Only one link of the chain of destiny can be handled at a time.

Winston Churchill

Obwohl das Law Of Demeter den folgenden Aufruf zu Recht verteufelt, hat man es, in der holprigen Projektwelt, immer wieder mal mit solchen Verkettungen zu tun.

String footnote = repository.getPart().getSection().getParagraph().getFootnotes().getFirst();

In diesem Beispiel kennt unser Code eine ganze Menge der internen Struktur des Objektes repository. Sollten sich Änderungen in dieser Struktur ergeben oder Umbenennungen der verwendeten Methoden erfolgen, dann muss dieser Code angepasst werden.

Bei einer schlecht gemachten API können all diese Methodenaufrufe außerdem NULL Werte zurückliefern. Dann muss der Entwickler jeden einzelnen Wert erst einmal prüfen, bevor die nächste Methode auf diesem aufgerufen werden kann.

Part part = repository.getPart();
Section section = part == null ? null : part.getSection();
Paragraph paragraph = section== null ? null : section.getParagraph();
Footnotes footnotes = paragraph == null ? null : paragraph .getFootnotes();
String footnote = footnotes== null ? null : footnotes.getFirst();
if (footnote == null) {
  throw new MissingFootnoteException();
}

An dieser Stelle kommen die Klasse Optional ist Spiel. Ihre map Methoden arbeitet ja nur auf den eingeschlossenen Wert, wenn dieser ungleich NULL ist. Auf unser Problem angesetzt können wir unseren Code weiter umformen.

Optional<Part> part = Optional.ofNullable(repository.getPart());
Optional<Section> section = part.map(Part::getSection);
Optional<Paragraph> paragraph = section.map(Section::getParagraph);
Optional<Footnotes> footnotes = paragraph.map(Paragraph::getFootnotes);
String footnote = footnotes.map(Footnotes::getFirst).orElseThrow(() -> new MissingFootnoteException());

Die ganzen Zwischenresultate sind uns egal, also streichen wir sie heraus.

String footnote = Optional.ofNullable(repository.getPart()).map(Part::getSection)
  .map(Section::getParagraph).map(Paragraph::getFootnotes).map(Footnotes::getFirst)
  .orElseThrow(() -> new MissingFootnoteException());

Etwas länger als zuvor, aber wir können wieder ohne Angst vor einer NullPointerException gegen das Law Of Demeter verstoßen.