it-swarm.com.de

Warum ist "enge Kopplung zwischen Funktionen und Daten" schlecht?

Ich fand dieses Zitat in " Die Freude von Clojure " auf S. 32, aber jemand hat mir letzte Woche beim Abendessen dasselbe gesagt, und ich habe es auch an anderen Orten gehört:

[A] Nachteil der objektorientierten Programmierung ist die enge Kopplung zwischen Funktion und Daten.

Ich verstehe, warum unnötige Kopplung in einer Anwendung schlecht ist. Ich kann auch sagen, dass veränderlicher Zustand und Vererbung vermieden werden sollten, selbst in der objektorientierten Programmierung. Aber ich verstehe nicht, warum es von Natur aus schlecht ist, Funktionen auf Klassen zu kleben.

Ich meine, das Hinzufügen einer Funktion zu einer Klasse scheint so, als würde man eine E-Mail in Google Mail markieren oder eine Datei in einen Ordner stecken. Es ist eine Organisationstechnik, mit der Sie sie wiederfinden können. Sie wählen einige Kriterien aus und setzen dann ähnliche Dinge zusammen. Vor OOP waren unsere Programme ziemlich viele Methoden in Dateien. Ich meine, man muss irgendwo Funktionen 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 meist eigenständigen Anliegen.

Sicher, manchmal verstehen es die Leute falsch und stellen die Funktionalität in die falsche Klasse. Im Vergleich zu anderen Fehlern scheint dies jedoch eine sehr kleine 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 ausgeführt werden Schauen Sie sich Java.lang.StringBuilder an - es funktioniert mit jedem Referenztyp oder durch Auto-Boxing mit jedem Typ überhaupt.

P.S. Dieses Zitat bezieht sich auf ein Buch, das ich nicht gelesen habe: Multiparadigm Programming in Leda: Timothy Budd, 1995 .

38
GlenPeterson

Theoretisch erleichtert die lose Kopplung von Funktionsdaten das Hinzufügen weiterer Funktionen, um dieselben Daten zu bearbeiten. 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 Kopplungsstufen aufweisen.

Nehmen Sie als Beispieldatenstruktur einen gerichteten azyklischen Graphen (DAG). In der funktionalen Programmierung benötigen Sie noch eine gewisse Abstraktion, 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, obwohl der Compiler sie nicht erzwingt. Sie können einen Knoten auf die harte Tour hinzufügen, aber warum möchten Sie das? Die Kohäsivität innerhalb eines Moduls verhindert eine enge Kopplung im gesamten System.

Umgekehrt werden auf der Seite OOP) alle Funktionen außer den grundlegenden DAG-Operationen in separaten "Ansicht" -Klassen ausgeführt, wobei das DAG-Objekt als Parameter übergeben wird. Dies ist genauso einfach Fügen Sie so viele Ansichten hinzu, wie Sie möchten, die mit den DAG-Daten arbeiten, und erzeugen Sie die gleiche Ebene der Entkopplung von Funktionsdaten wie im Funktionsprogramm. Der Compiler hält Sie nicht davon ab, alles in eine Klasse zu packen, aber Ihre Kollegen werden es tun .

Das Ändern von Programmierparadigmen ändert nicht die Best Practices für Abstraktion, Kohäsion und Kopplung, sondern nur die Praktiken, die der Compiler Ihnen beim Durchsetzen hilft. Wenn Sie in der funktionalen Programmierung eine Funktionsdatenkopplung wünschen, wird diese eher durch die Zustimmung der Herren als durch den Compiler erzwungen. In OOP wird die Trennung von Modellansichten eher durch die Zustimmung der Herren als durch den Compiler erzwungen.

34
Karl Bielefeldt

Falls Sie es noch nicht wussten, nehmen Sie diese Einsicht: Die Konzepte von objektorientiert und Verschlüsse sind zwei Seiten derselben Medaille. Was ist eine Schließung? Es nimmt Variablen oder Daten aus dem umgebenden Bereich und bindet sie innerhalb der Funktion daran. Aus OO-Sicht tun Sie dasselbe, wenn Sie beispielsweise etwas an einen Konstruktor übergeben, damit Sie es später verwenden können Daten in einer Mitgliedsfunktion dieser Instanz. Aber Dinge aus dem umgebenden Bereich zu nehmen ist keine schöne Sache - je größer der umgebende Bereich, desto böser ist es, dies zu tun (obwohl pragmatisch gesehen oft etwas Böses notwendig ist, um Arbeit zu erledigen). Die Verwendung globaler Variablen bringt dies auf das Äußerste, wo Funktionen in einem Programm Variablen im Programmumfang verwenden - wirklich sehr, sehr böse. Es gibt gute Beschreibungen anderswo darüber, warum globale Variablen böse sind.

