Warum haben private Felder nicht genug geschützt?

265

Ist die Sichtbarkeit privatevon Klassenfeldern / Eigenschaften / Attributen sinnvoll? In OOP werden Sie früher oder später eine Unterklasse einer Klasse erstellen. In diesem Fall ist es gut, die Implementierung zu verstehen und sie vollständig ändern zu können.

Eines der ersten Dinge, die ich mache, wenn ich eine Klasse untergeordnete, ist die Änderung einer Reihe von privateMethoden in protected. Es ist jedoch wichtig , Details vor der Außenwelt zu verbergen - also brauchen wir protectedund nicht nur public.

Meine Frage ist: Kennen Sie einen wichtigen Anwendungsfall, bei dem es sich privatenicht protectedum ein gutes Tool handelt, oder würden zwei Optionen " protected& public" für OOP-Sprachen ausreichen?

Adam Libuša
quelle
236
An die Abwähler: Obwohl ich mit den Räumlichkeiten des OP nicht einverstanden bin, stimme ich dieser Frage zu, weil sie absolut kohärent und es wert ist, beantwortet zu werden. Ja, dem OP muss gesagt werden, warum dies falsch ist, aber der Weg, dies zu tun, besteht darin , eine Antwort zu schreiben (oder Änderungen an vorhandenen Antworten vorzuschlagen), und nicht, nur weil er es noch nicht selbst herausgefunden hat.
Ixrec
18
Abgeleitete Klassen sind Teil der Außenwelt.
CodesInChaos
20
Vergessen Sie nicht, dass geschützt nicht immer bedeutet, dass der Zugriff auf die Vererbungshierarchie gesperrt ist. In Java wird auch der Zugriff auf Paketebene gewährt.
Berry120
8
Mein Professor pflegte zu sagen: "Es gibt Dinge, die ich meinen Kindern nicht erzählen würde. Die ich meine privaten Felder."
Samstag,

Antworten:

225

Denn wie Sie sagen, protectedbleibt Ihnen immer noch die Möglichkeit, "die Implementierung komplett zu modifizieren". Es schützt nichts in der Klasse.

Warum ist es uns wichtig, das Zeug in der Klasse "wirklich zu schützen"? Andernfalls ist es unmöglich, Implementierungsdetails zu ändern, ohne den Clientcode zu beschädigen . Anders ausgedrückt, Leute, die Unterklassen schreiben, sind auch "die Außenwelt" für die Person, die die ursprüngliche Basisklasse geschrieben hat.

In der Praxis sind protectedMitglieder im Wesentlichen eine "öffentliche API für Unterklassen" der Klasse und müssen ebenso stabil und abwärtskompatibel bleiben wie publicMitglieder. Wenn wir nicht die Möglichkeit haben , um wahr zu schaffen privateMitglieder, dann nichts in einer Implementierung würde jemals sicher zu sein , zu ändern, weil Sie nicht die Möglichkeit auszuschließen, wäre in der Lage , dass (nicht-bösartige) Client - Code irgendwie an hat es geschafft , hängen es.

Übrigens: Während in OOP früher oder später eine Unterklasse einer Klasse erstellt wird, scheint Ihr Argument die viel stärkere Annahme zu treffen, dass Sie früher oder später eine Unterklasse erstellen werden jede Klasse ", was mit ziemlicher Sicherheit nicht der Fall ist.

Ixrec
quelle
11
Ich würde dich mehr als +1 werfen, wenn ich könnte, da dies das erste Mal ist private, dass es für mich wirklich Sinn macht. Die lib-Perspektive muss öfter verwendet werden, sonst könnte jeder, der die (C-artige) Codierungsmentalität "absolute Kontrolle, absolute Verantwortung" mag, sie als "Schützt mich vor mir selbst" kennzeichnen. Beachten Sie, dass ich früher privateimmer noch verwendet habe . Ich fühlte mich einfach immer gut dokumentiert, und eine Namenskonvention _foo, die darauf hinweist, dass Sie wahrscheinlich nicht damit herumspielen sollten, war gleichbedeutend, wenn nicht sogar besser. Deterministisch sagen zu können, dass "nichts kaputt gehen wird", ist ein legitimes und privateeinziges Merkmal.
Abluejelly
Ursprünglich habe ich den Code für öffentliche Bibliotheken und Frameworks ignoriert und mehr oder weniger nur in Bezug auf "Client-Code" gedacht. Die Optimierung der internen Implementierung ist ein gutes Beispiel für meine Frage, obwohl ich mich frage, ob dies wirklich in der Realität geschieht (insbesondere, wenn viele Leute empfehlen, dass eine Klasse nicht länger als 1000 Codezeilen sein sollte). Generell mag ich den Ansatz des Rubys, bei dem privat eine Art Empfehlung ist: "Hier seid Drachen, geht vorsichtig vor".
Adam Libuša
9
@ AdamLibuša Wenn Ihr Code öffentlich ist, ist dies zwar ein viel größeres Geschäft, es gilt jedoch auch dann, wenn Sie der Autor aller Clients der Klasse sind. Das Problem ändert sich einfach von bestimmten Refaktoren, die unmöglich sind, zu bestimmten Refaktoren, die mühsam und fehleranfällig sind. Nach meiner Erfahrung ist die Optimierung der am wenigsten verbreitete Grund für diese Refaktoren (obwohl ich hauptsächlich Javascript verwende). In der Regel handelt es sich eher um einen Fehler, der einen grundlegenden Implementierungsfehler aufdeckt, der eine Umstrukturierung des Abhängigkeits- / Aufrufdiagramms der verschiedenen internen Bits erfordert, um dies zu erreichen eine wirklich robuste Lösung.
Ixrec
5
"" Früher oder später werden Sie aus jeder Klasse eine Unterklasse machen "ist mit ziemlicher Sicherheit nicht der Fall." Außerdem werden Sie mit ziemlicher Sicherheit nicht jede Funktion in einer Klasse überschreiben und die Verwendung jedes Datenelements in einer Klasse ändern. Einige Dinge sollten logischerweise in Stein gemeißelt sein, damit die Klasse irgendeine Bedeutung hat.
Jay
1
Ich würde dich mehr als +1 werfen, wenn ich könnte => Das nennen wir Kopfgeld @abluejelly
Thomas Ayoub
256

