Ich habe das Buch "C ++ Demystified" studiert . Jetzt habe ich angefangen, "Objektorientierte Programmierung in Turbo C ++ - Erstausgabe (1. Auflage)" von Robert Lafore zu lesen . Ich habe keine Programmierkenntnisse, die über diese Bücher hinausgehen. Dieses Buch ist möglicherweise veraltet, weil es 20 Jahre alt ist. Ich habe die neueste Ausgabe, ich benutze die alte, weil es mir gefällt, hauptsächlich studiere ich die Grundkonzepte von OOP, die in C ++ verwendet werden, durch die erste Ausgabe von Lafore's Buch.
Das Buch von Lafore betont, dass "OOP" nur für größere und komplexe Programme nützlich ist . In jedem OOP-Buch (auch in Lafore's Buch) heißt es, dass das prozedurale Paradigma fehleranfällig ist, z. B. die globalen Daten, die von den Funktionen leicht angegriffen werden können. Es wird gesagt, dass Programmierer ehrliche Fehler in prozeduralen Sprachen machen können, indem sie beispielsweise eine Funktion ausführen, die die Daten versehentlich verfälscht.
Ehrlich gesagt stelle ich meine Frage, weil ich die in diesem Buch gegebene Erklärung nicht verstehe : Objektorientierte Programmierung in C ++ (4. Ausgabe) Ich verstehe die in Lafore's Buch geschriebenen Aussagen nicht:
Die objektorientierte Programmierung wurde entwickelt, weil in früheren Ansätzen zur Programmierung Einschränkungen entdeckt wurden .... Da Programme immer umfangreicher und komplexer werden, zeigt auch der Ansatz der strukturierten Programmierung Anzeichen von Belastung ... .... Analyse der Gründe für Diese Misserfolge zeigen, dass das prozedurale Paradigma selbst Schwächen aufweist. Unabhängig davon, wie gut der strukturierte Programmieransatz implementiert ist, werden große Programme zu komplex. Es gibt zwei verwandte Probleme. Erstens haben Funktionen uneingeschränkten Zugriff auf globale Daten. Zweitens liefern unabhängige Funktionen und Daten, die die Grundlage des prozeduralen Paradigmas bilden, ein schlechtes Modell der realen Welt ...
Ich habe das Buch "dysmystified C ++" von Jeff Kent studiert, ich mag dieses Buch sehr, in diesem Buch wird hauptsächlich die prozedurale Programmierung erklärt. Ich verstehe nicht, warum prozedurale (strukturierte) Programmierung schwach ist!
Das Buch von Lafore erklärt das Konzept sehr gut mit einigen guten Beispielen. Auch ich habe durch das Lesen von Lafore's Buch die Intuition begriffen, dass OOP besser ist als prozedurale Programmierung, aber ich bin neugierig zu wissen, wie genau prozedurale Programmierung in der Praxis schwächer ist als OOP.
Ich möchte selbst sehen, welche praktischen Probleme bei der prozeduralen Programmierung auftreten und wie die OOP die Programmierung erleichtern wird. Ich denke, ich werde meine Antwort erhalten, indem ich nur nachdenklich das Buch von Lafore lese, aber ich möchte die Probleme im prozeduralen Code mit eigenen Augen sehen. Ich möchte sehen, wie der OOP-Stilcode eines Programms die oben genannten Fehler beseitigt, die auftreten würden, wenn Das gleiche Programm sollte unter Verwendung des prozeduralen Paradigmas geschrieben werden.
Es gibt viele Features von OOP und ich verstehe, dass es nicht möglich ist, mir zu erklären, wie all diese Features die oben genannten Fehler beseitigen, die beim Schreiben des Codes im prozeduralen Stil entstehen würden.
Also, hier ist meine Frage:
Welche Einschränkungen der prozeduralen Programmierung werden von OOP behoben und wie werden diese Einschränkungen in der Praxis effektiv beseitigt?
Gibt es insbesondere Beispiele für Programme, die mit dem prozeduralen Paradigma schwer zu entwerfen sind, sich aber mit OOP leicht entwerfen lassen?
PS: Cross gepostet von: /programming//q/22510004/3429430
Antworten:
In einer prozeduralen Sprache können Sie nicht unbedingt Einschränkungen ausdrücken, die erforderlich sind, um zu beweisen, dass der Aufrufer ein Modul auf unterstützte Weise verwendet. Wenn keine vom Compiler überprüfbare Einschränkung vorliegt, müssen Sie Dokumentation schreiben und hoffen, dass diese eingehalten wird, und Komponententests verwenden, um die beabsichtigten Verwendungszwecke zu demonstrieren.
Das Deklarieren von Typen ist die offensichtlichste deklarative Einschränkung (dh: "beweise, dass x ein Float ist"). Eine andere ist es, Datenmutationen dazu zu zwingen, eine Funktion zu durchlaufen, von der bekannt ist, dass sie für diese Daten ausgelegt ist. Die Durchsetzung von Protokollen (Reihenfolge der Methodenaufrufe) ist eine weitere teilweise unterstützte Einschränkung: "Konstruktor -> andere Methoden * -> Destruktor".
Es gibt auch echte Vorteile (und ein paar Nachteile), wenn der Compiler das Muster kennt. Statische Typisierung mit polymorphen Typen ist ein Problem, wenn Sie die Datenkapselung aus einer prozeduralen Sprache emulieren. Beispielsweise:
der Typ x1 ist ein Subtyp von x, t1 ist ein Subtyp von t
Dies ist eine Möglichkeit, Daten in einer prozeduralen Sprache zu kapseln, um einen Typ t mit den Methoden f und g und eine Unterklasse t1 zu erhalten, die ebenfalls Folgendes ausführt:
t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)
Um diesen Code so wie er ist zu verwenden, müssen Sie eine Typprüfung durchführen und den Typ t einschalten, bevor Sie sich für den Typ f entscheiden, den Sie aufrufen. Sie könnten das so umgehen:
Typ t {d: Daten f: Funktion g: Funktion}
Damit rufen Sie stattdessen tf (x, y, z) auf, wobei eine Typprüfung und ein Schalter zum Auffinden der Methode jetzt durch das explizite Speichern von Methodenzeigern für jede Instanz ersetzt werden. Wenn Sie nun eine große Anzahl von Funktionen pro Typ haben, ist dies eine verschwenderische Darstellung. Sie könnten dann eine andere Strategie verwenden, z. B. t auf eine Variable m zeigen zu lassen, die alle Elementfunktionen enthält. Wenn diese Funktion Teil der Sprache ist, können Sie dem Compiler überlassen, wie er mit einer effizienten Darstellung dieses Musters umgeht.
Bei der Datenkapselung wird jedoch erkannt, dass der veränderbare Zustand schlecht ist. Die objektorientierte Lösung besteht darin, sie hinter Methoden zu verstecken. Im Idealfall haben alle Methoden in einem Objekt eine genau definierte Aufrufreihenfolge (z. B .: Konstruktor -> Öffnen -> [Lesen | Schreiben] -> Schließen -> Zerstören). Dies wird manchmal als "Protokoll" bezeichnet (Recherche: "Microsoft Singularity"). Über das Konstruieren und Zerstören hinaus sind diese Anforderungen im Allgemeinen nicht Teil des Typsystems - oder gut dokumentiert. In diesem Sinne sind Objekte gleichzeitige Instanzen von Zustandsautomaten, die durch Methodenaufrufe übertragen werden. Auf diese Weise können Sie mehrere Instanzen haben und diese beliebig verschachtelt verwenden.
Wenn man jedoch erkennt, dass der veränderbare gemeinsame Status schlecht ist, kann festgestellt werden, dass die Objektorientierung zu einem Nebenläufigkeitsproblem führen kann, da die Objektdatenstruktur ein veränderbarer Status ist, auf den viele Objekte verweisen. Die meisten objektorientierten Sprachen werden im Thread des Aufrufers ausgeführt. Dies bedeutet, dass die Methodenaufrufe Race-Bedingungen enthalten. geschweige denn in nichtatomaren Folgen von Funktionsaufrufen. Alternativ könnte jedes Objekt asynchrone Nachrichten in einer Warteschlange abrufen und sie alle im Thread des Objekts (mit seinen privaten Methoden) bearbeiten und dem Aufrufer durch Senden von Nachrichten antworten.
Vergleichen Sie Java-Methodenaufrufe in einem Multithread-Kontext mit Erlang-Prozessen, indem Sie Nachrichten (die nur auf unveränderliche Werte verweisen) miteinander senden.
Uneingeschränkte Objektorientierung in Kombination mit Parallelität ist aufgrund von Sperren ein Problem. Es gibt Techniken, die von Software-Transaktionsspeicher (dh ACID-Transaktionen in einem Speicherobjekt ähnlich wie bei Datenbanken) bis zur Verwendung eines "Shared Memory by Communicating (Unveränderliche Daten)" -Ansatzes (Functional Programming Hybrid) reichen.
Meiner Meinung nach konzentriert sich die Literatur zur Objektorientierung viel zu sehr auf die Vererbung und nicht genug auf das Protokoll (Reihenfolge der überprüfbaren Methodenaufrufe, Vorbedingungen, Nachbedingungen usw.). Die Eingabe, die ein Objekt benötigt, sollte normalerweise eine genau definierte Grammatik haben, die als Typ ausgedrückt werden kann.
quelle
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.
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
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.
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 .
quelle
Ein bisschen Geschichte ist in Ordnung, denke ich.
Die Ära von Mitte der 1960er bis Mitte der 1970er Jahre wird heute als "Software-Krise" bezeichnet. Ich kann es nicht besser ausdrücken als Dijkstra in seinem Turing-Preisvortrag von 1972:
Dies war die Zeit der ersten 32-Bit-Computer, der ersten echten Multiprozessoren und der ersten eingebetteten Computer, und den Forschern war klar, dass diese in Zukunft für die Programmierung wichtig sein würden. Es war eine Zeit in der Geschichte, in der die Kundennachfrage die Programmierfähigkeiten zum ersten Mal übertraf.
Es überrascht nicht, dass es eine bemerkenswert fruchtbare Zeit in der Programmierforschung war. Vor Mitte der 1960er Jahre hatten wir LISP und AP / L, aber die "Hauptsprachen" waren grundsätzlich prozedural: FORTRAN, ALGOL, COBOL, PL / I und so weiter. Von Mitte der 1960er bis Mitte der 1970er haben wir Logo, Pascal, C, Forth, Smalltalk, Prolog, ML und Modula, und DSLs wie SQL und seine Vorgänger zählen nicht dazu.
Es war auch eine Zeit in der Geschichte, in der viele der Schlüsseltechniken zur Implementierung von Programmiersprachen entwickelt wurden. In dieser Zeit haben wir LR-Analyse, Datenflussanalyse, Eliminierung häufiger Teilausdrücke und die erste Erkenntnis erhalten, dass bestimmte Compilerprobleme (z. B. Registerzuweisung) NP-schwer waren, und haben versucht, sie als solche anzugehen.
Dies ist der Kontext, in dem OOP entstanden ist. Die Antwort der frühen 1970er Jahre auf Ihre Frage, welche Probleme OOP in der Praxis löst, ist die erste Antwort, dass sie viele der Probleme (sowohl zeitgemäß als auch antizipiert) zu lösen schien, mit denen Programmierer in dieser Periode der Geschichte konfrontiert waren. Dies ist jedoch nicht die Zeit, in der OO zum Mainstream wurde. Wir werden bald genug darauf zurückkommen.
Als Alan Kay den Begriff "objektorientiert" prägte, war das Bild, das er im Sinn hatte, dass Softwaresysteme wie ein biologisches System aufgebaut sein würden. Sie hätten so etwas wie einzelne Zellen ("Objekte"), die miteinander interagieren, indem sie etwas senden, das mit chemischen Signalen ("Nachrichten") vergleichbar ist. Sie konnten (oder würden zumindest nicht) in eine Zelle blicken; Sie würden nur über die Signalwege damit interagieren. Außerdem könnten Sie bei Bedarf mehr als eine Zelle von jeder Art haben.
Sie sehen, dass es hier einige wichtige Themen gibt: das Konzept eines genau definierten Signalisierungsprotokolls (in der modernen Terminologie eine Schnittstelle), das Konzept, die Implementierung von außen zu verbergen (in der modernen Terminologie Privatsphäre) und das Konzept von Mehrere "Dinge" desselben Typs gleichzeitig herumhängen lassen (in der modernen Terminologie Instanziierung).
Eine Sache, die Sie vielleicht bemerken, fehlt, und das ist Vererbung, und es gibt einen Grund dafür.
Objektorientierte Programmierung ist ein abstrakter Begriff, und der abstrakte Begriff kann auf verschiedene Arten in verschiedenen Programmiersprachen implementiert werden. Das abstrakte Konzept einer "Methode" könnte beispielsweise in C mithilfe von Funktionszeigern und in C ++ mithilfe von Memberfunktionen und in Smalltalk mithilfe von Methoden implementiert werden (was nicht verwunderlich sein sollte, da Smalltalk das abstrakte Konzept ziemlich direkt implementiert). Das meinen die Leute, wenn sie (zu Recht) darauf hinweisen, dass man OOP in (fast) jeder Sprache "machen" kann.
Vererbung hingegen ist ein konkretes Merkmal der Programmiersprache. Vererbung kann bei der Implementierung von OOP-Systemen hilfreich sein. Zumindest war dies bis Anfang der neunziger Jahre der Fall.
Die Zeit von Mitte der 1980er bis Mitte der 1990er Jahre war auch eine Zeit in der Geschichte, in der sich die Dinge änderten. In dieser Zeit gab es den Aufstieg des billigen, allgegenwärtigen 32-Bit-Computers, sodass es sich Unternehmen und viele Privathaushalte leisten konnten, auf jedem Schreibtisch einen Computer unterzubringen, der fast so leistungsfähig war wie die untersten Großrechner des Tages. Es war auch die Blütezeit von Dies war auch die Ära des Aufstiegs der modernen Benutzeroberfläche und des vernetzten Betriebssystems.
In diesem Zusammenhang entstand die objektorientierte Analyse und das objektorientierte Design.
Der Einfluss von OOAD, der Arbeit der "drei Amigos" (Booch, Rumbar und Jacobson) und anderer (z. B. die Shlaer-Mellor-Methode, verantwortungsbewusstes Design usw.) ist nicht zu unterschätzen. Dies ist der Grund, warum die meisten neuen Sprachen, die seit Anfang der neunziger Jahre entwickelt wurden (zumindest die meisten, von denen Sie gehört haben), Objekte im Simula-Stil haben.
Die Antwort der neunziger Jahre auf Ihre Frage lautet also, dass sie die beste (derzeitige) Lösung für domänenorientierte Analyse- und Entwurfsmethoden unterstützt.
Seitdem haben wir, weil wir einen Hammer hatten, OOP auf so ziemlich jedes Problem angewendet, das seitdem aufgetreten ist. OOAD und das verwendete Objektmodell ermutigten und ermöglichten eine agile und testgetriebene Entwicklung, Cluster und andere verteilte Systeme usw.
Moderne GUIs und alle Betriebssysteme, die in den letzten 20 Jahren entwickelt wurden, bieten ihre Dienste in der Regel als Objekte an. Daher erfordert jede neue praktische Programmiersprache zumindest eine Möglichkeit, sich an die Systeme zu binden, die wir heute verwenden.
Die moderne Antwort lautet also: Sie löst das Problem der Anbindung an die moderne Welt. Die moderne Welt baut auf OOP aus dem gleichen Grund wie die Welt von 1880 auf Dampf: Wir verstehen es, wir können es kontrollieren und es macht die Arbeit gut genug.
Das heißt natürlich nicht, dass die Forschung hier aufhört, aber es deutet nachdrücklich darauf hin, dass jede neue Technologie OO als Grenzfall benötigt. Sie müssen nicht OO sein, aber Sie können nicht grundsätzlich unvereinbar damit sein.
quelle
Wirklich keine. Genau genommen löst OOP ein Problem nicht wirklich. Es gibt nichts, was Sie mit einem objektorientierten System tun können, was Sie mit einem nicht objektorientierten System nicht tun können - in der Tat gibt es nichts, was Sie mit einer Turing-Maschine nicht tun können. Letztendlich verwandelt sich alles in Maschinencode, und ASM ist sicherlich nicht objektorientiert.
Was das OOP-Paradigma für Sie bedeutet, ist, dass es das Organisieren von Variablen und Funktionen erleichtert und Sie sie leichter zusammen verschieben können.
Angenommen, ich möchte ein Kartenspiel in Python schreiben. Wie würde ich die Karten darstellen?
Wenn ich nicht über OOP Bescheid wüsste, könnte ich es so machen:
Ich würde wahrscheinlich einen Code schreiben, um diese Karten zu generieren, anstatt sie nur von Hand zu schreiben, aber Sie verstehen, worum es geht. "1S" steht für die Pik-1, "JD" für den Diamantenbuben und so weiter. Ich würde auch einen Code für den Joker brauchen, aber wir tun einfach so, als gäbe es momentan keinen Joker.
Wenn ich jetzt das Deck mischen möchte, muss ich nur die Liste "mischen". Um dann eine Karte vom Stapel zu nehmen, entferne ich den obersten Eintrag von der Liste und gebe mir die Zeichenfolge. Einfach.
Wenn ich nun herausfinden möchte, mit welcher Karte ich arbeite, um sie dem Player anzuzeigen, benötige ich eine Funktion wie die folgende:
Ein bisschen groß, lang und ineffizient, aber es funktioniert (und ist sehr unpythonisch, aber das ist hier nicht der springende Punkt).
Was ist nun, wenn ich möchte, dass sich Karten auf dem Bildschirm bewegen können? Ich muss ihre Position irgendwie speichern. Ich könnte es an das Ende ihres Kartencodes anfügen, aber das könnte etwas unhandlich sein. Lassen Sie uns stattdessen eine andere Liste der Positionen der einzelnen Karten erstellen:
Dann schreibe ich meinen Code so, dass der Index der Position jeder Karte in der Liste mit dem Index der Karte selbst im Stapel übereinstimmt.
Zumindest sollte es so sein. Es sei denn, ich mache einen Fehler. Das könnte ich sehr gut, weil mein Code ziemlich komplex sein muss, um mit diesem Setup umzugehen. Wenn ich die Karten mischen möchte, muss ich die Positionen in derselben Reihenfolge mischen. Was passiert, wenn ich eine Karte ganz aus dem Stapel nehme? Ich muss auch seine Position herausnehmen und es woanders hinstellen.
Und wenn ich noch mehr Informationen über die Karten speichern möchte? Was ist, wenn ich speichern möchte, ob jede Karte umgedreht ist? Was ist, wenn ich eine Physik-Engine haben möchte und auch die Geschwindigkeit der Karten kennen muss? Ich brauche eine andere Liste, um die Grafiken für jede Karte zu speichern! Und für all diese Datenpunkte benötige ich einen separaten Code, um sie alle richtig zu organisieren, damit jede Karte irgendwie all ihren Daten zugeordnet werden kann!
Versuchen wir es jetzt auf die OOP-Art.
Anstelle einer Liste von Codes definieren wir eine Kartenklasse und erstellen daraus eine Liste von Kartenobjekten.
Jetzt ist plötzlich alles viel einfacher. Wenn ich die Karte bewegen möchte, muss ich nicht herausfinden, wo sie sich im Stapel befindet, und dann muss ich sie verwenden, um ihre Position aus der Reihe der Positionen zu entfernen. Ich muss nur sagen
thecard.pos=newpos
. Wenn ich die Karte aus der Hauptstapelliste nehme, muss ich keine neue Liste erstellen, um alle anderen Daten zu speichern. Wenn sich das Kartenobjekt bewegt, bewegen sich alle seine Eigenschaften mit. Und wenn ich eine Karte haben möchte, die sich beim Umdrehen anders verhält, muss ich die Umschaltfunktion in meinem Hauptcode nicht ändern, damit sie diese Karten erkennt und sich anders verhält. Ich muss nur Card unterordnen und die Funktion flip () in der Unterklasse ändern.Aber nichts, was ich dort getan habe, wäre ohne OO nicht möglich gewesen. Es ist nur so, dass bei einer objektorientierten Sprache die Sprache einen großen Teil der Arbeit leistet, um die Dinge für Sie zusammenzuhalten, was bedeutet, dass Sie viel weniger Chancen haben, Fehler zu machen, und Ihr Code kürzer und einfacher zu lesen und zu schreiben ist.
Oder, um noch mehr zusammenzufassen, mit OO können Sie einfachere Programme schreiben, die die gleiche Arbeit leisten wie komplexere Programme, indem Sie viele der üblichen Komplexitäten beim Umgang mit Daten hinter einem Schleier der Abstraktion verbergen.
quelle
Nachdem ich einige Jahre lang Embedded C geschrieben habe, um Geräte, serielle Schnittstellen und Kommunikationspakete zwischen seriellen Schnittstellen, Netzwerkschnittstellen und Servern zu verwalten; Ich befand mich als ausgebildeter Elektrotechniker mit begrenzter Erfahrung in der prozeduralen Programmierung und entwickelte meine eigenen Abstraktionen von der Hardware, die sich in dem manifestierten, was ich später erkannte, was normale Leute als "objektorientierte Programmierung" bezeichnen.
Als ich zur Serverseite überging, war ich einer Fabrik ausgesetzt, die die Objektdarstellung jedes Geräts im Speicher bei der Instantiierung einrichtete. Ich verstand weder die Worte noch das, was zuerst geschah - ich wusste nur, dass ich zu der Datei mit dem Namen so und so ging und Code schrieb. Später erkannte ich endlich wieder den Wert des OOP.
Ich persönlich denke, dies ist der einzige Weg, um Objektorientierung zu lehren. Ich hatte in meinem ersten Jahr eine Einführung in die OOP (Java) -Klasse und es war völlig über meinem Kopf. OOP-Beschreibungen, die darauf beruhen, Kätzchen-> Katze-> Säugetier-> Lebendige-> Ding-> Blatt-> Zweig-> Baum-> Garten zu klassifizieren, sind meiner bescheidenen Meinung nach absolut lächerliche Methoden, weil niemand jemals versuchen wird, diese zu lösen Probleme, wenn man sie auch Probleme nennen könnte ...
Ich denke, es ist einfacher, Ihre Frage zu beantworten, wenn Sie sie in weniger absoluten Begriffen betrachten - nicht "Was löst sie", sondern eher aus der Perspektive "Hier ist ein Problem, und so wird es einfacher". In meinem speziellen Fall mit seriellen Ports hatten wir eine Reihe von #ifdefs zur Kompilierungszeit, die Code hinzufügten und entfernten, der die seriellen Ports statisch öffnete und schloss. Die Funktionen zum Öffnen von Ports wurden überall aufgerufen und konnten sich an einer beliebigen Stelle in den 100.000 Zeilen des Betriebssystemcodes befinden, und die IDE hat nicht ausgegraut, was nicht definiert war - Sie mussten dies manuell aufspüren und Trage es in deinem Kopf. Unweigerlich könnten Sie mehrere Aufgaben haben, die versuchen, einen bestimmten seriellen Port zu öffnen, wobei das Gerät am anderen Ende erwartet wird. Dann funktioniert keiner der Codes, die Sie gerade geschrieben haben, und Sie können nicht herausfinden, warum.
Die Abstraktion war, obwohl immer noch in C, eine "Klasse" für serielle Ports (also nur ein Strukturdatentyp), für die wir ein Array von - einem für jeden seriellen Port - hatten und stattdessen [das DMA-Äquivalent in seriellen Ports] hatten. "OpenSerialPortA" "SetBaudRate" usw. Funktionen, die von der Task direkt auf der Hardware aufgerufen wurden. Wir haben eine Hilfsfunktion aufgerufen, an die Sie alle Kommunikationsparameter (Baud, Parität usw.) übergeben haben. Diese hat zuerst das Strukturarray überprüft, um festzustellen, ob dies der Fall ist port wurde bereits geöffnet - wenn ja, durch welche Task, die es Ihnen als Debug printf mitteilt, so dass Sie sofort zu dem zu deaktivierenden Codeabschnitt springen können - und wenn nicht, setzte es die Einstellung fort alle Parameter über ihre HAL-Assembly-Funktionen und öffnete schließlich den Port.
Natürlich gibt es auch Gefahren für OOP. Als ich endlich die Codebasis aufgeräumt und alles sauber und ordentlich gemacht hatte - das Schreiben neuer Treiber für diese Produktlinie war schließlich eine berechenbare, vorhersehbare Wissenschaft, und mein Manager EOL hat das Produkt speziell deswegen entwickelt, weil es ein Projekt weniger war, das er benötigen würde zu verwalten, und er war Grenze entfernbare Mittelverwaltung. Das hat mich sehr entmutigt und ich habe meinen Job gekündigt. LOL.
quelle
Es gibt viele Behauptungen und Absichten darüber, was / wo OOP-Programmierung einen Vorteil gegenüber prozeduraler Programmierung hat, auch durch seine Erfinder und Benutzer. aber nur , weil eine Technologie wurde entworfen für einen bestimmten Zweck durch seine Designer nicht garantieren , dass es gelingt , in diesen Zielen. Dies ist ein Schlüsselverständnis auf dem Gebiet des Software-Engineerings nach Brooks berühmtem Essay "No Silver Bullet", das trotz der OOP-Codierungsrevolution immer noch relevant ist. (Siehe auch den Gartner Hype Cycle für neue Technologien.)
Viele, die beide verwendet haben, haben auch Meinungen aus anekdotischen Erfahrungen, und dies hat einen gewissen Wert, aber es ist in vielen wissenschaftlichen Studien bekannt, dass selbst berichtete Analysen ungenau sein können. Es scheint kaum eine quantitative Analyse dieser Unterschiede zu geben, oder wenn es solche gibt, wird sie nicht so oft zitiert. es ist ein wenig erstaunlich , wie viele Computer Wissenschaftler von zentralen Bedeutung für ihr Feld autoritativ zu bestimmten Themen sprechen noch nicht wirklich zitieren wissenschaftliche Forschung , um ihre Ansichten zu unterstützen und erkennen nicht, sie geben tatsächlich auf konventionelle Weisheit in ihrem Bereich (wenn auch weit verbreitet ).
da es sich um eine wissenschaftliche Website / ein wissenschaftliches Forum handelt, wird hier kurz versucht, die zahlreichen Meinungen auf eine solidere Grundlage zu stellen und den tatsächlichen Unterschied zu quantifizieren . Es kann andere Studien geben und hoffen, dass andere sie darauf hinweisen, wenn sie davon hören. (zen frage: wenn es wirklich einen großen unterschied gibt und soviel massiven aufwand im bereich der kommerziellen softwareentwicklung und anderswo in die realisierung gesteckt wurde, warum sollten wissenschaftliche nachweise dafür so schwer zu bekommen sein? sollte es keinen geben eine klassische, vielzitierte Referenz auf dem Gebiet, die den Unterschied definitiv quantifiziert?)
Dieses Papier verwendet experimentelle / quantitative / wissenschaftliche Analysen und unterstützt insbesondere, dass das Verständnis für unerfahrene Programmierer in einigen Fällen durch OOP-Codierungsmethoden verbessert wurde, in anderen Fällen jedoch nicht schlüssig war (im Verhältnis zur Programmgröße). Beachten Sie, dass dies nur eine der vielen / wichtigsten Behauptungen ist, wonach OOP-Überlegenheit in anderen Antworten und von OOP-Befürwortern vertreten wird. In der Studie wurde möglicherweise ein psychologisches Element gemessen, das als "kognitive Belastung / Overhead" für das Codierungsverständnis bekannt ist.
Ein Vergleich des Verständnisses von objektorientierten und prozeduralen Programmen durch unerfahrene Programmierer, die mit Computern interagieren. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L. Corritore (1999)
siehe auch:
Was ist der Vorteil der objektorientierten Programmierung gegenüber der prozeduralen Programmierung? programmers.se
Hat die prozedurale Programmierung irgendwelche Vorteile gegenüber OOP? Paketüberfluss
quelle
Achtung. Lesen Sie den Klassiker von R. King "Meine Katze ist objektorientiert" in "Objektorientierte Konzepte, Datenbanken und Anwendungen" (Kim und Lochovsky, Hrsg.) (ACM, 1989). "Objektorientiert" ist mehr ein Modewort als ein klares Konzept geworden.
Außerdem gibt es viele Variationen des Themas, die wenig gemeinsam haben. Es gibt prototypbasierte Sprachen (Vererbung erfolgt von Objekten, es gibt keine Klassen als solche) und klassenbasierte Sprachen. Es gibt Sprachen, die Mehrfachvererbung zulassen, andere nicht. Einige Sprachen haben eine Idee wie Javas Schnittstellen (können als eine Form der Verwässerung der Mehrfachvererbung verstanden werden). Es gibt die Idee von Mixins. Die Vererbung kann ziemlich streng (wie in C ++, kann nicht wirklich ändern, was Sie in einer Unterklasse erhalten) oder sehr großzügig gehandhabt werden (in Perl kann die Unterklasse fast alles neu definieren). Einige Sprachen haben eine einzige Wurzel für die Vererbung (normalerweise Object genannt, mit Standardverhalten), andere erlauben es dem Programmierer, mehrere Bäume zu erstellen. Einige Sprachen bestehen darauf, dass "alles ein Objekt ist", andere behandeln Objekte und Nicht-Objekte, Einige (wie Java) haben "die meisten sind Objekte, aber diese wenigen Typen hier sind es nicht". Einige Sprachen bestehen auf einer strikten Kapselung des Zustands in Objekten, andere machen dies optional (C ++ 'privat, geschützt, öffentlich), andere haben überhaupt keine Kapselung. Wenn Sie eine Sprache wie Scheme aus dem richtigen Winkel betrachten, wird OOP ohne besonderen Aufwand eingebaut (kann Funktionen definieren, die Funktionen zurückgeben, die einen lokalen Zustand einschließen).
quelle
Die objektorientierte Programmierung befasst sich mit den Datensicherheitsproblemen, die bei der prozeduralen Programmierung auftreten. Hierzu wird das Konzept der Kapselung der Daten verwendet, sodass nur die legitimen Klassen die Daten erben können. Zugriffsmodifikatoren erleichtern das Erreichen dieses Ziels. Ich hoffe, das hilft.:)
quelle