In einem Blog-Post auf F # heißt es zum Spaß und Gewinn:
In einem funktionalen Design ist es sehr wichtig, das Verhalten von den Daten zu trennen. Die Datentypen sind einfach und "dumm". Und dann haben Sie separat eine Reihe von Funktionen, die auf diese Datentypen einwirken.
Dies ist das genaue Gegenteil eines objektorientierten Designs, bei dem Verhalten und Daten kombiniert werden sollen. Das ist doch genau das, was eine Klasse ist. In einem wirklich objektorientierten Design sollte man eigentlich nur Verhalten haben - die Daten sind privat und können nur über Methoden abgerufen werden.
Tatsächlich wird in OOD ein unzureichendes Verhalten in Bezug auf einen Datentyp als eine schlechte Sache angesehen und hat sogar einen Namen: das " anämische Domänenmodell ".
Angesichts dessen, dass wir in C # immer wieder von F # leihen und versuchen, funktionaleren Code zu schreiben; Wieso leihen wir uns die Idee der Trennung von Daten / Verhalten nicht aus und betrachten sie sogar als schlecht? Ist es einfach so, dass die Definition nicht mit OOP übereinstimmt, oder gibt es einen konkreten Grund dafür, dass es in C # schlecht ist, dass es aus irgendeinem Grund in F # nicht zutrifft (und tatsächlich umgekehrt ist)?
(Hinweis: Ich bin speziell an den Unterschieden in C # / F # interessiert, die die Meinung über das Gute / Schlechte ändern könnten, und nicht an Personen, die einer der beiden Meinungen im Blog-Beitrag nicht zustimmen.)
quelle
Antworten:
Der Hauptgrund, warum FP dies anstrebt und C # OOP dies nicht tut, ist, dass der Fokus in FP auf referentieller Transparenz liegt; Das heißt, Daten gehen in eine Funktion und Daten werden ausgegeben, aber die ursprünglichen Daten werden nicht geändert.
In C # OOP gibt es ein Konzept der Delegation von Zuständigkeiten, bei dem Sie die Verwaltung eines Objekts an dieses delegieren, und daher möchten Sie, dass es seine eigenen Interna ändert.
In FP möchten Sie niemals die Werte in einem Objekt ändern, daher ist es nicht sinnvoll, Ihre Funktionen in Ihr Objekt einzubetten.
Weiterhin haben Sie in FP einen höherwertigen Polymorphismus, der es Ihnen ermöglicht, Ihre Funktionen viel allgemeiner zu gestalten, als es C # OOP erlaubt. Auf diese Weise können Sie eine Funktion schreiben, die für jede Funktion funktioniert.
a
Daher ist es nicht sinnvoll, sie in einen Datenblock einzubetten. das würde die Methode eng koppeln, so dass es nur mit dieser bestimmten Art von funktionierta
. Ein solches Verhalten ist in C # OOP durchaus üblich, da Sie ohnehin nicht in der Lage sind, Funktionen allgemein zu abstrahieren, aber in FP ist es ein Kompromiss.Das größte Problem, das ich bei anämischen Domänenmodellen in C # OOP gesehen habe, ist, dass Sie am Ende doppelten Code haben, weil Sie DTO x und 4 verschiedene Funktionen haben, die die Aktivität f für DTO x festschreiben, weil 4 verschiedene Personen die andere Implementierung nicht gesehen haben . Wenn Sie die Methode direkt in DTO x einfügen, sehen diese 4 Personen alle die Implementierung von f und verwenden sie erneut.
Anämische Datenmodelle in C # OOP behindern die Wiederverwendung von Code, aber dies ist in FP nicht der Fall, da eine einzelne Funktion auf so viele verschiedene Typen verallgemeinert ist, dass Sie eine größere Wiederverwendung von Code erhalten, da diese Funktion in so viel mehr Szenarien als in einer von Ihnen verwendeten Funktion verwendet werden kann würde für ein einzelnes DTO in C # schreiben.
Wie in den Kommentaren ausgeführt , ist die Typinferenz einer der Vorteile, auf die sich FP stützt, um einen so signifikanten Polymorphismus zu ermöglichen, und insbesondere können Sie dies auf das Hindley-Milner- Typsystem mit der Typinferenz nach Algorithmus W zurückführen. Eine solche Typinferenz im C # OOP-Typsystem wurde vermieden, da die Kompilierungszeit beim Hinzufügen einer Constraint-basierten Inferenz aufgrund der erforderlichen umfassenden Suche extrem lang wird. Details finden Sie hier: https://stackoverflow.com/questions/3968834/generics-why -kann-der-Compiler-die-Typ-Argumente-in-diesem-Fall-ableiten
quelle
Ihre Frage hat ein großes Problem, das die Nützlichkeit der Antworten einschränkt: Sie setzen voraus, dass F # und FP ähnlich sind. FP ist eine große Familie von Sprachen, einschließlich symbolischer, dynamischer und statischer Umschreibungen. Selbst unter statisch typisierten FP-Sprachen gibt es viele verschiedene Technologien zum Ausdrücken von Domänenmodellen wie Modulen höherer Ordnung in OCaml und SML (die in F # nicht vorhanden sind). F # ist eine dieser funktionalen Sprachen, ist jedoch besonders schlank und bietet insbesondere weder Module höherer Ordnung noch Typen höherer Art.
Tatsächlich konnte ich Ihnen nicht sagen, wie Domänenmodelle in FP ausgedrückt werden. Die andere Antwort hier spricht sehr spezifisch darüber, wie es in Haskell gemacht wird und ist überhaupt nicht auf Lisp (die Mutter aller FP-Sprachen), die ML-Sprachfamilie oder andere funktionale Sprachen anwendbar.
Generika können als Mittel zur Trennung von Daten und Verhalten angesehen werden. Generika aus der ML-Familie funktionaler Programmiersprachen gehören nicht zu OOP. C # hat natürlich Generika. Man könnte also argumentieren, dass C # die Idee der Trennung von Daten und Verhalten langsam aufgreift.
Ich glaube, OOP basiert auf einer grundlegend anderen Prämisse und bietet Ihnen folglich nicht die Werkzeuge, die Sie benötigen, um Daten und Verhalten zu trennen. Für alle praktischen Zwecke benötigen Sie Produkt- und Summen-Datentypen und versenden über diese. In ML bedeutet dies Vereinigungs- und Datensatztypen sowie Mustervergleich.
Schauen Sie sich das Beispiel an, das ich hier gegeben habe .
Seien Sie vorsichtig, wenn Sie von OOP nach C # springen. C # ist in Bezug auf OOP bei weitem nicht so puritanisch wie andere Sprachen. Das .NET Framework ist jetzt voll von Generika, statischen Methoden und sogar Lambdas.
Das Fehlen von Union-Typen und Pattern-Matching in C # macht dies fast unmöglich. Wenn Sie nur einen Hammer haben, sieht alles aus wie ein Nagel ...
quelle
Ich denke, in einer Geschäftsanwendung möchten Sie häufig keine Daten ausblenden, da der Mustervergleich für unveränderliche Werte großartig ist, um sicherzustellen, dass Sie alle möglichen Fälle abdecken. Wenn Sie jedoch komplexe Algorithmen oder Datenstrukturen implementieren, sollten Sie Implementierungsdetails ausblenden und ADTs (algebraische Datentypen) in ADTs (abstrakte Datentypen) umwandeln.
quelle