In OOP erstellen Sie früher oder später eine Unterklasse einer Klasse

Das ist falsch. Nicht jede Klasse ist als Unterklasse gedacht, und einige statisch typisierte OOP-Sprachen verfügen sogar über Funktionen, die dies verhindern, z. B. final(Java und C ++) oder sealed(C #).

Es ist gut zu verstehen und in der Lage zu sein, die Implementierung vollständig zu modifizieren.

Nein, ist es nicht. Es ist gut für eine Klasse, ihre öffentliche Schnittstelle klar zu definieren und ihre Invarianten beizubehalten, selbst wenn sie von geerbt wird.

Bei der Zugangskontrolle geht es im Allgemeinen um die Unterteilung. Sie möchten, dass ein einzelner Teil des Codes verstanden wird, ohne im Detail zu verstehen, wie er mit dem Rest des Codes interagiert. Privater Zugang erlaubt das. Wenn alles mindestens geschützt ist, müssen Sie verstehen, was jede Unterklasse tut, um zu verstehen, wie die Basisklasse funktioniert.

Oder um es in den Begriffen von Scott Meyers auszudrücken: Private Teile einer Klasse sind von einer endlichen Menge an Code betroffen: dem Code der Klasse selbst.

Öffentliche Teile sind potenziell von jedem vorhandenen Codebit und jedem noch zu schreibenden Codebit betroffen, was eine unendliche Menge an Code darstellt.

Geschützte Teile sind möglicherweise von jeder vorhandenen Unterklasse und jeder noch zu schreibenden Unterklasse betroffen, was ebenfalls eine unendliche Menge an Code darstellt.

Die Schlussfolgerung ist, dass Sie mit protected nur wenig mehr als mit public erhalten, während private Ihnen eine echte Verbesserung bringen. Die Existenz des geschützten Zugriffsspezifizierers ist fraglich, nicht privat.

Sebastian Redl
quelle
34
+1 für das theoretische Szenario "Betroffen von unendlich viel Code"
Broken_Window
13
Vielleicht ist es auch erwähnenswert, dass die C # -Designer tatsächlich beschlossen haben, das Überschreiben einer vererbten Methode zu untersagen, es sei denn, sie ist virtualaus den in dieser Antwort genannten Gründen ausdrücklich als gekennzeichnet .
Will Vousden
11
Oder einfach gesagt: protectedist für Methoden, nicht für Datenmitglieder.
Matthieu M.
7
Ich kann es jetzt nicht finden, aber ich erinnere mich, dass ich eine gut geschriebene Antwort von @EricLippert gelesen habe, warum MS sealedgroße Teile der .net-Bibliothek und des IIRC, warum er den Rest gerne eingesperrt hätte. In dem Moment, in dem Sie zulassen, dass Vererbungen von Drittanbietern interne Werte berühren, müssen Sie jeder Methode eine große Menge an Validierungs- / Sicherheitsüberprüfungen hinzufügen, da Sie keinen Konstruktionsinvarianten mehr in Bezug auf den internen Status des Objekts vertrauen können.
Dan Neely
4
@ ThorbjørnRavnAndersen "während der Entwicklung, aber nicht wenn veröffentlicht" - wie um alles in der Welt soll eine Klasse das wissen? Möchten Sie wirklich eine Testversion kompilieren, die sich von der veröffentlichten Version unterscheidet, wenn Sie die veröffentlichte Version nicht einmal testen können? Tests sollten sowieso nicht auf private Inhalte zugreifen müssen.
Sebastian Redl
33

Ja, private Felder sind unbedingt erforderlich. Gerade diese Woche musste ich eine Implementierung eines benutzerdefinierten Wörterbuchs schreiben, in der ich kontrollierte, was in das Wörterbuch eingefügt wurde. Wenn das Wörterbuchfeld geschützt oder öffentlich gemacht werden soll, können die von mir so sorgfältig geschriebenen Steuerelemente leicht umgangen werden.

Bei privaten Feldern geht es in der Regel darum, sicherzustellen, dass die Daten dem erwarteten ursprünglichen Codierer entsprechen. Machen Sie alles geschützt / öffentlich und fahren Sie mit einer Kutsche und Pferden durch diese Verfahren und Validierung.

Robbie Dee
quelle
2
+1 für "Kutsche und Pferde durchreiten". Ich wünschte, ich hätte mehr gehört.
Nic Hartley
2
Wenn jemand Ihre Klasse unterordnen muss, sollte er vielleicht Zugang zu Ihren Sicherheitsvorkehrungen haben? Und wenn Sie nicht möchten, dass jemand Ihre Sicherheitsvorkehrungen ändert, um ein bestimmtes Ziel zu erreichen, geben Sie den Code möglicherweise nicht weiter? In Ruby funktioniert das so - privat ist mehr oder weniger eine Empfehlung.
Adam Libuša
3
@ AdamLibuša "Code nicht teilen"? Sobald Sie eine DLL veröffentlichen, geben Sie den Code frei - alle Strukturen und Methoden und alles ist für die ganze Welt sichtbar, insbesondere in Sprachen, die standardmäßig die Reflektion unterstützen. Jeder kann mit "Ihrem Code" machen, was er will - es privateist nur eine Art zu sagen, "fassen Sie diese nicht an" und lassen Sie es im Compilervertrag durchsetzen. In Systemen wie .NET hat dies auch wichtige Auswirkungen auf die Sicherheit. Sie können die Privatsphäre anderer nur dann berühren, wenn Sie volles Vertrauen haben (im Grunde das Äquivalent zu Administrator- / Root-Zugriff).
Luaan
2
@ AdamLibuša Ich denke, Ihre Verwirrung rührt hauptsächlich von den verschiedenen OOP-Ansätzen her, die verschiedene Sprachen ergriffen haben. Die Wurzel von OOP (wie ursprünglich definiert) ist Messaging - das bedeutet, dass bis auf die Nachrichten, auf die Sie antworten , alles privat ist. In den meisten OOPish-Sprachen wird dies als "Halten Sie Ihre Daten privat" angezeigt - eine Möglichkeit, die öffentliche Oberfläche so klein wie möglich zu halten. Der Benutzer (sei es eine Unterklasse oder eine andere Klasse) muss Ihre Klasse nur über die von Ihnen definierte öffentliche Oberfläche manipulieren - ähnlich wie Sie normalerweise das Lenkrad und die Pedale zum Fahren Ihres Autos verwenden :)
Luaan
3
@Luaan +1, wenn es in Ordnung ist, die Privatsphäre anderer zu berühren!
Nigel Touch
12

