Warum wird ein anämisches Domänenmodell in C # / OOP als schlecht, in F # / FP jedoch als sehr wichtig angesehen?

46

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

Danny Tuppeny
quelle
1
Hallo Dan! Ihre Einstellung ist inspirierend. Neben der .NET- (und der Haskell-) Plattform empfehle ich Ihnen, sich Scala anzuschauen. Debashish ghosh hat geschrieben ein paar Blogs über Domain - Modellierung mit funktionellen Tools war es aufschlussreich für mich, hoffentlich auch für Sie, hier gehen Sie: debasishg.blogspot.com/2012/01/...
AndreasScheinert
2
Mir wurde heute ein interessanter Blogbeitrag von einem Kollegen geschickt: blog.inf.ed.ac.uk/sapm/2014/02/04/… Es scheint, dass die Leute anfangen, die Idee in Frage zu stellen, dass anämische Domain-Modelle ausgesprochen schlecht sind; was ich denke, könnte eine gute Sache sein!
Danny Tuppeny
1
Der Blog-Beitrag, auf den Sie verweisen, basiert auf einer falschen Vorstellung: "Normalerweise ist es in Ordnung, dass die Daten offengelegt werden, ohne dass sie gekapselt werden. Die Daten sind unveränderlich, sodass sie durch eine fehlerhafte Funktion nicht" beschädigt "werden können." Sogar unveränderliche Typen haben Invarianten, die beibehalten werden müssen und die das Ausblenden von Daten und die Steuerung ihrer Erstellung erfordern. Beispielsweise können Sie die Implementierung eines unveränderlichen rot-schwarzen Baums nicht offenlegen, da dann jemand einen Baum erstellen könnte, der nur aus roten Knoten besteht.
Doval
4
@Doval um fair zu sein, das heißt, Sie können keinen Dateisystem-Writer verfügbar machen, weil jemand Ihre Festplatte füllen könnte. Jemand, der einen Baum aus nur roten Knoten erstellt, beschädigt weder den rot-schwarzen Baum, von dem er geklont wurde, noch den Code im gesamten System, der zufällig diese wohlgeformte Instanz verwendet. Wenn Sie Code schreiben, der aktiv neue Instanzen von Müll erzeugt oder gefährliche Dinge tut, werden Sie durch Unveränderlichkeit nicht gerettet, andere jedoch vor Ihnen . Das Ausblenden der Implementierung hindert die Benutzer nicht daran, Unsinn-Code zu schreiben, der durch Null dividiert wird.
Jimmy Hoffa
2
@JimmyHoffa Ich stimme zu, aber das hat nichts mit dem zu tun, was ich kritisiert habe. Der Autor behauptet, dass es normalerweise in Ordnung ist, Daten offenzulegen, weil sie unveränderlich sind, und ich sage, dass Unveränderlichkeit die Notwendigkeit, Implementierungsdetails zu verbergen, nicht auf magische Weise beseitigt.
Doval

Antworten:

37

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. aDaher 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 funktioniert a. 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

Jimmy Hoffa
quelle
Welche Funktionen in F # erleichtern das Schreiben von wiederverwendbarem Code? Warum kann Code in C # nicht so wiederverwendbar sein? (Ich glaube, ich habe die Möglichkeit gesehen, dass Methoden Argumente mit bestimmten Eigenschaften annehmen können, ohne dass eine Schnittstelle erforderlich ist. Ich schätze, dies wäre eine wichtige?)
Danny Tuppeny,
7
@DannyTuppeny ehrlich gesagt F # ist ein schlechtes Vergleichsbeispiel, es ist nur ein leicht verkleideter C #; Es ist eine wandelbare imperative Sprache, genau wie C #. Es hat ein paar FP-Funktionen, die C # nicht hat, aber nicht viel. Schauen Sie auf haskell, um zu sehen, wo FP wirklich auffällt, und solche Dinge werden aufgrund von Typklassen und generischen ADTs viel möglicher
Jimmy Hoffa
@MattFenwick Darin beziehe ich mich ausdrücklich auf C #, weil das im Poster gefragt wurde. Wo ich mich in meiner Antwort hier auf OOP beziehe, meine ich C # OOP.
Jimmy Hoffa
2
Ein gemeinsames Merkmal der funktionalen Sprachen, die diese Art der Wiederverwendung zulassen, ist die dynamische oder abgeleitete Typisierung. Während die Sprache selbst gut definierte Datentypen verwendet, ist es der typischen Funktion egal, welche Daten vorhanden sind, solange die Operationen (andere Funktionen oder Arithmetik) gültig sind. Dies ist auch in OO-Paradigmen verfügbar (Go hat beispielsweise eine implizite Schnittstellenimplementierung, die es einem Objekt ermöglicht, eine Ente zu sein, da es fliegen, schwimmen und quaken kann, ohne dass das Objekt explizit als Ente deklariert wurde) so ziemlich eine Voraussetzung für die funktionale Programmierung.
KeithS
4
@ KeithS Mit wertbasierter Überladung in Haskell meine ich Pattern Matching. Haskells Fähigkeit, mehrere gleichnamige Funktionen der obersten Ebene mit unterschiedlichen Mustern zu haben, führt sofort zu 1 Funktion der obersten Ebene + einer Musterübereinstimmung.
Jozefg
6

Warum wird ein anämisches Domänenmodell in C # / OOP als schlecht, in F # / FP jedoch als sehr wichtig angesehen?

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.

Wieso leihen wir uns die Idee der Trennung von Daten / Verhalten nicht aus und betrachten sie sogar als schlecht?

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.

Ist es einfach so, dass die Definition nicht zu OOP passt,

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 .

Oder gibt es einen konkreten Grund, warum es in C # schlecht ist, dass es aus irgendeinem Grund in F # nicht gilt (und tatsächlich umgekehrt ist)?

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.

(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.)

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

Jon Harrop
quelle
2
... wenn alles, was Sie haben, ein Hammer ist, werden OOP-Leute Hammerfabriken erstellen; P +1, um den Kern dessen, was C # fehlt, wirklich festzuhalten, damit Daten und Verhalten vollständig voneinander abstrahiert werden können: Unionstypen und Musterabgleich.
Jimmy Hoffa,
-4

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.

Giacomo Citi
quelle
4
Können Sie etwas näher erläutern, wie dies für die objektorientierte Programmierung im Vergleich zur funktionalen Programmierung gilt?