Vermeidung objektorientierter Fallstricke, Migration von C, Was hat bei Ihnen funktioniert?

12

Ich programmiere seit einiger Zeit in prozeduralen Sprachen und meine erste Reaktion auf ein Problem besteht darin, es in Aufgaben zu zerlegen, anstatt die verschiedenen Entitäten (Objekte), die existieren, und ihre Beziehungen zu berücksichtigen.

Ich habe einen Universitätskurs in OOP absolviert und verstehe die Grundlagen von Kapselung, Datenabstraktion, Polymorphismus, Modularität und Vererbung.

Ich lese /programming/2688910/learning-to-think-in-the-object-oriented-way und /programming/1157847/learning-object-oriented-thinking , und wir werden uns einige der Bücher ansehen, auf die in diesen Antworten verwiesen wird.

Ich denke, dass einige meiner mittleren bis großen Projekte von der effektiven Nutzung von OOP profitieren werden, aber als Anfänger möchte ich zeitaufwendige, häufige Fehler vermeiden.

Welche Fallstricke haben Sie aufgrund Ihrer Erfahrungen und welche Umgehungsmöglichkeiten bieten sich an? Wenn Sie erklären könnten, warum es sich um Fallstricke handelt und wie effektiv Ihr Vorschlag bei der Lösung des Problems ist, wären Sie dankbar.

Ich denke an etwas wie "Ist es üblich, eine ganze Reihe von Beobachter- und Modifikatormethoden zu haben und private Variablen zu verwenden, oder gibt es Techniken, um diese zu konsolidieren / zu reduzieren?"

Ich mache mir keine Sorgen, C ++ als reine OO-Sprache zu verwenden, wenn es gute Gründe gibt, Methoden zu mischen. (Erinnert an die Gründe für den sparsamen Einsatz von GOTOs.)

Vielen Dank!

Stephen
quelle
2
Keine vollständige Antwort verdient, aber eine wichtige, deren Akzeptanz mir sehr viel Zeit gekostet hat (dh ich habe viel darüber gelesen, sie aber als Fanboy-Vortrag behandelt): Ich bevorzuge kostenlose Funktionen gegenüber Mitgliederfunktionen. Dies hält Ihre Klassen minimal, was eine gute Sache ist.
Mittwoch,
@stijn Du sagst im Grunde, wenn es nicht in der Klasse sein muss, lege es nicht dort ab. Zum Beispiel sehe ich eine Menge Utility-Member-Funktionen, bei denen es sich leicht um freie Funktionen im Code handeln könnte, den ich bisher gelesen habe.
Stephen
ja das ist es. Wenn Sie nach "Nicht-Mitglied-Nicht-Freund bevorzugen" suchen, finden Sie viele Informationen dazu. Am Ende kommt es darauf an, das Prinzip der einheitlichen
Verantwortung einzuhalten

Antworten:

10

Eine große Sache, die ich gelernt habe, ist das Entwerfen von Klassen von außen nach innen. Entwerfen Sie die Benutzeroberfläche, bevor Sie überhaupt über die Implementierung nachdenken. Dies macht die Klasse für Ihre Benutzer (die die Klasse verwenden) viel intuitiver als das Schreiben der zugrunde liegenden Algorithmen und das Erstellen der Klasse sowie das Schreiben neuer Funktionen für öffentliche Mitglieder, wenn diese benötigt werden.

Dominic Gurto
quelle
7
Darüber hinaus können wir "absichtlich programmieren", dh einen Beispielcode schreiben, der die neue Klasse verwenden würde, und herausfinden, mit welchen Methoden und Richtlinien die Verwendung komfortabel ist. Verwenden Sie diese Informationen dann als Grundlage für die Implementierung.
2
In der Theorie großartig - Schnittstellen entwerfen und Sie sind im Grunde fertig. Aber in der Praxis funktioniert das nicht so einfach. Interface-Design und -Implementierung gehen oft in aufeinander folgenden Iterationen Hand in Hand, bis das endgültige Interface kristallisiert ist. Zu diesem Zeitpunkt haben Sie wahrscheinlich auch die endgültige Implementierung.
Gene Bushuyev
9