Bei dem Versuch, formal über die Richtigkeit eines objektorientierten Programms nachzudenken, ist es typisch , einen modularen Ansatz zu verwenden, der Objektinvarianten einbezieht . bei diesem Ansatz

  1. Methoden sind mit Vor- und Nachbedingungen (Verträgen) verbunden.
  2. Mit Objekten sind Invarianten verknüpft.

Das modulare Denken über ein Objekt läuft wie folgt ab (zumindest in erster Näherung)

  1. Beweisen Sie, dass der Konstruktor des Objekts die Invariante erstellt
  2. Nehmen Sie für jede nicht-private Methode an, dass die Objektinvariante und die Methodenvoraussetzung beim Eintritt gehalten werden, und beweisen Sie dann, dass der Hauptteil des Codes impliziert, dass die Nachbedingung und die Invariante beim Verlassen der Methode gehalten werden

Stellen Sie sich vor, wir überprüfen eine object AVerwendung des obigen Ansatzes. Und nun will , um zu überprüfen method gvon object Bden Anrufen method fvon object A. Dank des modularen Denkens können wir überlegen, method gohne die Implementierung von überdenken zu müssen method f. Vorausgesetzt, wir können die Invariante von object Aund die Vorbedingung von method fam Aufrufort festlegen, indem method g,wir die Post-Bedingung von method fals eine Zusammenfassung des Verhaltens des Methodenaufrufs betrachten. Darüber hinaus wissen wir auch, dass nach der Rückgabe des Anrufs die Invariante von Astill gilt.

Diese Modularität des Denkens ermöglicht es uns, formal über große Programme nachzudenken. Wir können über jede der Methoden einzeln argumentieren und dann die Ergebnisse dieser Argumentation zusammenfassen, um über größere Teile des Programms zu argumentieren.

Private Felder sind in diesem Prozess sehr nützlich. Um zu wissen, dass die Invariante eines Objekts zwischen zwei Methodenaufrufen für dieses Objekt bestehen bleibt, verlassen wir uns normalerweise darauf, dass das Objekt in der Zwischenzeit nicht geändert wird.

Damit das modulare Denken in einem Kontext funktioniert, in dem Objekte keine privaten Felder haben, muss sichergestellt werden, dass die Invariante immer (nach dem Feld) wiederhergestellt wird, auf was auch immer ein Feld von einem anderen Objekt gesetzt wurde einstellen). Es ist schwierig, sich eine Objektinvariante vorzustellen, die unabhängig vom Wert der Felder des Objekts beides enthält und die auch nützlich ist, um über die Korrektheit des Programms nachzudenken. Wir müssten wahrscheinlich eine komplizierte Konvention für den Feldzugang erfinden. Und wahrscheinlich verlieren auch einige (im schlimmsten Fall sogar alle) unserer Fähigkeit, modular zu argumentieren.

Geschützte Felder

Geschützte Felder stellen einen Teil unserer Fähigkeit wieder her, modular zu argumentieren. Je nach Sprache protectedkann die Möglichkeit eingeschränkt sein, ein Feld auf alle Unterklassen oder alle Unterklassen und Klassen desselben Pakets festzulegen. Es kommt häufig vor, dass wir nicht auf alle Unterklassen zugreifen können, wenn wir über die Richtigkeit eines von uns geschriebenen Objekts nachdenken. Beispielsweise schreiben Sie möglicherweise eine Komponente oder Bibliothek, die später in einem größeren Programm (oder mehreren größeren Programmen) verwendet wird - von denen einige möglicherweise noch nicht einmal geschrieben wurden. In der Regel wissen Sie nicht, ob und in welcher Weise eine Unterklasse vorliegt.

Normalerweise ist es jedoch Aufgabe einer Unterklasse, die Objektinvariante der Klasse, die sie erweitert, beizubehalten. In einer Sprache, in der schützen nur "Unterklasse" bedeutet und in der wir diszipliniert dafür sorgen, dass Unterklassen immer die Invarianten ihrer Oberklasse beibehalten, kann man argumentieren, dass die Wahl der Verwendung von geschützt anstelle von privat nur minimale Modularität verliert .

Obwohl ich über formale Argumente gesprochen habe, wird oft angenommen, dass Programmierer, die informelle Gründe für die Richtigkeit ihres Codes haben, manchmal auch auf ähnliche Argumente zurückgreifen.

flamingpenguin
quelle
8

privateVariablen in einer Klasse sind besser als protectedaus demselben Grund, aus dem eine breakAnweisung in einem switchBlock besser ist als eine goto labelAnweisung. Das ist, dass menschliche Programmierer fehleranfällig sind.

protectedVariablen eignen sich für unbeabsichtigten Missbrauch (Programmiererfehler), genauso wie sich die gotoAnweisung für die Erstellung von Spaghetti-Code eignet.

Ist es möglich, fehlerfreien Code mit protectedKlassenvariablen zu schreiben ? Ja natürlich! So wie es möglich ist, fehlerfreien Code mit zu schreiben goto; aber wie es im Klischee heißt: "Nur weil du kannst, heißt das nicht, dass du sollst!"

Es gibt Klassen und in der Tat das OO-Paradigma, um zu verhindern, dass unglückliche, fehleranfällige menschliche Programmierer Fehler machen. Die Abwehr menschlicher Fehler ist nur so gut wie die in die Klasse eingebauten Abwehrmaßnahmen. Die Umsetzung Ihrer Klasse protectedist das Äquivalent dazu, ein riesiges Loch in die Wände einer Festung zu sprengen.

