Die prozedurale / funktionale Programmierung ist in keiner Weise schwächer als OOP , auch ohne auf Turing-Argumente einzugehen (meine Sprache hat Turing-Macht und kann alles tun, was ein anderer tun wird), was nicht viel bedeutet. Tatsächlich wurden objektorientierte Techniken zuerst in Sprachen experimentiert, in denen sie nicht integriert waren. In diesem Sinne ist die OO-Programmierung nur ein bestimmter Stil der prozeduralen Programmierung . Es hilft jedoch dabei, bestimmte Disziplinen wie Modularität, Abstraktion und das Verbergen von Informationen durchzusetzen , die für die Verständlichkeit und Wartung des Programms unerlässlich sind.
Einige Programmierparadigmen entwickeln sich aus der theoretischen Sicht der Berechnung. Eine Sprache wie Lisp entwickelte sich aus der Lambda-Rechnung und der Idee der Meta-Zirkularität von Sprachen (ähnlich der Reflexivität in der natürlichen Sprache). Horn-Klauseln haben Prolog und Constraint-Programmierung hervorgebracht. Die Algol-Familie verdankt auch Lambda-Kalkül, jedoch ohne eingebaute Reflexivität.
Lisp ist ein interessantes Beispiel, da es das Testfeld vieler Programmierspracheninnovationen war, die auf sein doppeltes genetisches Erbe zurückzuführen sind.
Dann entwickeln sich jedoch Sprachen, oft unter neuen Namen. Ein wesentlicher Faktor der Evolution ist die Programmierpraxis. Benutzer identifizieren Programmierpraktiken, die die Eigenschaften von Programmen verbessern, z. B. Lesbarkeit, Wartbarkeit und Korrektheit. Dann versuchen sie, den Sprachen Features oder Einschränkungen hinzuzufügen, die diese Praktiken unterstützen und manchmal erzwingen, um die Qualität der Programme zu verbessern.
Dies bedeutet, dass diese Praktiken bereits in älteren Programmiersprachen möglich sind, aber es erfordert Verständnis und Disziplin, um sie anzuwenden. Die Einbindung in neue Sprachen als primäre Konzepte mit spezifischer Syntax erleichtert die Verwendung und das Verständnis dieser Praktiken, insbesondere für weniger erfahrene Benutzer (dh die große Mehrheit). Dies erleichtert auch den erfahrenen Anwendern das Leben.
In gewisser Weise ist es für die Sprachgestaltung so, wie ein Unterprogramm / eine Funktion / eine Prozedur für ein Programm ist. Sobald das nützliche Konzept identifiziert ist, wird es (möglicherweise) mit einem Namen und einer Syntax versehen, so dass es problemlos in allen Programmen verwendet werden kann, die mit dieser Sprache entwickelt wurden. Und wenn es gelingt, wird es auch in zukünftigen Sprachen verwendet.
Beispiel: Objektorientierung neu erstellen
Ich versuche das jetzt an einem Beispiel zu veranschaulichen (das mit Sicherheit bei gegebener Zeit weiter poliert werden könnte). Das Beispiel soll nicht zeigen, dass ein objektorientiertes Programm im prozeduralen Programmierstil geschrieben werden kann, möglicherweise auf Kosten der Lesbarkeit und Wartbarkeit. Ich werde eher versuchen zu zeigen, dass einige Sprachen ohne OO-Einrichtungen Funktionen und Datenstrukturen höherer Ordnung tatsächlich verwenden können, um die Mittel zur effektiven Nachahmung der Objektorientierung zu schaffen , um von ihren Qualitäten in Bezug auf die Programmorganisation, einschließlich Modularität, Abstraktion und Verstecken von Informationen , zu profitieren .
Wie ich bereits sagte, war Lisp der Prüfstand vieler Sprachentwicklungen, einschließlich des OO-Paradigmas (obwohl die erste OO-Sprache Simula 67 aus der Algol-Familie sein könnte). Lisp ist sehr einfach und der Code für seinen grundlegenden Interpreter ist weniger als eine Seite. Aber Sie können OO-Programmierung in Lisp machen. Sie benötigen lediglich Funktionen höherer Ordnung.
Ich werde nicht die esoterische Lisp-Syntax verwenden, sondern Pseudocode, um die Darstellung zu vereinfachen. Und ich werde ein einfaches wesentliches Problem betrachten: das Verbergen von Informationen und die Modularität . Definieren einer Klasse von Objekten, ohne dass der Benutzer auf die Implementierung (größtenteils) zugreifen kann.
Angenommen, ich möchte eine Klasse namens "Vektor" erstellen, die zweidimensionale Vektoren darstellt, mit folgenden Methoden: Vektoraddition, Vektorgröße und Parallelität.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Dann kann ich den erstellten Vektor den tatsächlichen Funktionsnamen zuweisen, die verwendet werden sollen.
[vector, xcoord, ycoord, vplus, vsize, vparallel] = Vektorklasse ()
Warum so kompliziert sein? Weil ich in der Funktion vectorrec Intermediärkonstrukte definieren kann, die für den Rest des Programms nicht sichtbar sein sollen, um die Modularität zu bewahren.
Wir können eine weitere Sammlung in Polarkoordinaten erstellen
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Aber ich möchte vielleicht beide Implementierungen gleichgültig verwenden. Eine Möglichkeit besteht darin, allen Werten eine Typkomponente hinzuzufügen und alle oben genannten Funktionen in derselben Umgebung zu definieren: Dann kann ich jede der zurückgegebenen Funktionen so definieren, dass sie zuerst den Typ der Koordinaten testet und dann die spezifische Funktion anwendet dafür.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Was ich gewonnen habe: Die spezifischen Funktionen bleiben unsichtbar (aufgrund des Bereichs lokaler Bezeichner), und der Rest des Programms kann nur die abstraktesten verwenden, die vom Aufruf von vectorclass zurückgegeben wurden.
Ein Einwand ist, dass ich jede der abstrakten Funktionen im Programm direkt definieren und innerhalb der Definition der koordinatentypabhängigen Funktionen belassen könnte. Dann wäre auch versteckt. Das ist wahr, aber dann würde der Code für jeden Koordinatentyp in kleine Stücke geschnitten, die über das Programm verteilt sind, was weniger redbar und wartbar ist.
Eigentlich muss ich ihnen nicht einmal einen Namen geben, und ich könnte einfach die als anonyme Funktionswerte in einer Datenstruktur halten, die durch den Typ und eine Zeichenfolge indiziert ist, die den Funktionsnamen darstellt. Diese für den Funktionsvektor lokale Struktur wäre für den Rest des Programms nicht sichtbar.
Um die Verwendung zu vereinfachen, kann anstelle einer Funktionsliste eine einzelne Funktion mit dem Namen apply zurückgegeben werden, wobei ein expliziter Typwert und eine Zeichenfolge als Argument verwendet werden und die Funktion mit dem richtigen Typ und Namen angewendet wird. Dies ähnelt dem Aufrufen einer Methode für eine OO-Klasse.
Ich werde hier in dieser Rekonstruktion einer objektorientierten Einrichtung aufhören.
Was ich versucht habe, ist zu zeigen, dass es nicht allzu schwer ist, eine brauchbare Objektorientierung in einer ausreichend mächtigen Sprache zu erstellen, einschließlich Vererbung und anderer solcher Funktionen. Metacircularity des Interpreters kann helfen, aber meistens auf einer syntaktischen Ebene, die immer noch nicht vernachlässigbar ist.
Die ersten Benutzer der Objektorientierung experimentierten auf diese Weise mit den Konzepten. Und das gilt im Allgemeinen für viele Verbesserungen der Programmiersprachen. Natürlich spielt auch die theoretische Analyse eine Rolle und hat geholfen, diese Konzepte zu verstehen oder zu verfeinern.
Die Vorstellung, dass Sprachen ohne OO-Funktionen in einigen Projekten zum Scheitern verurteilt sind, ist jedoch einfach unbegründet. Bei Bedarf können sie die Implementierung dieser Funktionen sehr effektiv nachahmen. Viele Sprachen haben die syntaktische und semantische Kraft, um die Objektorientierung sehr effektiv durchzuführen, auch wenn sie nicht eingebaut ist. Und das ist mehr als ein Turing-Argument.
OOP geht nicht auf Einschränkungen anderer Sprachen ein, sondern unterstützt oder erzwingt Programmiermethoden , mit denen bessere Programme geschrieben werden können. Auf diese Weise können weniger erfahrene Benutzer bewährten Methoden folgen, die fortgeschrittenere Programmierer bisher ohne diese Unterstützung verwendet und entwickelt haben.
Ich glaube, ein gutes Buch, um all dies zu verstehen, könnte Abelson & Sussman sein: Struktur und Interpretation von Computerprogrammen .