Nun, das erste ist die Gefahr, zu viele Informationen preiszugeben. Die Standardeinstellung sollte privatenicht sein public.

Danach kommen zu viele Getter / Setter. Angenommen, ich habe ein Datenelement. Brauche ich diese Daten wirklich in einer anderen Klasse? Ok, mach einen Getter. Muss ich diese Daten wirklich während der Lebensdauer des Objekts ändern? Dann mach einen Setter.

Die meisten unerfahrenen Programmierer haben die Standardeinstellung, für jedes Datenelement einen Getter / Setter zu erstellen. Dies wirft Schnittstellen auf und ist oft eine schlechte Designauswahl.

orlp
quelle
2
Ja, es ist ziemlich beliebt bei denen, die das Kapseln oberflächlich verstanden haben, um Daten privat zu machen und dann die Kapselung mit Gettern und Setzern zu blasen.
Gene Bushuyev
Java ist die einzige populäre Sprache, die noch Get / Set-Methoden benötigt. Jede andere Sprache unterstützt Eigenschaften, sodass kein syntaktischer Unterschied zwischen einem öffentlichen Datenelement und einer Eigenschaft besteht. Selbst in Java ist es keine Sünde, öffentliche Datenmitglieder zu haben, wenn Sie auch alle Benutzer der Klasse kontrollieren.
Kevin Cline
Öffentliche Datenmitglieder sind schwerer zu pflegen. Mit Gettern / Settern (oder Eigenschaften) können Sie die Schnittstelle beibehalten, während Sie die interne Datendarstellung ändern, ohne externen Code zu ändern. In Sprachen, in denen Eigenschaften aus Verbrauchersicht syntaktisch mit Elementvariablen identisch sind, gilt dieser Punkt jedoch nicht.
tdammers
Bisher denke ich, dass ich private Mitglieder als lokale Variablen in einer Funktion betrachte ... der Rest der Welt muss nichts über sie wissen, damit die FN funktioniert ... und ob die FN Änderungen, nur die Schnittstelle muss konsistent sein. Ich tendiere nicht dazu, mit Spam Getter / Setter zu arbeiten, also bin ich vielleicht auf dem richtigen Weg. Wenn ich mir eine Pufferklasse anschaue, die ich geschrieben habe, gibt es überhaupt keine öffentlichen Datenmitglieder. Vielen Dank!
Stephen
2

Als ich diesen Abgrund überquerte, entschied ich mich für den folgenden Ansatz:

0) Ich habe langsam angefangen, mit kleinen / mittleren prozeduralen Apps und ohne wichtige Aufgaben.

1) Eine einfache Zuordnung im ersten Durchgang, wie ich das Programm von Grund auf im OO-Stil schreiben würde - was für mich damals am wichtigsten war und das ist subjektiv -, bestand darin, alle Basisklassen herauszufinden. Mein Ziel war es, so viel wie möglich in den Basisklassen zu kapseln. Reine virtuelle Methoden für alles Mögliche in den Basisklassen.

2) Im nächsten Schritt wurden die Ableitungen erstellt.

3) Der letzte Schritt war - im ursprünglichen Verfahrenscode die Datenstrukturen vom Beobachter- / Modifikatorcode zu trennen. Verwenden Sie dann das Ausblenden von Daten, und ordnen Sie alle allgemeinen Daten den Basisklassen zu. In den Unterklassen wurden die Daten abgelegt, die im gesamten Programm nicht üblich waren. Und die gleiche Behandlung für den prozeduralen Beobachter / Modifizierer-Code - die gesamte Logik, die überall verwendet wird, floss in die Basisklassen. Die Logik zum Anzeigen / Ändern, die nur auf eine Teilmenge der Daten angewendet wurde, wurde in die abgeleiteten Klassen übernommen.