Basisklassen haben absolut keine Kenntnis über abgeleitete Klassen. Wenn es sich um eine Basisklasse handelt, protectedbietet diese Klasse eigentlich keinen größeren Schutz als public, da nichts eine abgeleitete Klasse davon abhält, einen publicGetter / Setter zu erstellen, der sich wie eine Hintertür verhält.

Wenn eine Basisklasse einen ungehinderten Zugriff auf ihre internen Implementierungsdetails zulässt, ist es für die Klasse selbst unmöglich, sich gegen Fehler zu verteidigen. Basisklassen kennen ihre abgeleiteten Klassen überhaupt nicht und können sich daher nicht vor Fehlern schützen, die in diesen abgeleiteten Klassen gemacht wurden.

Das Beste, was eine Basisklasse tun kann, ist, so viel wie möglich von ihrer Implementierung zu verbergen privateund genügend Einschränkungen zu treffen, um Änderungen von abgeleiteten Klassen oder anderen Objekten außerhalb der Klasse zu verhindern.

Letztendlich existieren Hochsprachen, um menschliche Fehler zu minimieren. Es gibt auch gute Programmierpraktiken (wie SOLID-Prinzipien ), um menschliche Fehler zu minimieren.

Softwareentwickler, die gute Programmierpraktiken ignorieren, haben ein viel höheres Fehlerrisiko und führen mit größerer Wahrscheinlichkeit zu fehlerhaften, nicht zu wartenden Lösungen. Diejenigen, die sich an bewährte Verfahren halten, haben eine viel geringere Wahrscheinlichkeit, dass sie versagen, und führen mit größerer Wahrscheinlichkeit zu funktionsfähigen, wartbaren Lösungen.

Ben Cottrell
quelle
Zum Downvoter - was könnte an dieser Antwort geändert / verbessert werden?
Ben Cottrell
Ich habe nicht abgestimmt, aber die Zugriffsebene mit break / goto verglichen?
imel96
@ imel96 Nein, vergleiche den Grund, warum sie vermieden werden und von "Best Practice" (insbesondere für das Schreiben von neuem Code) abgeraten werden . dh ein zuständiger Programmierer würde vermeiden publicImplementierungsdetails , weil es mir verleiht wartbaren Code. Ein kompetenter Programmierer würde es vermeiden, gotoweil es sich für nicht wartbaren Code eignet. Die reale Welt ist jedoch so, dass Sie manchmal in einem schrecklichen Durcheinander von Legacy-Code verirrt sind und keine andere Wahl haben, als diese zu verwenden goto, und aus dem gleichen Grund haben Sie manchmal keine andere Wahl, als öffentliche / geschützte Implementierungsdetails zu verwenden.
Ben Cottrell
Der Schweregrad ist unterschiedlich groß, es ist unvergleichlich. Ich könnte mit Code leben, der nur publicEigenschaften / Schnittstelle hat, aber nicht mit Code, der nur verwendet goto.
Imel96
2
@ imel96 Hast du jemals tatsächlich in Code gearbeitet, der verwendet gotooder liegt es nur daran, dass du solche Artikel gelesen hast? Ich verstehe, warum goto es böse ist, weil ich 2 Jahre lang an altem Code gearbeitet habe, der ihn verwendet. und ich habe noch länger mit Code gearbeitet, bei dem die Implementierungsdetails von "Klassen" überall durchgesickert sind publicund friendSpezifizierer als schnelle / einfache / schmutzige Hacks verwendet werden. Ich akzeptiere das Argument nicht, dass das "öffentliche Offenlegen von Implementierungsdetails" kein ineinandergreifendes Spaghetti-Chaos mit dem gleichen Schweregrad verursachen kann, wie gotoes absolut möglich ist.
Ben Cottrell
4

Vererbbare Klassen haben zwei Verträge - einen mit Inhabern von Objektreferenzen und einen mit abgeleiteten Klassen. Öffentliche Mitglieder sind an den Vertrag mit Referenzinhabern gebunden, und geschützte Mitglieder sind an den Vertrag mit abgeleiteten Klassen gebunden.

Durch das Erstellen von Mitgliedern wird protecteddie Basisklasse vielseitiger, aber die Art und Weise, in der sich zukünftige Versionen der Klasse ändern, wird häufig eingeschränkt. Durch das Erstellen von Mitgliedern privatekann der Klassenautor die innere Funktionsweise der Klasse vielseitiger ändern, begrenzt jedoch die Arten von Klassen, die sinnvoll daraus abgeleitet werden können.

Beispiel: List<T>In .NET wird der Sicherungsspeicher als privat definiert. Wenn es geschützt wäre, könnten abgeleitete Typen einige nützliche Dinge tun, die ansonsten nicht möglich List<T>wären , aber zukünftige Versionen von müssten seinen klobigen monolithischen Hintergrundspeicher sogar für Listen mit Millionen von Elementen verwenden. Wenn Sie den Sicherungsspeicher privat machen, können zukünftige Versionen von List<T>einen effizienteren Sicherungsspeicher verwenden, ohne abgeleitete Klassen zu unterbrechen.

Superkatze
quelle
4

Ich glaube, Ihre Argumentation geht von der Annahme aus, dass jemand, der eine Klasse schreibt, nicht weiß, wer diese Klasse später und aus welchem ​​Grund erweitern könnte . Unter dieser Annahme ist Ihr Argument durchaus sinnvoll, da jede Variable, die Sie privat machen, möglicherweise eine Entwicklungsstraße in der Zukunft abschneidet. Diese Annahme würde ich jedoch ablehnen.

Wenn diese Annahme zurückgewiesen wird, sind nur zwei Fälle zu berücksichtigen.

  1. Der Autor der ursprünglichen Klasse hatte sehr klare Vorstellungen darüber, warum sie erweitert werden könnte (z. B. handelt es sich um ein BaseFoo, und es werden einige konkrete Foo-Implementierungen folgen).

