Ich fand dieses Zitat in " Die Freude an Clojure " auf S. 32, aber jemand hat mir letzte Woche beim Abendessen dasselbe gesagt und ich habe es auch an anderen Orten gehört:
Ein Nachteil der objektorientierten Programmierung ist die enge Kopplung zwischen Funktion und Daten.
Ich verstehe, warum unnötige Kopplung in einer Anwendung schlecht ist. Ich sage auch gerne, dass veränderlicher Zustand und Vererbung vermieden werden sollten, auch in der objektorientierten Programmierung. Aber ich verstehe nicht, warum das Festhalten an Klassenfunktionen von Natur aus schlecht ist.
Ich meine, das Hinzufügen einer Funktion zu einer Klasse scheint so, als würde man eine Mail in Google Mail markieren oder eine Datei in einen Ordner stecken. Es ist eine organisatorische Technik, die Ihnen hilft, es wiederzufinden. Sie wählen einige Kriterien aus und fügen sie dann zusammen. Vor OOP waren unsere Programme ziemlich viele Methoden in Dateien. Ich meine, du musst Funktionen irgendwo platzieren. Warum nicht organisieren?
Wenn dies ein verschleierter Angriff auf Typen ist, warum sagen sie dann nicht einfach, dass es falsch ist, die Art der Eingabe und Ausgabe auf eine Funktion zu beschränken? Ich bin mir nicht sicher, ob ich dem zustimmen könnte, aber zumindest kenne ich die Argumente für und gegen die Sicherheit. Das klingt für mich nach einem größtenteils separaten Problem.
Klar, manchmal verstehen die Leute es falsch und ordnen Funktionalität der falschen Klasse zu. Im Vergleich zu anderen Fehlern scheint dies jedoch eine sehr geringfügige Unannehmlichkeit zu sein.
Clojure hat also Namespaces. Wie unterscheidet sich das Festhalten einer Funktion an einer Klasse in OOP vom Festhalten einer Funktion in einem Namespace in Clojure und warum ist es so schlecht? Denken Sie daran, dass Funktionen in einer Klasse nicht unbedingt nur für Mitglieder dieser Klasse ausgeführt werden. Schauen Sie sich java.lang.StringBuilder an - es funktioniert auf jedem Referenztyp oder durch Auto-Boxing auf jedem Typ.
PS Dieses Zitat bezieht sich auf ein Buch, das ich nicht gelesen habe: Multiparadigmenprogrammierung in Leda: Timothy Budd, 1995 .
Antworten:
Theoretisch erleichtert die lose Funktions-Daten-Kopplung das Hinzufügen weiterer Funktionen, um mit denselben Daten zu arbeiten. Der Nachteil ist, dass es schwieriger ist, die Datenstruktur selbst zu ändern, weshalb in der Praxis gut gestalteter Funktionscode und gut gestalteter OOP-Code sehr ähnliche Kopplungsgrade aufweisen.
Nehmen Sie einen gerichteten azyklischen Graphen (DAG) als Beispiel für eine Datenstruktur. Bei der funktionalen Programmierung müssen Sie immer noch abstrahieren, um Wiederholungen zu vermeiden. Daher erstellen Sie ein Modul mit Funktionen zum Hinzufügen und Löschen von Knoten und Kanten, zum Auffinden von Knoten, die von einem bestimmten Knoten aus erreichbar sind, zum Erstellen einer topologischen Sortierung usw. Diese Funktionen sind effektiv eng mit den Daten verbunden, auch wenn der Compiler sie nicht erzwingt. Sie können einen Knoten auf die harte Tour hinzufügen, aber warum möchten Sie das? Kohäsivität innerhalb eines Moduls verhindert eine enge Kopplung im gesamten System.
Umgekehrt werden auf der OOP-Seite alle anderen Funktionen als die grundlegenden DAG-Operationen in separaten "Ansichts" -Klassen ausgeführt, wobei das DAG-Objekt als Parameter übergeben wird. Es ist genauso einfach, so viele Ansichten hinzuzufügen, wie Sie möchten, um die DAG-Daten zu verarbeiten. Dadurch wird die gleiche Ebene der Entkopplung von Funktionsdaten wie im Funktionsprogramm erreicht. Der Compiler wird Sie nicht davon abhalten, alles in eine Klasse zu packen, aber Ihre Kollegen werden es tun.
Das Ändern von Programmierparadigmen ändert nicht die bewährten Methoden für Abstraktion, Zusammenhalt und Kopplung, sondern nur die Methoden, mit deren Hilfe Sie den Compiler durchsetzen können. Wenn Sie in der funktionalen Programmierung eine Funktions-Daten-Kopplung wünschen, wird dies eher durch die Zustimmung von Gentlemen's als durch den Compiler erzwungen. In OOP wird die Trennung zwischen Modell und Ansicht eher durch die Zustimmung von Gentlemen's als durch den Compiler erzwungen.
quelle
Falls Sie es noch nicht wussten, nehmen Sie diese Einsicht: Die Konzepte von objektorientiert und Verschlüssen sind zwei Seiten derselben Medaille. Das heißt, was ist eine Schließung? Es nimmt Variablen oder Daten aus dem umgebenden Bereich und bindet sie innerhalb der Funktion an, oder aus einer OO-Perspektive tun Sie genau dasselbe, wenn Sie beispielsweise etwas an einen Konstruktor übergeben, damit Sie dies später verwenden können Daten in einer Mitgliedsfunktion dieser Instanz. Es ist jedoch nicht ratsam, Dinge aus dem Umfeld zu entnehmen - je größer das Umfeld, desto schlimmer ist es, dies zu tun (obwohl pragmatisch gesehen, ist oft etwas Schlimmes erforderlich, um die Arbeit zu erledigen). Die Verwendung globaler Variablen bringt dies auf das Äußerste, wo Funktionen in einem Programm Variablen im Programmumfang verwenden - wirklich, wirklich böse. Es gibtGute Beschreibungen darüber, warum globale Variablen böse sind.
Wenn Sie OO-Techniken befolgen, akzeptieren Sie grundsätzlich bereits, dass jedes Modul in Ihrem Programm ein bestimmtes Mindestmaß an Übel aufweist. Wenn Sie einen funktionalen Ansatz für die Programmierung wählen, streben Sie ein Ideal an, bei dem kein Modul in Ihrem Programm das Übel des Abschlusses enthält, obwohl Sie vielleicht noch einige haben, aber es wird viel weniger als OO sein.
Das ist der Nachteil von OO - es ermutigt diese Art von Übel, die Kopplung von Daten an Funktionen durch die Standardisierung von Closures (eine Art Theorie der Programmierung mit zerbrochenen Fenstern ).
Die einzige positive Seite ist, dass, wenn Sie wüssten, dass Sie am Anfang viele Closures verwenden würden, OO Ihnen zumindest einen idealen logischen Rahmen bietet, um diesen Ansatz so zu organisieren, dass der durchschnittliche Programmierer ihn verstehen kann. Insbesondere werden die Variablen, über die geschlossen wird, im Konstruktor explizit und nicht nur implizit in einem Funktionsschluss verwendet. Funktionsprogramme, die viele Verschlüsse verwenden, sind oft kryptischer als das entsprechende OO-Programm, obwohl sie nicht unbedingt weniger elegant sind :)
quelle
Es geht um Typ - Kopplung:
Eine in ein Objekt eingebaute Funktion zur Bearbeitung dieses Objekts kann nicht für andere Objekttypen verwendet werden.
In Haskell schreiben Sie Funktionen zur Arbeit gegen Typ - Klassen - so gibt es viele verschiedene Arten von Objekten jede gegebene Funktion gegen funktionieren kann, solange es sich um eine Art der gegebenen ist Klasse diese Funktion funktioniert auf.
Freistehende Funktionen ermöglichen eine solche Entkopplung, die Sie nicht erhalten, wenn Sie sich darauf konzentrieren, Ihre Funktionen so zu schreiben, dass sie innerhalb des Typs A funktionieren, da Sie sie dann nicht verwenden können, wenn Sie keine Instanz des Typs A haben, auch wenn die Funktion dies könnte Andernfalls sollten Sie allgemein genug sein, um für eine Instanz vom Typ B oder Typ C verwendet zu werden.
quelle
In Java und ähnlichen Inkarnationen von OOP können Instanzmethoden (im Gegensatz zu freien Funktionen oder Erweiterungsmethoden) nicht aus anderen Modulen hinzugefügt werden.
Dies stellt eine größere Einschränkung dar, wenn Sie Schnittstellen berücksichtigen, die nur von den Instanzmethoden implementiert werden können. Sie können eine Schnittstelle und eine Klasse nicht in verschiedenen Modulen definieren und dann den Code eines dritten Moduls verwenden, um sie miteinander zu verbinden. Ein flexiblerer Ansatz, wie Haskells Typklassen, sollte das können.
quelle
Bei der Objektorientierung geht es im Wesentlichen um die prozedurale Datenabstraktion (oder die funktionale Datenabstraktion, wenn Sie orthogonale Nebenwirkungen beseitigen). In gewisser Hinsicht ist Lambda Calculus die älteste und reinste objektorientierte Sprache, da sie nur funktionale Datenabstraktion bietet (da sie außer Funktionen keine Konstrukte enthält).
Nur die Operationen eines einzelnen Objekts können die Datendarstellung dieses Objekts überprüfen. Das können nicht einmal andere Objekte des gleichen Typs . (Dies ist der Hauptunterschied zwischen objektorientierter Datenabstraktion und abstrakten Datentypen: Bei ADTs können Objekte desselben Typs die gegenseitige Datendarstellung überprüfen, nur die Darstellung von Objekten anderer Typen wird ausgeblendet.)
Dies bedeutet, dass mehrere Objekte desselben Typs unterschiedliche Datendarstellungen haben können. Sogar das gleiche Objekt kann zu verschiedenen Zeiten unterschiedliche Datendarstellungen haben. (In Scala wechseln
Map
s undSet
s beispielsweise abhängig von der Anzahl der Elemente zwischen einem Array und einem Hash-Versuch, da bei sehr kleinen Zahlen die lineare Suche in einem Array aufgrund der sehr kleinen konstanten Faktoren schneller ist als die logarithmische Suche in einem Suchbaum .)Von außerhalb eines Objekts sollten Sie nicht, Sie können seine Datendarstellung nicht kennen. Das ist das Gegenteil von enger Kopplung.
quelle
Eine enge Kopplung zwischen Daten und Funktionen ist schlecht, weil Sie in der Lage sein möchten, sich unabhängig voneinander zu ändern, und eine enge Kopplung erschwert dies, weil Sie nicht eines ändern können, ohne das andere zu kennen und möglicherweise zu ändern.
Sie möchten, dass unterschiedliche Daten, die für die Funktion angezeigt werden, keine Änderungen in der Funktion erfordern, und Sie möchten in ähnlicher Weise Änderungen an der Funktion vornehmen können, ohne dass Änderungen an den Daten erforderlich sind, mit denen sie arbeitet, um diese Funktionsänderungen zu unterstützen.
quelle