Dies ist subjektiv, aber es ist SCHNELL, wenn Sie den Verfahrenscode und die Datenstrukturen gut kennen. Ein FAIL in einer Codeüberprüfung liegt vor, wenn ein Teil der Daten oder der Logik nicht in den Basisklassen enthalten ist, sondern überall verwendet wird.


quelle
2

Werfen Sie einen Blick auf andere erfolgreiche Projekte, die OOP verwenden, um ein Gefühl für guten Stil zu bekommen. Ich empfehle, mir Qt anzuschauen, ein Projekt, an dem ich mich immer orientiere, wenn ich meine eigenen Designentscheidungen treffe.

rcv
quelle
Ja! Bevor ein Mensch ein Meister von etwas wird, lernt er, die großen Meister nachzuahmen, die vor ihm gearbeitet haben. Das Studieren von gutem Code, der von anderen implementiert wurde, ist ein guter Weg, um zu lernen. Ich würde empfehlen, Boost zu betrachten, angefangen bei einfachen Designs und tiefer zu gehen, wenn sich das Verständnis verbessert.
Gene Bushuyev
1

Je nachdem, mit wem Sie sprechen, sollten alle Daten in OOP privat oder geschützt sein und nur über Accessoren und Mutatoren verfügbar sein. Im Allgemeinen halte ich dies für eine gute Praxis, aber es gibt Fälle, in denen ich von dieser Norm abweiche. Wenn Sie beispielsweise eine Klasse haben (in Java beispielsweise), deren einziger Zweck darin besteht, einige Daten zu einer logischen Einheit zusammenzufassen, ist es sinnvoll, die Felder öffentlich zu lassen. Wenn es sich um eine unveränderliche Kapsel handelt, markieren Sie sie einfach als endgültig und initialisieren Sie sie im Konstruktor. Dies reduziert die Klasse (in diesem Fall) auf etwas mehr als eine Struktur (in C ++ sollten Sie dies eigentlich eine Struktur nennen. Funktioniert genau wie eine Klasse, aber die Standardsichtbarkeit ist öffentlich und Ihre Absicht ist klarer), aber Ich denke, Sie werden feststellen, dass die Verwendung in diesen Fällen viel komfortabler ist.

Eine Sache, die Sie definitiv nicht tun möchten, ist, ein Feld zu haben, das auf Konsistenz im Mutator überprüft werden muss, und es öffentlich zu lassen.

jpm
quelle
3
Ich würde auch vorschlagen, wenn Sie solche Klassen haben, die nur öffentliche Daten enthalten, sollten Sie sie als structs deklarieren - es macht keinen semantischen Unterschied, aber es verdeutlicht die Absicht und lässt ihre Verwendung natürlicher erscheinen, insbesondere für C-Programmierer.
1
Ja, ich stimme hier zu, wenn Sie in C ++ sind (was in der Frage erwähnt wurde), aber ich arbeite den größten Teil meiner Arbeit in Java, und wir haben leider keine Struktur.
Sie müssen klar zwischen aggregierten Klassen (seltener Fall) und Klassen mit Funktionalität (allgemeiner Fall) unterscheiden. Letzterer muss die Kapselung üben und darf nur über die öffentliche Schnittstelle auf seine Mitglieder zugreifen. Es dürfen keine Daten öffentlich zugänglich gemacht werden.
Gene Bushuyev
1

Kent Becks Buch Implementation Patterns bietet eine hervorragende Grundlage für den Einsatz objektorientierter Mechanismen, nicht für den Missbrauch.

Steven A. Lowe
quelle
1

Ich mache mir keine Sorgen, C ++ als reine OO-Sprache zu verwenden, wenn es gute Gründe gibt, Methoden zu mischen. (Erinnert an die Gründe für den sparsamen Einsatz von GOTOs.)

Ich dachte nicht wirklich, dass ich viel zu bieten hätte, bis ich dieses Stück gesehen habe. Ich muss dem Gefühl nicht zustimmen. OOP ist nur eines der Paradigmen, die in C ++ verwendet werden können und sollten. Ehrlich gesagt, meiner Meinung nach ist es nicht eines der stärksten Merkmale.