In diesem Fall weiß der Autor, dass jemand die Klasse erweitern wird und warum und daher genau weiß, was geschützt und was privat gemacht werden soll. Sie verwenden die private / protected-Unterscheidung, um dem Benutzer, der die Unterklasse erstellt, eine Art Schnittstelle mitzuteilen.

  1. Der Autor der untergeordneten Klasse versucht, ein bestimmtes Verhalten in eine übergeordnete Klasse zu hacken.

Dieser Fall sollte selten vorkommen (Sie könnten argumentieren, dass er nicht legitim ist) und es wird nicht bevorzugt, nur die ursprüngliche Klasse in der ursprünglichen Codebasis zu ändern. Es könnte auch ein Symptom für schlechtes Design sein. In diesen Fällen würde ich es vorziehen, wenn die Person, die das Verhalten hackt, andere Hacks wie Friends (C / C ++) und setAccessible(true)(Java) verwendet.

Ich denke, es ist sicher, diese Annahme abzulehnen.

Dies geht im Allgemeinen auf die Idee der Komposition über die Vererbung zurück . Vererbung wird häufig als ideale Methode zur Reduzierung der Wiederverwendung von Code gelehrt, sollte jedoch selten die erste Wahl für die Wiederverwendung von Code sein. Ich habe kein einfaches KO-Argument und es kann ziemlich schwierig und umstritten sein, es zu verstehen. Aufgrund meiner Erfahrungen mit der Domänenmodellierung habe ich jedoch festgestellt, dass ich Vererbung selten verwende, ohne genau zu wissen, wer meine Klasse aus welchem ​​Grund erbt.

Tempo
quelle
2

Alle drei Zugriffsebenen haben ihren Anwendungsfall, OOP wäre unvollständig, wenn keine von ihnen vorhanden wäre. Normalerweise tust du das

  • Alle Variablen / Datenelemente als privat kennzeichnen . Sie möchten nicht, dass jemand von außen mit Ihren internen Daten spielt. Auch Methoden, die Ihrer öffentlichen oder geschützten Schnittstelle Hilfsfunktionen bieten (z. B. Berechnungen, die auf mehreren Mitgliedsvariablen basieren) - dies ist nur für den internen Gebrauch und Sie möchten diese möglicherweise in Zukunft ändern / verbessern.
  • Machen Sie die allgemeine Oberfläche Ihrer Klasse öffentlich . Damit sollen die Benutzer Ihrer ursprünglichen Klasse arbeiten, und Sie denken, dass abgeleitete Klassen auch so aussehen sollten. Um eine ordnungsgemäße Kapselung zu gewährleisten, handelt es sich in der Regel nur um Methoden (und Hilfsklassen / -strukturen, Aufzählungen, Typedefs, unabhängig davon, was der Benutzer für die Arbeit mit Ihren Methoden benötigt), nicht um Variablen.
  • erklären die Methoden geschützt , die von Nutzen für jemanden sein könnte, der die Funktionalität Ihrer Klasse erweitern / spezialisieren will, soll aber nicht Teil der seine öffentlichen Schnittstelle - in der Tat erhöhen Sie in der Regel private Mitglieder zu schützen , wenn nötig. Im Zweifelsfall nicht, bis Sie das wissen
    1. Ihre Klasse kann / kann / wird untergeordnet sein,
    2. und eine klare Vorstellung davon haben, wie die Anwendungsfälle von Unterklassen aussehen können.

Und Sie weichen nur dann von diesem allgemeinen Schema ab, wenn es einen guten Grund gibt . Hüten Sie sich vor „das wird mein Leben leichter machen , wenn ich es frei von außen zugreifen kann“ (und außerhalb hier auch enthält Subklassen). Wenn ich Klassenhierarchien implementiere, beginne ich oft mit Klassen, die keine geschützten Mitglieder haben, bis ich zu deren Unterklassen / Erweiterung / Spezialisierung komme, zu den Basisklassen eines Frameworks / Toolkits werde und manchmal einen Teil ihrer ursprünglichen Funktionalität eine Ebene nach oben verschiebe.

Murphy
quelle
1

Eine vielleicht interessantere Frage ist, warum eine andere Art von Feld als privat notwendig ist. Wenn eine Unterklasse mit den Daten einer Superklasse interagieren muss, wird direkt eine direkte Kopplung zwischen den beiden erstellt, während die Verwendung von Methoden zum Bereitstellen der Interaktion zwischen den beiden eine Indirektionsebene ermöglicht, die es ermöglicht, Änderungen an den Daten einer Superklasse vorzunehmen Superklasse, die sonst sehr schwierig wäre.

Einige Sprachen (z. B. Ruby und Smalltalk) stellen keine öffentlichen Felder zur Verfügung, sodass Entwickler davon abgehalten werden, eine direkte Kopplung mit ihren Klassenimplementierungen zuzulassen. Warum aber nicht weiter gehen und nur private Felder verwenden? Es würde keinen Verlust an Allgemeinheit geben (da die Oberklasse immer geschützte Zugriffsmechanismen für die Unterklasse bereitstellen kann), aber dies würde sicherstellen, dass Klassen immer mindestens einen geringen Grad an Isolation von ihren Unterklassen aufweisen. Warum ist dies kein häufigeres Design?

Jules
quelle
1

Viele gute Antworten hier, aber ich werfe trotzdem meine zwei Cent ein. :-)

Privat ist aus demselben Grund gut, aus dem auch globale Daten schlecht sind.

Wenn eine Klasse Daten als privat deklariert, wissen Sie absolut, dass der einzige Code, der mit diesen Daten in Konflikt gerät, der Code in der Klasse ist. Wenn ein Fehler auftritt, müssen Sie nicht die gesamte Erstellung durchsuchen, um alle Stellen zu finden, an denen diese Daten möglicherweise geändert werden. Sie wissen, dass es in der Klasse ist. Wenn Sie eine Änderung am Code vornehmen und Änderungen an der Verwendung dieses Felds vornehmen, müssen Sie nicht alle potenziellen Stellen ausfindig machen, an denen dieses Feld verwendet werden könnte, und untersuchen, ob Ihre geplante Änderung sie beschädigen wird. Sie wissen, dass die einzigen Orte in der Klasse sind.

