Von Stefan López Romero

und Stephan Schneider

Estimated reading time: 5 Minuten

Techblog

Decorator Pattern in Funktionaler Programmierung

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…

Techblog

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.

public interface Pizza {

    Double price();

    String description();

}

public class BasePizza implements Pizza {

 

    @Override

    public Double price() {

        return 5.0;

    }

 

    @Override

    public String description() {

        return "Pizza mit Tomaten, Käse";

    }

}

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.

public class Decorator {

 

    public static Pizza withSalami(Pizza pizza) {

        return new Pizza() {

 

            @Override

            public Double price() {

                return pizza.price() + 0.5;

            }

 

            @Override

            public String description() {

                return pizza.description() + ", Salami";

            }

        };

    }

 

    public static Pizza withMushrooms(Pizza pizza) {

        return new Pizza() {

 

            @Override

            public Double price() {

                return pizza.price() + 0.7;

            }

 

            @Override

            public String description() {

                return pizza.description() + ", Pilzen";

            }

        };

    }

}

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

public class Main {

 

    public static void main(String... args) {

 

        Pizza basePizza = new BasePizza();

        Function<Pizza, Pizza> salami = 

            Decorator::withSalami;

        Function<Pizza, Pizza> mushrooms = 

            Decorator::withMushrooms;

        

        Pizza pizzaRegina = 

            salami.compose(mushrooms).apply(basePizza);

 

        System.out.println(pizzaRegina.description());

        System.out.println("Price: " + pizzaRegina.price() + " €");

 

    }

 

}

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“.


Über den Autor

Von Stefan López Romero

und Stephan Schneider