Wenn Sie OO Techniken folgen, akzeptieren Sie im Grunde bereits, dass jedes Modul in Ihrem Programm ein bestimmtes Mindestmaß an Übel aufweist. Wenn Sie einen funktionalen Ansatz für die Programmierung verfolgen, streben Sie ein Ideal an, bei dem nein Das Modul in Ihrem Programm enthält Closure Evil, obwohl Sie möglicherweise noch einige haben, aber es wird viel weniger als OO sein.

Das ist der Nachteil von OO - es fördert diese Art von Übel, die Kopplung von Daten, um zu funktionieren, indem Verschlüsse zum Standard gemacht werden (eine Art Theorie des zerbrochenen Fensters der Programmierung).

Das einzige Plus ist, dass, wenn Sie wüssten, dass Sie zunächst viele Verschlüsse verwenden würden, OO bietet Ihnen zumindest einen idealogischen Rahmen, um diesen Ansatz so zu organisieren, dass der durchschnittliche Programmierer kann es verstehen. Insbesondere die Variablen, die geschlossen werden, sind im Konstruktor explizit und nicht nur implizit in einem Funktionsabschluss enthalten. Funktionsprogramme, die viele Abschlüsse verwenden, sind häufig kryptischer als das Äquivalent OO Programm, wenn auch nicht unbedingt weniger elegant :)

13
Benedict

Es geht um Typ Kopplung:

Eine in ein Objekt integrierte Funktion zur Bearbeitung dieses Objekts kann nicht für andere Objekttypen verwendet werden.

In Haskell schreiben Sie Funktionen, um gegen Klassen vom Typ zu arbeiten . Es gibt also viele verschiedene Arten von Objekten, gegen die eine bestimmte Funktion arbeiten kann, solange es sich um einen Typ handelt der angegebenen Klasse , an der diese Funktion arbeitet.

Freistehende Funktionen ermöglichen eine solche Entkopplung, die Sie nicht erhalten, wenn Sie sich darauf konzentrieren, Ihre Funktionen innerhalb von Typ A zu schreiben, da Sie sie dann nicht verwenden können, wenn Sie keine Instanz von Typ A haben, obwohl die Funktion dies möglicherweise tut Andernfalls ist es allgemein genug, um für eine Instanz vom Typ B oder Typ C verwendet zu werden.

7
Jimmy Hoffa

In Java und ähnliche Inkarnationen von OOP können Instanzmethoden (im Gegensatz zu freien Funktionen oder Erweiterungsmethoden) nicht aus anderen Modulen hinzugefügt werden.

Dies wird zu einer größeren Einschränkung, 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 Code aus einem dritten Modul verwenden, um sie miteinander zu verbinden. Ein flexiblerer Ansatz wie Haskells Typklassen sollte dies tun können.

4
CodesInChaos

Bei der Objektorientierung geht es im Wesentlichen um prozedurale Datenabstraktion (oder funktionale Datenabstraktion, wenn Sie Nebenwirkungen beseitigen, die ein orthogonales Problem darstellen). In gewissem Sinne ist Lambda Calculus die älteste und reinste objektorientierte Sprache, da sie nur eine 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 vom Typ gleicher Typ. (Dies ist der Hauptunterschied zwischen objektorientierter Datenabstraktion und abstrakten Datentypen: Bei ADTs können Objekte desselben Typs die Datendarstellung des jeweils anderen überprüfen, nur die Darstellung von Objekten des Typs other ist ausgeblendet. )

Dies bedeutet, dass mehrere Objekte desselben Typs unterschiedliche Datendarstellungen haben können. Sogar dasselbe Objekt kann zu unterschiedlichen Zeiten unterschiedliche Datendarstellungen aufweisen. (In Scala wechseln beispielsweise Maps und Sets je nach Anzahl der Elemente zwischen einem Array und einem Hash-Versuch, da bei sehr kleinen Zahlen die lineare Suche in einem Array schneller ist als die logarithmische Suche in ein Suchbaum wegen der sehr kleinen konstanten Faktoren.)

Von der Außenseite eines Objekts sollten Sie nicht, Sie können seine Datendarstellung kennen. Das ist das Gegenteil der engen Kopplung.

3
Jörg W Mittag

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 macht dies schwierig, weil Sie eine nicht ändern können, ohne die andere zu kennen und möglicherweise zu ändern.

Sie möchten, dass verschiedene Daten, die der Funktion angezeigt werden, keine Änderungen an der Funktion erfordern, und Sie möchten in der Lage sein, Änderungen an der Funktion vorzunehmen, ohne dass Änderungen an den Daten erforderlich sind, mit denen sie arbeitet, um diese Funktionsänderungen zu unterstützen.

2
Michael Durrant