Ich musste viele, viele Male Änderungen an Klassen vornehmen, die sich in einer Bibliothek befinden und von mehreren Apps verwendet werden, und ich muss sehr vorsichtig vorgehen, um sicherzustellen, dass ich keine App beschädige, über die ich nichts weiß. Je mehr öffentliche und geschützte Daten vorhanden sind, desto größer ist die Gefahr von Problemen.

Jay
quelle
1

Ich denke, es lohnt sich, einige abweichende Meinungen zu erwähnen.

Theoretisch ist es aus allen in den anderen Antworten genannten Gründen gut, die Zugriffsebene kontrolliert zu haben.

In der Praxis sehe ich bei der Codeüberprüfung zu oft Leute (die gerne privat arbeiten), die die Zugriffsebene von privat -> geschützt und nicht zu oft von geschützt -> öffentlich ändern. Das Ändern von Klasseneigenschaften erfordert fast immer das Ändern von Setter / Gettern. Diese haben viel meiner Zeit (Codeüberprüfung) und ihrer Zeit (Codeänderung) verschwendet.

Es ärgert mich auch, dass dies bedeutet, dass ihre Klassen nicht für Änderungen geschlossen sind.

Das war mit internem Code, wo Sie es immer ändern können, wenn Sie auch brauchen. Bei Code von Drittanbietern ist die Situation noch schlimmer, wenn es nicht so einfach ist, den Code zu ändern.

Wie viele Programmierer halten das für einen Ärger? Nun, wie viele verwenden Programmiersprachen, die keine privaten haben? Natürlich verwenden die Leute nicht nur diese Sprachen, weil sie keine privaten Spezifizierer haben, aber es hilft, die Sprachen zu vereinfachen, und Einfachheit ist wichtig.

Imo ist es sehr ähnlich zu dynamischer / statischer Eingabe. Theoretisch ist die statische Typisierung sehr gut. In der Praxis verhindert es nur wie 2% der Fehler die Unvernünftige Wirksamkeit der dynamischen Typisierung ... . Die Verwendung von privat verhindert wahrscheinlich weniger Fehler.

Ich denke, dass SOLID-Prinzipien gut sind, und ich wünsche mir, dass sich die Leute mehr für sie interessieren als für die Schaffung einer Klasse mit öffentlichen, geschützten und privaten.

imel96
quelle
1
Wenn Sie Code von Drittanbietern bei der Verwendung ändern müssen, ist er entweder nicht gut gestaltet oder wird nicht gut wiederverwendet. Sie können eine nicht abstrakte Klasse jederzeit wiederverwenden, indem Sie sie einkapseln. In der Praxis müssen Sie sie jedoch kaum unterklassifizieren.
Little Santi
0

Ich möchte auch ein weiteres praktisches Beispiel hinzufügen, warum dies protectednicht ausreicht. An meiner Universität unternehmen die ersten Jahre ein Projekt, bei dem sie eine Desktop-Version eines Brettspiels entwickeln müssen (für das später eine KI entwickelt und über ein Netzwerk mit anderen Spielern verbunden wird). Ihnen wird ein Teilcode zur Verfügung gestellt, um sie mit einem Testframework zum Einstieg zu bewegen. Einige der Eigenschaften der Hauptspielklasse werden offengelegt, protecteddamit die Testklassen, die diese Klasse erweitern, auf sie zugreifen können. Diese Felder sind jedoch keine vertraulichen Informationen.

Als TA für die Einheit sehe ich oft Studenten einfach machen alle zusätzlichen Code protectedoder public(vielleicht , weil sie die anderen sah protectedund publicSachen und nahm sie sollten folgen). Ich frage sie, warum ihr Schutzniveau unangemessen ist und viele wissen nicht warum. Die Antwort ist, dass die sensiblen Informationen, die sie Subklassen aussetzen, bedeuten, dass ein anderer Spieler schummeln kann, indem er einfach diese Klasse erweitert und auf die hochsensiblen Informationen für das Spiel zugreift (im Wesentlichen die verborgene Position des Gegners, denke ich ähnlich wie bei Ihnen könnte die Teile deines Gegners auf einem Schlachtschiffbrett sehen, indem du eine Klasse ausbaust). Das macht ihren Code im Kontext des Spiels sehr gefährlich.

Abgesehen davon gibt es viele andere Gründe, etwas für Ihre Unterklassen geheim zu halten. Es kann sein, dass Implementierungsdetails ausgeblendet werden, die die korrekte Arbeitsweise der Klasse beeinträchtigen, wenn sie von jemandem geändert werden, der nicht unbedingt weiß, was er tut (meistens denken sie daran, dass andere Ihren Code hier verwenden).

J_mie6
quelle
1
Ich verstehe nicht Wie kann man damit schummeln, es sei denn, die Klassen werden dynamisch erweitert, während das Spiel läuft? Dies würde bedeuten, dass Sie nicht vertrauenswürdigen Code in Ihre Codebasis importieren. Wenn Sie behaupten, dass Sie Schülern kompilierte Klassen geben und verhindern, dass sie in ihrer Zuweisung schummeln, indem Sie Implementierungsdetails ändern, wird die Zuweisung durch das Festlegen einer Schutzstufe nicht sicherer. Die Schüler können Bibliotheken dekompilieren oder Reflektion zu ihrem Vorteil nutzen. Dies hängt natürlich vom Grad der Durchsetzung ab, den Sie anstreben.
Sam
@sam Im Allgemeinen sollte Code mit der Annahme geschrieben werden, dass jeder, der ihn als nächstes ändert, die gesamte Codebasis nicht genau kennen muss. Das könnte manchmal sogar der ursprüngliche Autor sein (Zeit vergeht, Schlafmangel ...). Schummeln könnte sein, dass die Schüler einen Code erhalten, der sie ausformuliert und die Logik ändert, die sie nicht anfassen sollten.
Phil Lello
0

Private Methoden / Variablen werden in der Regel vor einer Unterklasse verborgen. Das kann gut sein.

