Techblog

Decorator Pattern in Funktionaler Programmierung

 und Stephan Schneider @stefichjo auf Twitter
27. März 2018

Als Schablone zur Lösung wiederkehrender Entwurfsprobleme haben sich in der objektorientierten Programmierung Design-Patterns etabliert. Sie beschreiben Objekte und deren Beziehungen mit dem Ziel, ein Entwurfsproblem im OOP-Sinne möglichst elegant zu lösen. Nach einem längeren Schattendasein der funktionalen Programmierung erkennt man langsam deren Vorteile. So fließen mittlerweile immer wieder funktionale Paradigmen in objektorientierte Sprachen ein. Durch diesen Umbruch wollen wir beurteilen, welche Auswirkungen funktionale Sprachkonstrukte auf für die objektorientierte Programmierung definierte Lösungsschablonen haben.

Wie unterscheiden sich funktionale und objekt-orientierte Programmierung?

Die funktionale Programmierung hat unter anderem das Ziel, durch starke Abstraktion eine gute Wiederverwendung von Programm-Bausteinen zu ermöglichen. Die Grundlage hierfür sind Funktionen, die laut der mathematischen Definition eine Beziehung zwischen einer Ausgangsmenge und einer Zielmenge beschreiben. Diese Funktionen können in der funktionalen Programmierung zusätzlich zu herkömmlichen Daten anderen Funktionen übergeben und von diesen wieder zurückgegeben werden. Dank dieser Flexibilität können Verarbeitungsketten durch die Komposition von Funktionen erstellt werden. Mit Hilfe dieser Grundlagen wollen wir ein Entwurfsproblem funktional mit Java 8 lösen:

Als Inhaber einer Pizzeria bieten wir unseren Kunden die Möglichkeit, ihre Pizzen aus unterschiedlichsten Zutaten flexibel zusammenzustellen. Damit unterscheiden wir uns von unseren Konkurrenten. Eine Basis-Pizza mit Tomaten und Käse kann nach Belieben mit einer Reihe weiterer Zutaten belegt werden. IT-unterstützt wollen wir die Beschreibung der Pizza erstellen und den Gesamtpreis berechnen. Für diesen Anwendungsfall eignet sich das Decorator Pattern.

Das Decorator Pattern

Das Decorator Pattern dient dazu, Objekte flexibel um Verhalten anzureichern, ohne dazu eine Vielzahl von Unterklassen zu erstellen. Die objektorientierte Umsetzung dieses Beispiels kann auf Github nachgelesen werden. Mit Hilfe der funktionalen Anteile von Java 8 lässt sich die Implementierung des Decorator Patterns etwas anders lösen.

Wie im OOP-Fall definieren wir ein Interface für die Pizza und implementieren dieses für die BasePizza mit Tomaten und Käse.

  1. public interface Pizza {
  2.  
  3.     Double price();
  4.  
  5.     String description();
  6. }

 

  1. public class BasePizza implements Pizza {
  2.  
  3.  
  4.     @Override
  5.     public Double price() {
  6.         return 5.0;
  7.     }
  8.  
  9.     @Override
  10.     public String description() {
  11.         return "Pizza mit Tomaten, Käse";
  12.     }
  13. }

Die Dekoratoren implementieren wir im Gegensatz zu OOP als statische Methoden (Funktionen), die eine Pizza entgegennehmen und diese angereichert um den Preis der Zutat und deren Beschreibung zurückgeben. Hier sieht man auch schon den größten Unterschied zu OOP: Funktionen sind first class citizens. Sie können wie herkömmliche Daten Funktionen übergeben bzw. von diesen zurückgegeben werden.

  1. public class Decorator {
  2.  
  3.     public static Pizza withSalami(Pizza pizza) {
  4.         return new Pizza() {
  5.  
  6.             @Override
  7.             public Double price() {
  8.                 return pizza.price() + 0.5;
  9.             }
  10.  
  11.             @Override
  12.             public String description() {
  13.                 return pizza.description() + ", Salami";
  14.             }
  15.         };
  16.     }
  17.  
  18.     public static Pizza withMushrooms(Pizza pizza) {
  19.         return new Pizza() {
  20.  
  21.             @Override
  22.             public Double price() {
  23.                 return pizza.price() + 0.7;
  24.             }
  25.  
  26.             @Override
  27.             public String description() {
  28.                 return pizza.description() + ", Pilzen";
  29.             }
  30.         };
  31.     }
  32. }

 

Unsere Wunschpizza erhalten wir dann durch Funktionskomposition der Dekoratoren und Anwendung dieser auf die BasePizza

  1. public class Main {
  2.  
  3.     public static void main(String... args) {
  4.  
  5.         Pizza basePizza = new BasePizza();
  6.         Function<Pizza, Pizza> salami =
  7.             Decorator::withSalami;
  8.         Function<Pizza, Pizza> mushrooms =
  9.             Decorator::withMushrooms;
  10.        
  11.         Pizza pizzaRegina =
  12.             salami.compose(mushrooms).apply(basePizza);
  13.  
  14.         System.out.println(pizzaRegina.description());
  15.         System.out.println("Price: " + pizzaRegina.price() + " €");
  16.  
  17.     }
  18.  
  19. }

Patterns mean: I have run out of language

Was wir an dem Beispiel des Decorator-Patterns exemplarisch gesehen haben, gilt auch für viele andere Design-Patterns. Insbesondere Patterns, die Verhalten modellieren (Strategy, Visitor, Command...), sind in der funktionalen Programmierung nicht mehr notwendig. Dies erklärt sich folgendermaßen: Dadurch, dass Funktionen nun first class citizens sind, muss Verhalten nicht mehr indirekt über Objekte und deren Beziehungen modelliert werden, sondern kann direkt durch Funktionen und deren Komposition dagestellt werden. Wir haben so eine reichhaltigere Sprache und können auf einige Design-Patterns verzichten.

Oder mit den Worten von Rich Hickey, Autor von Clojure: „Patterns mean: I have run out of language“.

Kommentare

"Patterns mean: I have run out of language" würde ich gerne öfter hören oder lesen.

Ihr könntet MushroomDecorator::new und HamDecorator::new benutzen anstelle der statischen Methoden, dann ist der Unterschied zur OOP-Version nur minimal.

Der Link zum Dekorator-Muster ist übrigens kaputt, ich glaube da fehlt ein Doppelpunkt.

Danke für den Hinweis zum kaputten Link - ist gefixt

Neuen Kommentar schreiben

Public Comment form

  • Zulässige HTML-Tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd><p><h1><h2><h3>

Plain text

  • Keine HTML-Tags erlaubt.
  • Internet- und E-Mail-Adressen werden automatisch umgewandelt.
  • HTML - Zeilenumbrüche und Absätze werden automatisch erzeugt.

ME Landing Page Question

Erhalten Sie regelmäßig Praxis-Tipps per E-Mail

Praxis-Tipps per E-Mail erhalten