Aus Sicht von OO denke ich, dass C ++ tatsächlich ein bisschen zu kurz kommt. Die Idee, nicht-virtuelle Funktionen zu haben, spricht in dieser Hinsicht dagegen. Ich habe mit denen gestritten, die mit mir nicht übereinstimmen, aber nicht-virtuelle Mitglieder passen einfach nicht zum Paradigma, was mich betrifft. Polymorphismus ist eine Schlüsselkomponente von OO, und Klassen mit nicht virtuellen Funktionen sind im Sinne von OO nicht polymorph. Als OO-Sprache denke ich, dass C ++ im Vergleich zu Sprachen wie Java oder Objective-C eher schwach ist.

Generische Programmierung auf der anderen Seite hat C ++ diese ziemlich gut. Ich habe gehört, dass es auch dafür bessere Sprachen gibt, aber die Kombination von Objekten und generischen Funktionen ist etwas ziemlich Mächtiges und Ausdrucksstarkes. Außerdem kann es sowohl in der Programmierzeit als auch in der Verarbeitungszeit verdammt schnell sein. Es ist wirklich in diesem Bereich, dass ich denke, C ++ scheint, obwohl es zugegebenermaßen besser sein könnte (Sprachunterstützung für Konzepte zum Beispiel). Jemand, der denkt, dass er sich an das OO-Paradigma halten und die anderen in der Reihenfolge der goto-Anweisung in Stufen der Unmoral behandeln sollte, verpasst es wirklich, wenn er sich dieses Paradigma nicht ansieht.

Die Metaprogrammierungskapazität von Vorlagen ist ebenfalls beeindruckend. Schauen Sie sich zum Beispiel die Boost.Units-Bibliothek an. Diese Bibliothek bietet Unterstützung für Maßgrößen. Ich habe diese Bibliothek in dem Ingenieurbüro, für das ich derzeit arbeite, ausgiebig genutzt. Es bietet nur eine viel schnellere Rückmeldung für einen Aspekt des möglichen Programmierers oder sogar einen Spezifikationsfehler. Es ist unmöglich, ein Programm zu kompilieren, das eine Formel verwendet, bei der beide Seiten des Operators '=' ohne explizite Umwandlung nicht dimensional äquivalent sind. Ich persönlich habe keine Erfahrung mit einer anderen Sprache, in der dies möglich ist, und schon gar nicht mit einer, die auch die Leistung und Geschwindigkeit von C ++ hat.

Metaprogrammierung ist ein rein funktionales Paradigma.

Also wirklich, ich denke, Sie betreten C ++ bereits mit einigen unglücklichen Missverständnissen. Die anderen Paradigmen neben OO sind nicht zu vermeiden, sie sind zu GEHEBELN. Verwenden Sie das Paradigma, das für den Aspekt des Problems, an dem Sie arbeiten, natürlich ist. Erzwingen Sie keine Objekte auf etwas, das im Wesentlichen kein objektanfälliges Problem ist. Für mich ist OO nicht einmal die halbe Wahrheit für C ++.

Edward Strange
quelle
Bietet das Schlüsselwort virtual keine Funktionalität für virtuelle Klassen in C ++? Was den goto-Kommentar anbelangt, war das, dass ich mir keine Sorgen darüber mache, empirische Regeln zu brechen, solange ich die Argumentation verstehe. Ich habe mich jedoch weiter darum bemüht, Imperative / Verfahrensweisen zugunsten von OO zu meiden, als dies möglicherweise notwendig gewesen wäre. Vielen Dank.
Stephen
Virtuelle Methoden sind keine Voraussetzung für Polymorphismus, sondern nur eine übliche Methode. In der Tat ist alles andere gleich. Wenn Sie dann eine Methode virtuell machen, wird die Kapselung tatsächlich geschwächt, da Sie die Größe der API der Klasse erhöhen und es schwieriger machen, sicherzustellen, dass Sie Liskov folgen und so weiter. Machen Sie etwas Virtuelles nur, wenn Sie eine Naht benötigen, einen Ort, an dem Sie über die Vererbung neues Verhalten einfügen können (obwohl die Vererbung in OOP ebenfalls etwas vorsichtiges ist). virtuell um virtuelles willen macht eine Klasse "mehr OOP"
sara
1

