In den letzten Jahren wurden die Sprachen, die ich gerne benutze, immer "funktionaler". Ich benutze jetzt Sprachen, die eine Art "Hybrid" sind: C #, F #, Scala. Ich entwerfe meine Anwendung gerne mit Klassen, die den Domänenobjekten entsprechen, und verwende Funktionsmerkmale, die das Codieren einfacher, übereinstimmender und sicherer machen (insbesondere beim Bearbeiten von Sammlungen oder beim Übergeben von Funktionen).
Die beiden Welten "kollidieren" jedoch, wenn es darum geht, Muster zu entwerfen. Das spezifische Beispiel, mit dem ich kürzlich konfrontiert wurde, ist das Beobachtermuster. Ich möchte, dass ein Produzent einen anderen Code benachrichtigt (die "Verbraucher / Beobachter", z. B. einen DB-Speicher, einen Logger usw.), wenn ein Artikel erstellt oder geändert wird.
Ich habe es anfangs "funktional" so gemacht:
producer.foo(item => { updateItemInDb(item); insertLog(item) })
// calls the function passed as argument as an item is processed
Aber ich frage mich jetzt, ob ich einen "OO" -Ansatz verwenden soll:
interface IItemObserver {
onNotify(Item)
}
class DBObserver : IItemObserver ...
class LogObserver: IItemObserver ...
producer.addObserver(new DBObserver)
producer.addObserver(new LogObserver)
producer.foo() //calls observer in a loop
Welches sind die Vor- und Nachteile der beiden Ansätze? Ich hörte einmal einen FP-Guru sagen, dass Designmuster nur aufgrund der Einschränkungen der Sprache vorhanden sind, und deshalb gibt es in funktionalen Sprachen so wenige. Vielleicht könnte dies ein Beispiel dafür sein?
EDIT: In meinem speziellen Szenario brauche ich es nicht, aber ... wie würden Sie das Entfernen und Hinzufügen von "Beobachtern" auf funktionale Weise implementieren? (Dh wie würden Sie alle Funktionen im Muster implementieren?) Übergeben Sie beispielsweise nur eine neue Funktion?
quelle
Antworten:
Dies ist ein gutes Beispiel für zwei verschiedene Ansätze, die den Gedanken tragen, eine Aufgabe außerhalb der für das aufrufende Objekt relevanten Grenze auszuführen.
Während in diesem Beispiel klar ist, dass Sie sich für den funktionalen Ansatz entscheiden sollten, hängt dies im Allgemeinen davon ab, wie komplex das Verhalten des aufgerufenen Objekts ist. Wenn es sich wirklich um ein komplexes Verhalten handelt, bei dem Sie häufig ähnliche Logik erneut anwenden und Funktionsgeneratoren nicht verwendet werden können, um sie klar auszudrücken, sollten Sie sich wahrscheinlich für die Klassenzusammensetzung oder -vererbung entscheiden, wo Sie möchten haben ein bisschen mehr Freiheit, vorhandenes Verhalten auf Ad-hoc-Basis wiederzuverwenden und zu erweitern.
Ein Muster, das ich beobachtet habe, ist jedoch, dass Entwickler normalerweise zunächst den funktionalen Ansatz wählen und sich erst dann für einen klassenbasierten Ansatz entscheiden, wenn die Nachfrage nach einem detaillierteren Verhalten entsteht. Ich weiß zum Beispiel, dass Django von funktionsbasierten Ansichten, Vorlagenladern und Testläufern zu klassenbasierten Ansichten überging, sobald die Vorteile und Anforderungen offensichtlich wurden, aber nicht vorher.
quelle
Die funktionale Version ist viel kürzer, leichter zu warten, leichter zu lesen und im Allgemeinen in nahezu jeder erdenklichen Hinsicht weit überlegen.
Viele - obwohl alles andere als Muster - sollen den Mangel an Funktionen in OOP ausgleichen, wie z. B. Beobachter. Diese sind funktional weitaus besser modelliert.
quelle
Dein "FP-Guru" hat teilweise recht; Viele OO-Muster sind Hacks, um funktionale Dinge zu tun. (Seine Behauptung, dass dies der Grund ist, warum es nur wenige FP-Sprachen gibt, scheint bestenfalls zweifelhaft.) Die Muster Observer und Strategy versuchen, erstklassige Funktionen zu emulieren. Das Besuchermuster ist ein Hack, um den Mustervergleich zu simulieren. Ihr
IItemObserver
ist nur eine Funktion in Verkleidung. Wenn Sie so tun, als ob es sich von jeder anderen Funktion unterscheidet, die einen Gegenstand übernimmt, kaufen Sie nichts.Objekte sind nur eine Art der Datenabstraktion. Dieses Papier wird dazu beitragen, Licht in dieses Thema zu bringen. Objekte können nützlich sein, aber es ist wichtig zu erkennen, dass sie nicht für alles geeignet sind. Es gibt keine Zweiteilung; Es geht einfach darum, das richtige Werkzeug für den richtigen Job auszuwählen, und für die funktionale Programmierung müssen Sie in keiner Weise Objekte aufgeben. Abgesehen davon ist funktionale Programmierung mehr als nur die Verwendung von Funktionen. Es geht auch darum, Nebenwirkungen und Mutationen zu minimieren.
quelle
Ich kann die Frage nicht wirklich beantworten, weil ich nicht gut in funktionalen Sprachen bin. Aber ich denke, Sie sollten sich weniger Gedanken über die Vorgehensweise machen, solange das, was Sie haben, funktioniert. Soweit ich weiß, können Sie das Observer-Muster hier sehr gut überspringen, solange Sie in Zukunft keine weiteren Listener hinzufügen oder die Listener während der Ausführung nicht ändern.
Ich bin nicht der Meinung, dass Entwurfsmuster "Einschränkungen" in OO-Sprachen ausgleichen. Sie sind dazu da, die OOP-Funktionen gut zu nutzen. Polymorphismus und Vererbung sind Merkmale, keine Einschränkungen. Entwurfsmuster nutzen diese Funktionen, um ein flexibles Design zu fördern. Sie können ein absolut Nicht-OO-Programm in einem OO erstellen. Mit viel Sorgfalt können Sie ein ganzes Programm schreiben, das Objekte enthält, die keinen Status haben und FP imitieren.
quelle