Diese Frage, wann privat und wann geschützt in Klassen verwendet werden soll, brachte mich zum Nachdenken. (Ich werde diese Frage auch auf die letzten Klassen und Methoden ausdehnen, da sie verwandt sind. Ich programmiere in Java, denke aber, dass dies für jede OOP-Sprache relevant ist.)
Die akzeptierte Antwort lautet:
Eine gute Faustregel lautet: Machen Sie alles so privat wie möglich.
- Machen Sie alle Klassen endgültig, es sei denn, Sie müssen sie sofort unterordnen.
- Machen Sie alle Methoden endgültig, es sei denn, Sie müssen sie sofort in Unterklassen unterteilen und überschreiben.
- Machen Sie alle Methodenparameter endgültig, es sei denn, Sie müssen sie im Hauptteil der Methode ändern, was in den meisten Fällen ohnehin umständlich ist.
Das ist ziemlich einfach und klar, aber was ist, wenn ich hauptsächlich Bibliotheken (Open Source auf GitHub) anstelle von Anwendungen schreibe?
Ich könnte viele Bibliotheken und Situationen nennen, in denen
- Eine Bibliothek wurde auf eine Weise erweitert, an die die Entwickler niemals gedacht hätten
- Dies musste aufgrund von Sichtbarkeitsbeschränkungen mit "Klassenladeprogramm-Magie" und anderen Hacks durchgeführt werden
- Bibliotheken wurden auf eine Weise verwendet, für die sie nicht gebaut wurden, und die benötigte Funktionalität wurde "gehackt"
- Bibliotheken konnten aufgrund eines kleinen Problems (Fehler, fehlende Funktionalität, "falsches" Verhalten) nicht verwendet werden, das aufgrund einer eingeschränkten Sichtbarkeit nicht geändert werden konnte
- Ein Problem, das nicht behoben werden konnte, führte zu riesigen, hässlichen und fehlerhaften Problemumgehungen, bei denen das Überschreiben einer einfachen (privaten oder endgültigen) Funktion hätte helfen können
Und ich fing tatsächlich an, diese zu benennen, bis die Frage zu lang wurde und ich beschloss, sie zu entfernen.
Ich mag die Idee, nicht mehr Code als nötig zu haben, mehr Sichtbarkeit als nötig, mehr Abstraktion als nötig. Dies funktioniert möglicherweise auch beim Schreiben einer Anwendung für den Endbenutzer, bei der der Code nur von denjenigen verwendet wird, die ihn schreiben. Aber wie hält es sich, wenn der Code von anderen Entwicklern verwendet werden soll, bei denen es unwahrscheinlich ist, dass der ursprüngliche Entwickler im Voraus über jeden möglichen Anwendungsfall nachgedacht hat und Änderungen / Umgestaltungen schwierig / unmöglich sind?
Wie wird in solchen Projekten mit objektorientierten Sprachen am häufigsten mit der Sichtbarkeit umgegangen, da große Open-Source-Bibliotheken nichts Neues sind?
Antworten:
Die unglückliche Wahrheit ist, dass viele Bibliotheken nicht entworfen , sondern geschrieben werden . Das ist traurig, denn ein bisschen vorheriges Nachdenken kann viele Probleme auf der Straße verhindern.
Wenn wir uns daran machen, eine Bibliothek zu entwerfen, wird es einige erwartete Anwendungsfälle geben. Die Bibliothek erfüllt möglicherweise nicht alle Anwendungsfälle direkt, kann jedoch als Teil einer Lösung dienen. Die Bibliothek muss also flexibel genug sein, um sich anzupassen.
Die Einschränkung ist, dass es normalerweise keine gute Idee ist, den Quellcode der Bibliothek zu übernehmen und ihn zu ändern, um den neuen Anwendungsfall zu behandeln. Für proprietäre Bibliotheken ist die Quelle möglicherweise nicht verfügbar, und für Open-Source-Bibliotheken ist es möglicherweise unerwünscht, eine gespaltene Version beizubehalten. Es ist möglicherweise nicht möglich, hochspezifische Anpassungen in das vorgelagerte Projekt einzubeziehen.
Hier kommt das Open-Closed-Prinzip ins Spiel: Die Bibliothek sollte für Erweiterungen offen sein, ohne den Quellcode zu ändern. Das kommt natürlich nicht. Dies muss ein beabsichtigtes Entwurfsziel sein. Es gibt eine Fülle von Techniken, die hier helfen können. Einige davon sind die klassischen OOP-Designmuster. Im Allgemeinen geben wir Hooks an, mit denen Benutzercode sicher in die Bibliothek eingefügt und Funktionen hinzugefügt werden können.
Es reicht nicht aus, jede Methode zu veröffentlichen oder zuzulassen, dass jede Klasse einer Unterklasse zugeordnet wird, um Erweiterbarkeit zu erzielen. Erstens ist es wirklich schwierig, die Bibliothek zu erweitern, wenn nicht klar ist, wo Benutzer sich in die Bibliothek einklinken können. Das Überschreiben der meisten Methoden ist beispielsweise nicht sicher, da die Basisklassenmethode mit impliziten Annahmen geschrieben wurde. Sie müssen wirklich auf Erweiterbarkeit ausgelegt sein.
Noch wichtiger ist, dass Sie etwas, das Teil der öffentlichen API ist, nicht mehr zurücknehmen können. Sie können es nicht umgestalten, ohne den nachgelagerten Code zu brechen. Vorzeitige Offenheit beschränkt die Bibliothek auf ein suboptimales Design. Im Gegensatz dazu ist es sicherer, interne Inhalte privat zu machen, aber Hooks hinzuzufügen, wenn sie später benötigt werden. Dies ist zwar eine vernünftige Methode, um die langfristige Entwicklung einer Bibliothek in Angriff zu nehmen, für Benutzer, die die Bibliothek verwenden müssen, jedoch unbefriedigend jetzt .
Was passiert also stattdessen? Wenn der aktuelle Zustand der Bibliothek erheblich beeinträchtigt ist, können die Entwickler das gesamte Wissen über die tatsächlichen Anwendungsfälle, die sich im Laufe der Zeit angesammelt haben, nutzen und eine Version 2 der Bibliothek schreiben. Es wird toll sein! Es wird all diese Designfehler beheben! Es wird auch länger dauern als erwartet, in vielen Fällen sprudelt es aus. Und wenn die neue Version der alten Version sehr unähnlich ist, kann es schwierig sein, Benutzer zur Migration zu ermutigen. Sie behalten dann zwei inkompatible Versionen bei.
quelle
Jede öffentliche und erweiterbare Klasse / Methode ist Teil Ihrer API, die unterstützt werden muss. Begrenzen dieses Satzes auf a vernünftiges Maß Teilmenge der Bibliothek ermöglicht die größte Stabilität und begrenzt die Anzahl der Fehler, die auftreten können. Es ist eine Managemententscheidung (und selbst OSS-Projekte werden bis zu einem gewissen Grad verwaltet), basierend auf dem, was Sie vernünftigerweise unterstützen können.
Der Unterschied zwischen OSS und Closed Source besteht darin, dass die meisten Menschen versuchen, eine Community rund um den Code zu erstellen und zu erweitern, sodass die Bibliothek von mehr als einer Person verwaltet wird. Es stehen jedoch eine Reihe von Verwaltungstools zur Verfügung:
In ausgereiften Projekten sehen Sie Folgendes:
Wenn die Änderung akzeptiert wurde, der Benutzer sie jedoch beschleunigen möchte, um sie zu reparieren, kann er die Arbeit erledigen und entweder eine Pull-Anforderung oder einen Patch senden (abhängig vom Versionskontroll-Tool).
Keine API ist statisch. Das Wachstum muss jedoch in gewisser Weise gestaltet werden. Indem Sie alles geschlossen halten, bis nachweislich Bedarf besteht, Dinge zu öffnen, vermeiden Sie den Ruf einer fehlerhaften oder instabilen Bibliothek.
quelle
Ich werde meine Antwort umformulieren, da es den Anschein hat, als hätte es bei ein paar Leuten einen Nerv getroffen.
Sichtbarkeit von Klasseneigenschaften / Methoden hat nichts mit Sicherheit oder Offenheit der Quelle zu tun.
Der Grund, warum Sichtbarkeit besteht, ist, weil Objekte für 4 spezifische Probleme anfällig sind:
Wenn Sie Ihr Modul ungekapselt erstellen, werden sich Ihre Benutzer daran gewöhnen, den Modulstatus direkt zu ändern. Dies funktioniert problemlos in einer Umgebung mit nur einem Thread, aber wenn Sie erst einmal darüber nachgedacht haben, Threads hinzuzufügen, können Sie dies auch tun. Sie werden gezwungen sein, den Staat privat zu machen und Sperren / Monitore zusammen mit Gettern und Setzern zu verwenden, die andere Threads auf die Ressourcen warten lassen, anstatt auf ihnen zu rasen. Dies bedeutet, dass Ihre Benutzerprogramme nicht mehr funktionieren, da auf private Variablen nicht auf herkömmliche Weise zugegriffen werden kann. Dies kann bedeuten, dass Sie viele Änderungen vornehmen müssen.
Die Wahrheit ist, dass es viel einfacher ist, mit Blick auf eine einzelne Thread-Laufzeit zu codieren, und mit privatem Schlüsselwort können Sie einfach das synchronisierte Schlüsselwort oder einige Sperren hinzufügen, und der Code Ihrer Benutzer wird nicht beschädigt, wenn Sie ihn von Anfang an gekapselt haben .
Jedes Objekt hat eine Reihe von Dingen, die wahr sein müssen, um in einem konsistenten Zustand zu sein. Leider leben diese Dinge im sichtbaren Bereich des Clients, da es teuer ist, jedes Objekt in seinen eigenen Prozess zu verschieben und über Nachrichten mit ihm zu sprechen. Dies bedeutet, dass es für ein Objekt sehr einfach ist, das gesamte Programm zum Absturz zu bringen, wenn der Benutzer die volle Sichtbarkeit hat.
Dies ist unvermeidlich. Sie können jedoch verhindern, dass ein Objekt versehentlich in einen inkonsistenten Zustand versetzt wird, indem Sie die Dienste einer Schnittstelle schließen, um versehentliche Abstürze zu vermeiden, indem Sie dem Benutzer nur die Interaktion mit dem Objektzustand über eine sorgfältig gestaltete Schnittstelle ermöglichen, die das Programm wesentlich robuster macht . Dies bedeutet nicht, dass der Benutzer die Invarianten nicht absichtlich beschädigen kann. Wenn dies jedoch der Fall ist, stürzt der Client ab. Sie müssen lediglich das Programm neu starten (die zu schützenden Daten sollten nicht clientseitig gespeichert werden ).
Ein weiteres gutes Beispiel, mit dem Sie die Benutzerfreundlichkeit Ihrer Module verbessern können, besteht darin, den Konstruktor privat zu machen. Denn wenn der Konstruktor eine Ausnahme auslöst, bricht er das Programm ab. Ein fauler Ansatz, dies zu lösen, besteht darin, den Konstruktor zu veranlassen, einen Fehler bei der Kompilierung auszulösen, den Sie nur dann erstellen können, wenn er sich in einem try / catch-Block befindet. Indem Sie den Konstruktor privat machen und eine öffentliche statische create-Methode hinzufügen, können Sie die create-Methode null zurückgeben lassen, wenn sie ihn nicht erstellt, oder eine Rückruffunktion verwenden, um den Fehler zu behandeln, wodurch das Programm benutzerfreundlicher wird.
Viele Klassen haben viele Zustände und Methoden und es ist leicht, überwältigt zu werden, wenn man versucht, durch sie zu scrollen. Viele dieser Methoden sind nur visuelles Rauschen wie Hilfsfunktionen, Zustand. Indem Variablen und Methoden privatisiert werden, wird die Belastung des Anwendungsbereichs verringert und es wird dem Benutzer erleichtert, die gewünschten Dienste zu finden.
Im Wesentlichen können Sie damit davonkommen, Hilfsfunktionen nicht außerhalb der Klasse, sondern innerhalb der Klasse zu haben. ohne Sichtbarkeitskontrolle, ohne den Benutzer durch eine Reihe von Diensten abzulenken, die der Benutzer niemals nutzen sollte, sodass Sie davonkommen können, Methoden in eine Reihe von Hilfsmethoden aufzuteilen (obwohl dies immer noch Ihren Bereich verschmutzt, aber nicht den des Benutzers).
Eine gut gestaltete Benutzeroberfläche kann interne Datenbanken / Fenster / Imaging verbergen, von denen sie abhängig ist, und wenn Sie zu einer anderen Datenbank / einem anderen Fenstersystem / einer anderen Imaging-Bibliothek wechseln möchten, können Sie die Benutzeroberfläche und die Benutzer gleich lassen werde es nicht bemerken.
Wenn Sie dies nicht tun, kann es jedoch leicht vorkommen, dass Ihre Abhängigkeiten nicht mehr geändert werden können, da sie offengelegt werden und der Code darauf basiert. Bei einem ausreichend großen System können die Migrationskosten unerschwinglich werden, während eine Kapselung gut verhaltene Clientbenutzer vor zukünftigen Entscheidungen zum Auslagern von Abhängigkeiten schützen kann.
quelle