Getty Images

Einstieg in funktionale und objektorientierte Programmierung

Die Entscheidung zwischen funktionaler und objektorientierter Programmierung ist nicht einfach, aber es gibt eine Reihe von Faktoren, die die Entscheidung erleichtern.

Die Entscheidung für ein Programmierparadigma ist ein wichtiger Schritt bei der Softwareentwicklung. Zwar sind dies kaum die einzigen beiden Optionen, wenn es um übergreifende Entwicklungsmodelle geht, aber die Wahl zwischen funktionaler Programmierung und objektorientierter Programmierung ist eine, vor der heute immer mehr Entwickler stehen.

Die objektorientierte Programmierung ist ein weithin akzeptierter Entwicklungsansatz und liegt oft den strukturierten Programmen zugrunde, die die meisten Entwickler in den ersten Phasen ihrer Karriere zu schreiben lernen. Viele dieser Sprachen enthalten Elemente, die kaum von Funktionen zu unterscheiden sind, aber sie sind weit entfernt von den Mechanismen, die hinter einer rein funktionalen Programmiersprache wie Haskell stehen.

In diesem Beitrag gehen wir auf die wichtigsten Unterschiede zwischen funktionaler und objektorientierter Programmierung ein, zeigen Ihnen einige Beispiele, wie sie funktionieren, und gehen auf Überlegungen ein, die bei der Wahl zwischen diesen Programmierparadigmen am wichtigsten sind.

Funktionale versus objektorientierte Programmiermethoden

Im Wesentlichen verhalten sich funktionale Programme wie gewöhnliche mathematische Funktionen, zum Beispiel die Berechnungen hinter einer Umrechnung von Celsius in Fahrenheit. Bei Funktionen führen die gleichen Eingaben immer zum gleichen Ergebnis. Eine reine Funktion ist deterministisch und erzeugt keine Nebeneffekte – mit anderen Worten, sie gibt immer denselben Wert zurück, wenn sie aufgerufen wird, und verändert nichts außerhalb ihres Bereichs oder ihrer Parameter.

Ein unglaublich leistungsfähiges Beispiel für die funktionale Methode ist Googles Implementierung von MapReduce und sein Ansatz zur Rückgabe von Ergebnissen zu bestimmten Suchbegriffen. MapReduce verknüpft Suchbegriffe in Form von Schlüssel/Wert-Paaren mit einer Funktion namens reduce. Dieser Prozess aggregiert die Begriffe und weist jedem Begriff einen Wert zu, der angibt, welche Art von Ergebnis zurückgegeben werden soll. Bei gleichem Datensatz wird Google jedes Mal die gleiche Antwort ausgeben, ohne dass es zu Nebenwirkungen kommt.

Andererseits kann die objektorientierte Programmierung zustandsabhängige Variablen enthalten, was bedeutet, dass Objekte nicht unbedingt konsistente Werte beibehalten. Wenn Sie zum Beispiel eine Methode aufrufen, die ein Gehalt zurückgibt, erhalten Sie vielleicht 50.000 Euro zurück. Wenn Sie jedoch eine Methode ausführen, die zehn Prozent zu dieser Summe hinzufügt, und dann erneut nach dem Gehalt fragen, lautet der zurückgegebene Wert 55.000 Euro. Objektorientierte Programme können auch Dinge wie globale Variablen und statische Variablen enthalten, die dafür sorgen, dass die Antworten auf Anfragen jedes Mal anders ausfallen.

Um funktionalen Programmen einen Zustand hinzuzufügen, verwenden Sie eine Programmiersprache, die nicht rein funktional ist, wie zum Beispiel F#. Sie können auch Funktionen zu einem eher traditionellen Programm wie LINQ hinzufügen.

Code-Beispiele für funktionale Programmierung versus objektorientierte Programmierung

Im Folgenden finden Sie ein Codebeispiel für die funktionale Programmierung mit der FizzBuzz-Codieraufgabe in F#. FizzBuzz ist ein gängiger Codierungstest, bei dem Entwickler ein Programm erstellen, das eine Reihe von Buchstaben und Zahlen auf der Grundlage einer einfachen Reihe von Regeln ausgibt. Wenn die Zahl durch drei teilbar ist, wird an ihrer Stelle das Wort Fizz ausgegeben. Als nächstes wird bei Zahlen, die durch fünf teilbar sind, das Wort Buzz ausgegeben. Und wenn die Zahl sowohl durch drei als auch durch fünf teilbar ist, geben Sie FizzBuzz aus. In einer funktionalen Sprache wie F# kann diese Logik mit Funktionen strukturiert werden. Das Programm besteht vollständig aus diesen Funktionen, wie im folgenden Beispiel gezeigt wird:

open System

let isFizzBuzz value =
 (value % 15) = 0

let isFizz value =
 (value % 3) = 0

let isBuzz value =
 (value % 5) = 0

let output (value : int) =
 if isFizzBuzz value then
   "FizzBuzz"
 else if isFizz value then
   "Fizz"
 else if isBuzz value then
   "Buzz"
 else
   string value

[< EntryPoint >]
let main argv =
 let message =
   seq { for i in 1..100 do output i}
   |> String.concat " "
 printfn "%s" message
 printfn "Done"

Als Nächstes sehen wir uns den objektorientierten Ansatz mit C# als Sprache an. Während die Logik ähnlich ist, besteht der große Unterschied beim objektorientierten Ansatz darin, dass die Schleife in ein Objekt verpackt ist, das die aktuelle Nummer der Schleife als Variable enthält.