Ich wollte eine Antwort auf diese Frage akzeptieren, konnte mich aber nicht für eine Antwort entscheiden, um das Häkchen zu setzen. Als solche habe ich die Originalautoren aufgewertet und diese als zusammenfassende Antwort erstellt. Vielen Dank an alle, die sich ein paar Minuten Zeit genommen haben. Ich habe festgestellt, dass der von Ihnen gewährte Einblick mir eine gute Richtung und ein wenig die Gewissheit gab, dass ich nicht von der Stange bin.

@nightcracker

Nun, das erste ist die Gefahr, zu viele Informationen preiszugeben. Die Standardeinstellung sollte privat und nicht öffentlich sein. Danach kommen zu viele Getter / Setter.

Ich hatte das Gefühl, dieses Problem in der Vergangenheit in Aktion beobachtet zu haben. Ihre Kommentare haben mich auch daran erinnert, dass ich durch das Ausblenden der zugrunde liegenden Variablen und ihrer Implementierung die Möglichkeit habe, ihre Implementierung zu ändern, ohne irgendetwas zu zerstören, das von ihnen abhängig ist.

Dominic Gurto

Entwerfen Sie die Benutzeroberfläche, bevor Sie überhaupt an die Implementierung denken. Design und Implementierung der Gene Bushuyev-Schnittstelle gehen oft in aufeinander folgenden Iterationen Hand in Hand, bis die endgültige Schnittstelle kristallisiert ist.

Ich fand, dass Dominics Kommentar ein großartiges Ideal war, um danach zu streben, aber ich denke, dass Gens Kommentar wirklich die Realität der Situation trifft. Bisher habe ich das in Aktion gesehen ... und fühle mich ein bisschen besser, dass es nicht ungewöhnlich ist. Ich denke, wenn ich als Programmierer reife, neige ich zu vollständigeren Designs, aber im Moment leide ich immer noch darunter, mich einzumischen und Code zu schreiben.

wantTheBest

Ich habe langsam angefangen, mit kleinen / mittleren prozeduralen Apps und ohne unternehmenskritische Dinge bei der Arbeit. Trennen Sie im ursprünglichen Prozedurcode die Datenstrukturen vom Beobachter- / Modifikatorcode

Das macht sehr viel Sinn ... Ich mochte die Idee, die Dinge am Laufen zu halten, aber einige der unkritischen Dinge mit Klassen zu überarbeiten.

jpm

Eine Sache, die Sie definitiv nicht tun möchten, ist, ein Feld zu haben, das auf Konsistenz im Mutator überprüft werden muss, und es öffentlich zu lassen

Ich habe seit einiger Zeit gewusst, dass dies eine der Stärken der Kapselung von Daten ist.

Verrückter Eddie

Jemand, der denkt, dass er sich an das OO-Paradigma halten und die anderen in der Reihenfolge der goto-Anweisung in Stufen der Unmoral behandeln sollte, verpasst es wirklich, wenn er sich dieses Paradigma nicht ansieht. Die Metaprogrammierungskapazität von Vorlagen ist ebenfalls beeindruckend.

Ich habe Crazy Eddies Antwort ursprünglich sehr vermisst, weil ich einige der genannten Themen nicht gelesen hatte ... wie Metaprogrammierung. Ich denke, die großartige Botschaft in CEs Post war, dass C ++ eine solche Mischung aus Fähigkeiten und Stilen ist, dass jeder sein bestes Potenzial ausschöpfen sollte ... auch zwingend, wenn das Sinn macht.

Nochmals vielen Dank an alle, die geantwortet haben!

Stephen
quelle
0

Die größte Gefahr ist der Glaube, dass OOP eine Silberkugel oder das "perfekte Paradigma" ist.

Alternative
quelle