Eine private Methode kann Annahmen über Parameter treffen und die Überprüfung der Integrität dem Aufrufer überlassen.

Eine geschützte Methode sollte Eingaben auf ihre Richtigkeit überprüfen.

Phil Lello
quelle
-1

"privat" bedeutet: Nicht für andere Personen als die Klasse selbst bestimmt. Nicht dazu gedacht, von Unterklassen geändert oder aufgerufen zu werden. Unterklassen? Welche Unterklassen? Du sollst das nicht unterordnen!

"protected" bedeutet: Nur zum Ändern von oder Zugreifen auf Klassen oder Unterklassen vorgesehen. Wahrscheinlich Abzug, den du unterordnen sollst, sonst warum "geschützt" und nicht "privat"?

Hier gibt es einen deutlichen Unterschied. Wenn ich etwas privat mache, solltest du deine schmutzigen Finger davon lassen. Auch wenn Sie eine Unterklasse sind.

gnasher729
quelle
-1

Eines der ersten Dinge, die ich mache, wenn ich eine Klasse unterklassifiziere, ist, eine Reihe privater Methoden in protected zu ändern

Einige Überlegungen zu privatevs. protected Methoden :

privateMethoden verhindern die Wiederverwendung von Code. Eine Unterklasse kann den Code nicht in der privaten Methode verwenden und muss ihn möglicherweise erneut implementieren - oder die ursprünglich von der privaten Methode & c abhängigen Methoden erneut implementieren.

Andererseits kann jede Methode, die nicht private als API angesehen werden kann, die von der Klasse für "die Außenwelt" bereitgestellt wird, in dem Sinne, dass Unterklassen von Drittanbietern auch als "Außenwelt" betrachtet werden, wie jemand anderes in seiner Antwort vorgeschlagen hat bereits.

Ist das etwas schlechtes? - Das glaube ich nicht.

Natürlich sperrt eine (pseudo-) öffentliche API den ursprünglichen Programmierer und behindert das Refactoring dieser Schnittstellen. Aber umgekehrt: Warum sollte ein Programmierer seine eigenen "Implementierungsdetails" nicht so sauber und stabil gestalten wie seine öffentliche API? Sollte er privatedamit umgehen, um seinen "privaten" Code zu strukturieren? Denken Sie vielleicht, dass er es später aufräumen könnte, weil niemand es bemerken wird? - Nein.

Der Programmierer sollte auch ein wenig über seinen "privaten" Code nachdenken, um ihn so zu strukturieren, dass er die Wiederverwendung von so viel wie möglich überhaupt erst ermöglicht oder sogar fördert. Dann werden die nicht-privaten Teile in Zukunft möglicherweise nicht mehr so ​​belastend wie manche befürchten.

Eine Menge (Framework-) Code, den ich sehe, verwendet uneinheitlich private: protectedNicht endgültige Methoden, die kaum mehr als das Delegieren an eine private Methode leisten, sind häufig anzutreffen. protected, nicht endgültige Methoden, deren Vertrag nur durch direkten Zugang zu privaten Feldern erfüllt werden kann.

Diese Methoden können logisch nicht überschrieben / erweitert werden, obwohl technisch nichts da ist, was das (Compiler-) offensichtlich macht.

Wünschen Sie Erweiterbarkeit und Vererbung? Machen Sie nicht Ihre Methoden private.

Willst du nicht, dass sich ein bestimmtes Verhalten deiner Klasse ändert? Machen Sie Ihre Methoden final.

Können Sie Ihre Methode wirklich nicht außerhalb eines bestimmten, genau definierten Kontexts aufrufen lassen? Stellen Sie Ihre Methode bereit privateund / oder überlegen Sie, wie Sie den erforderlichen gut definierten Kontext für die Wiederverwendung durch eine andere protectedWrapper-Methode verfügbar machen können .

Deshalb empfehle ich, privatesparsam zu verwenden . Und nicht zu verwechseln privatemit final. - Wenn die Implementierung einer Methode für den allgemeinen Vertrag der Klasse von entscheidender Bedeutung ist und daher nicht ersetzt / überschrieben werden darf, machen Sie es final!

Für Felder privateist das nicht wirklich schlecht. Solange das / die Feld (er) mit geeigneten Methoden sinnvoll "genutzt" werden kann (das ist nicht getXX() oder setXX()!).

JimmyB
quelle
2
-1 Dies ist nicht die Adresse privatevs protectedursprüngliche Frage, gibt irrt Rat ( „verwenden private? Sparsam“) und geht gegen den allgemeinen Konsens über OO - Design. Verwenden privatehat nichts damit zu tun, schlampigen Code zu verbergen.
Andres F.
3
Sie erwähnen, dass eine Klasse eine API hat, scheinen aber nicht die Verbindung herzustellen, die der Benutzer der API nicht mit Details belasten möchte, die er nicht wissen muss. Die ideale Situation für den Benutzer einer Klasse besteht darin, dass die Schnittstelle nur die Methoden enthält, die sie benötigen, und sonst nichts. Das Erstellen von Methoden trägt privatedazu bei, die API sauber zu halten.
Ben Cottrell
1
@HannoBinder Ich stimme zu, dass es viele Klassen gibt, die unter Starrheit leiden. Flexibilität und Wiederverwendung von Code sind jedoch weniger wichtig als Kapselung und langfristige Wartbarkeit. Betrachten Sie Ihren Fall, in dem alle Klassen geschützte Mitglieder haben und abgeleitete Klassen mit den Implementierungsdetails herumspielen können, die sie wünschen. Wie können Sie diese Klassen zuverlässig in einem Unit-Test testen, wenn Sie wissen, dass irgendetwas in einem Teil des noch nicht geschriebenen Codes dazu führen kann, dass dieser Code jederzeit fehlschlägt? Wie viele solcher "Hacks" könnte der Code aushalten, bevor alles zu einer großen Schlammkugel ausartet?
Ben Cottrell
2
Oh, du hast "Mitglieder" gesagt. Was ich sage, bezieht sich nur auf Methoden . Mitgliedsvariablen (Felder) sind eine andere Geschichte, in der die Privatsphäre viel mehr gerechtfertigt ist.
JimmyB
2
Die Nomenklatur in der Diskussion ist allgegenwärtig, weil sie nicht spezifisch für eine bestimmte Sprache ist. "Mitglied" in C ++ kann Daten, Funktion, Typ oder Vorlage sein.
JDługosz
-1