Dieser objektorientierte Ansatz hat einige Vorteile. Erstens: Wenn die Anwendung aus einer Reihe von Logikobjekten besteht, können die Objekte über vereinfachte Schnittstellen interagieren. Zweitens kann ein Programmierer, um eine Reihe von Spielen ähnlich wie FizzBuzz zu erstellen, die Vererbung nutzen, um je nach Bedarf Logik hinzuzufügen und zu ändern. Hier sehen Sie die C#-Implementierung als Klasse sowie eine traditionellere Hauptroutine zur Durchführung der Ausgabe:

public class Fizzer {
   private int _val;
        
   public Fizzer() {
     _val = 1;
   }
 
   public string getNewVal() {
       string answer = "";
       if (_val%5==0) answer = "Fizz";

       if (_val%3==0) answer+= "Buzz";

       if (!(_val%3==0 || _val%5==0)) answer = Convert.ToString(_val);
 
       answer+="\n";
       _val+=1;
       return answer;
   }
}

class Program {
    static void Main(string[] args) {
        Fizzer f = new Fizzer();
        for (int idx=1;idx<100;idx++){
            Console.Write(f.getNewVal());
        }
    }   
}

Auf einer höheren Abstraktionsebene kann ein Objekt eine Schleife von Anfang bis Ende durchlaufen und die Methode aufrufen. Dies ist nützlich, wenn der Programmierer Spiele zur Laufzeit austauschen oder das Programm anhand von Regeln aus einer Datei erstellen möchte, die sich im Laufe der Zeit ändern können. Um eine objektorientierte Implementierung von FizzBuzz zu sehen, sehen Sie sich dieses objektorientierte C#-Beispiel von Steve Poling an, einem ehemaligen Softwareentwickler und technischen Berater bei Excelon Development. Beachten Sie, dass es mehr Codezeilen gibt, die für eine Wiederverwendung in Frage kommen.

Anwendungsfälle für funktionale Programmierung versus objektorientierte Programmierung

Die Gestaltung von Benutzeroberflächen ist ein natürlicher Fall für den objektorientierten Ansatz. Die Fenster, die auf dem Bildschirm eines Benutzers erscheinen, bestehen häufig aus Schaltflächen, Textfeldern und Menüs. Diese Fenster haben einen Status – zum Beispiel ändert sich der Status des Textes auf einer Seite in einem Textverarbeitungsprogramm, wenn der Benutzer tippt. Sobald das grundlegende Layout eines Fensters verfügbar ist, können andere Programmierer diesen Code übernehmen und wiederverwenden, indem sie mit einer Shell beginnen und die Objekte ausfüllen.

Funktionale Programmierung ist die beste Option, wenn die Anwendung aus Funktionen besteht, die aufeinander aufbauen. Unter Unix ist es zum Beispiel üblich, eine große Anzahl kleiner Programme miteinander zu verknüpfen, die die Ergebnisse einer Prozessauflistung an eine bestimmte Suche wie grep oder mit less an eine Seite senden.

Kombinieren Sie funktionale und objektorientierte Programmierung

Ein unglücklicher Nachteil der objektorientierten Programmierung ist das Risiko, eine komplexe Codebasis zu schaffen, die im Laufe der Zeit immer schwieriger zu verwalten ist. Selbst bei hochentwickelten Entwicklungen, die den objektorientierten Ansatz verwenden, ähnelt der Code oft dem obigen Beispiel – ein paar Objekte, verstreut zwischen einer beträchtlichen Menge an prozeduralem Code.

Es kann schwierig sein, Objekte zu testen oder zu debuggen, die andere Objekte erstellen oder Verbindungen zu externen Datenbanken und APIs haben. Im Falle eines Fehlers ist es unwahrscheinlich, dass der Programmierer die Werte in allen Objekten kennt oder genau weiß, wie er sie wiederherstellen kann, selbst wenn er eine vollständige Aufzeichnung des Fehlers hat. Es gibt sicherlich einige Entwurfsmuster, die sich mit diesen Problemen befassen, aber die Akzeptanz dieser Muster in der Industrie ist eher gering. Auf jeden Fall sehen sich Entwicklungsabteilungen, die sich dem objektorientierten Ansatz verschrieben haben, oft mit schwierigen Code-Reviews und Wartungsprojekten konfrontiert.

Die funktionale Programmierung hingegen stellt eine andere Herausforderung dar: Sie kann sehr schwer zu erlernen und in die Praxis umzusetzen sein. Der funktionale Ansatz erfordert eine völlig andere Art des Denkens über Code, einen beträchtlichen Zeitaufwand und rigorose Aufmerksamkeit für Details. Aus diesen Gründen mag die IT-Leitung die funktionale Programmierung als Risiko betrachten.

Es gibt jedoch einen Mittelweg. Programmierer können den funktionalen Ansatz nutzen, um einen kleinen Teil einer größeren Anwendung zu erstellen. Jede Logik, die Daten verarbeitet und Ergebnisse im Batch-Verfahren erzeugt, ist ein guter Kandidat für diese Strategie. Dazu gehören Routinen zur Erstellung von Versicherungsangeboten, zur Produktplanung und zum Extrahieren, Transformieren und Laden (ETL). Es ist auch möglich, funktionalen Programmen einen Zustand hinzuzufügen, indem Sie eine Programmiersprache verwenden, die nicht rein funktional ist, wie zum Beispiel F#.

Ebenso ist es möglich, Funktionen zu Programmen hinzuzufügen, die auf traditionellen, objektorientierten Sprachen basieren. So verfügen Sprachen wie C# und Java mittlerweile über Funktionen, die einen funktionalen Ansatz ermöglichen. Eine dieser Funktionen ist die Möglichkeit, Code in F# zu schreiben, der über die Common Language Runtime mit C# interagiert.

Erfahren Sie mehr über Softwareentwicklung