Dependency Injection in der funktionalen Programmierung

Know-how  –  14 Kommentare
Anzeige

Trotz vieler Unterschiede können die Anhänger der objektorientierten und die der funktionalen Programmierung einiges voneinander lernen. Dependency Injection ist ein gutes Beispiel.

Wenn sich funktionale und objektorientierte Programmierer unterhalten, gibt es oft Kommunikationsprobleme. Die Herangehensweisen beider Lager sind bei vielen grundlegenden Aufgaben unterschiedlich. Entwickler, die bereit sind, sich auf die fremde Welt des jeweils anderen einzulassen, können allerdings neue Einblicke gewinnen. Besonders gut lässt sich das beim Thema Dependency Injection betrachten, das die funktionale Welt vollkommen anders aufzieht als die der OO-Programmierung. Der Artikel konzentriert sich auf die Dependency Injection in funktionalen Sprachen. Nebenbei führt er in die Sprache OCaml ein und zeigt die Implementierung einer Monade.

Als Grundannahme dient, dass die Kernaufgabe der Dependency Injection darin besteht, für einen Satz von Operationen (eine Art API) innerhalb eines Programms unterschiedliche Implementierungen angeben zu können. In einem OO-Programm ist ein Operationssatz in der Regel durch ein Interface vertreten. Es geht folglich darum, in einem Programm zwischen unterschiedlichen Umsetzungen des Interface wählen zu können.

Die Beispiele des Artikels sind in OCaml verfasst, verwendete Elemente erläutert der Autor im Verlauf. Durch den schnellen Compiler, der auch leistungsfähigen nativen Code generieren kann, ist die Sprache für viele Projekte attraktiv. Sie repräsentiert typisierte, funktionale Programmierung in Reinform und hat gerade ihren 20. Geburtstag hinter sich.

In letzter Zeit erfreut sich OCaml eines Popularitätsschubs, was unter anderem daran liegt, dass nun mit OPAM ein Package-Manager zur Verfügung steht, der über 1000 nützliche Bibliotheken verwaltet.

Als Beispiel dient eine einfache Datenbank, die nur zwei Operationen kennt: get und put. In OCaml könnte der erste Anlauf dafür so aussehen:

type key = string
type value = int

type table = (key, value) Hashtbl.t

let the_table = Hashtbl.create 0

let get (k: key): value = Hashtbl.find the_table k
let put (k: key) (v: value) = Hashtbl.add the_table k v

Zunächst werden die Typen key und value definiert, im Beispiel sind sie der Einfachheit halber Strings beziehungsweise Integer-Werte. Im Anschluss kommt das in OCaml enthaltene Hashtbl-Modul zum Einsatz, um einen Typ für die Datenbank festzulegen. Der Typ für Hash-Tabellen Hashtbl.t hat zwei Typparameter für Schlüssel und Werte – die Entsprechung in Java wäre sinngemäß Hashtbl.t<key, value>.

Es folgt das Erzeugen und Benennen der "Datenbank", genannt the_table, sowie die Definition der Operationen get und put. Erstere bekommt einen Parameter k vom Typ key und liefert ein Ergebnis vom Typ value. Die Definition nutzt die in Hashtbl enthaltene find-Funktion. put akzeptiert die Parameter k und v und benutzt Hashtbl.add, um das Schlüssel/Wert-Paar in die Datenbank zu schreiben. In OCaml sind die Argumente eines Funktionsaufrufs nicht in Klammern mit Komma, sondern hintereinander zu schreiben.

Der Beispielcode enthält eine implizite Abhängigkeit ("Dependency") vom globalen Zustand. Der erste Schritt auf dem Weg, den Code mit Dependency Injection auszurüsten, sollte daher sein, für den Zustand einen Parameter einzuführen:

let get (k: key) (ht: table): value = Hashtbl.find ht k
let put (k: key) (v: value) (ht: table) = Hashtbl.add ht k v

Für die nun noch vorhandene Hashtbl-Abhängigkeit gibt es in der OCaml-Standardbibliothek mit Assoziationslisten, also Listen aus Schlüssel/Wert-Paaren, eine einfache Alternative. Da sie funktionale Datenstrukturen sind, gibt es allerdings keine Operation, die wie Hashtbl.add einfach einen Eintrag in einer Assoziationsliste ergänzt. Es besteht lediglich die Option, aus einer Liste eine neue mit einem zusätzlichem Eintrag zu generieren.

Der Versuch, Hashtbl durch eine Assoziationsliste zu ersetzen, offenbart ein grundsätzliches Problem bei der API-Gestaltung von get und put: Das Zusammenspiel der beiden Operationen ist implizit, da put kein Resultat liefert, obwohl die Funktion durch die Modifikation der Datenbank eine Ausgabe hat.

Anzeige