Kennen Sie einen wichtigen Anwendungsfall, bei dem privat statt geschützt ein gutes Werkzeug ist, oder würden zwei Optionen "geschützt & öffentlich" für OOP-Sprachen ausreichen?

Privat : Wenn Sie etwas haben, das für eine Unterklasse zum Aufrufen oder Überschreiben niemals nützlich sein wird.

Geschützt : wenn Sie etwas haben, das eine Unterklassen-spezifische Implementierung / Konstante hat.

Ein Beispiel:

public abstract Class MercedesBenz() extends Car {
  //Might be useful for subclasses to know about their customers
  protected Customer customer; 

  /* Each specific model has its own horn. 
     Therefore: protected, so that each subclass might implement it as they wish
  */
  protected abstract void honk();

  /* Taken from the car class. */
  @Override
  public void getTechSupport(){
     showMercedesBenzHQContactDetails(customer);
     automaticallyNotifyLocalDealer(customer);
  }

  /* 
     This isn't specific for any subclass.
     It is also not useful to call this from inside a subclass,
     because local dealers only want to be notified when a 
     customer wants tech support. 
   */
  private void automaticallyNotifyLocalDealer(){
    ...
  }
}
Arnab Datta
quelle
2
wenn Sie etwas haben, das für eine Unterklasse zum Aufrufen oder Überschreiben niemals nützlich sein wird - und dies ist etwas, das Sie meiner Meinung nach niemals im Voraus wissen.
Adam Libuša
Möglicherweise muss ich auch die Reset-Taste an meinem CMOS drücken. Aber die Standardannahme ist, dass ich es nicht wirklich brauche und es daher in das Chassis gesteckt wird. Gleiches gilt für Methoden. Sie machen es geschützt, wenn die Unterklasse es unbedingt neu implementieren / aufrufen muss (siehe: Hupen). Sie machen es öffentlich, wenn andere Parteien es anrufen müssen.
Arnab Datta
-2

Es war schwer für mich, diese Angelegenheit zu verstehen, deshalb möchte ich einen Teil meiner Erfahrung teilen:

  • Was ist das Schutzfeld ? Es ist nichts anderes als ein Feld, das nicht außerhalb einer Klasse zugegriffen werden kann, das heißt öffentlich wie folgt aus : $classInstance->field. Und der Trick, dass es "das ist es" ist. Die Kinder Ihrer Klasse haben uneingeschränkten Zugriff darauf, da dies ihr rechtmäßiger interner Teil ist.
  • Was ist der private Bereich? Es ist ein " echtes Privates " für Ihre eigene Klasse und Ihre eigene Implementierung dieser Klasse. "Darf nicht in die Hände von Kindern gelangen", wie auf einer Medikamentenflasche. Sie haben die Garantie, dass es von den Derivaten Ihrer Klasse nicht überschrieben werden kann. Ihre Methoden haben - wenn sie aufgerufen werden - genau das, was Sie deklariert haben

UPDATE: Ein praktisches Beispiel für eine echte Aufgabe, die ich gelöst habe. Hier ist es: Sie haben ein Token wie USB oder LPT (das war mein Fall) und Sie haben eine Middleware. Der Token fragt Sie nach einem PIN-Code, öffnet sich, wenn er korrekt ist, und Sie können einen verschlüsselten Teil und eine Reihe von Schlüsseln zum Entschlüsseln senden. Die Schlüssel werden im Token gespeichert, Sie können sie nicht lesen, sondern nur verwenden. Und es gab temporäre Schlüssel für eine Sitzung, die von einem Schlüssel in einem Token signiert, aber in einer Middleware selbst gespeichert wurden. Der temporäre Schlüssel sollte nicht irgendwo draußen lecken, sondern nur auf Fahrerebene existieren. Und ich habe private Felder verwendet, um diesen temporären Schlüssel und einige Hardware-Verbindungsdaten zu speichern. Daher konnten keine Derivate nicht nur eine öffentliche Schnittstelle verwenden, sondern auch einige geschützte"handliche" Unterprogramme, die ich für eine Aufgabe erstellt habe, aber mit den Tasten und der HW-Interaktion nicht in der Lage waren, einen Geldschrank zu öffnen. Macht Sinn?

Alexey Vesnin
quelle
Tut mir leid, Leute! habe sie nur vermasselt - aktualisiere meine Antwort =) DANKE! Ich hatte es eilig und habe mich vertippt =)
Alexey Vesnin
2
Ich denke, das OP ist sich der Unterschiede zwischen den beiden Schutzstufen bewusst, ist aber daran interessiert, warum eine über der anderen verwendet werden sollte.
Sam
@Sam, bitte lies meine Antwort genauer durch. Das sind ganz andere Arten von Feldern! Das einzige, was sie gemeinsam haben, ist, dass sie beide nicht öffentlich referenziert werden können
Alexey Vesnin
2
Ich bin nicht sicher, worauf Sie sich beziehen. Mir sind die Unterschiede zwischen privat und geschützt bewusst. Vielleicht haben Sie falsch verstanden, was ich unter Schutzstufen verstehe? Ich beziehe mich auf "Zugriffsebene". Ich habe versucht darauf hinzuweisen, dass Ihre Antwort zwar nicht schlecht ist, die Frage aber nicht direkt anspricht. Das OP scheint zu wissen, was "geschützt", "privat" oder "öffentlich" bedeutet, ist sich jedoch nicht sicher, unter welchen Umständen sie sich entscheiden möchten. Auf diese Weise wurdest du möglicherweise abgewählt (nicht von mir).
Sam
@Sam Ich denke, ich habe Ihren Standpunkt verstanden - ich habe einen praktischen Fall aus meiner Praxis / Erfahrung hinzugefügt. Ist es das, was dir fehlt?
Alexey Vesnin