Was können Gründe sein, zu verhindern, dass eine Klasse vererbt wird? (z. B. mit versiegelten auf einer c # -Klasse) Im Moment kann ich mir keine vorstellen.
Da das Schreiben von Klassen, die ersatzweise erweitert werden soll, verdammt schwer ist, müssen Sie genaue Vorhersagen darüber treffen, wie zukünftige Benutzer das, was Sie geschrieben haben, erweitern möchten.
Wenn Sie Ihre Klasse versiegeln, müssen sie eine Komposition verwenden, die viel robuster ist.
Wie wäre es, wenn Sie sich bezüglich der Schnittstelle noch nicht sicher sind und keinen anderen Code abhängig von der aktuellen Schnittstelle möchten? [Das ist nicht in meinem Kopf, aber ich würde mich auch für andere Gründe interessieren!]
Bearbeiten: Ein bisschen Googeln gab folgendes: Ссылка
Zitieren:
Es gibt drei Gründe, warum eine versiegelte Klasse besser ist als eine nicht versiegelte Klasse:
Ich möchte Ihnen diese Nachricht von "Code Complete" geben:
Vererbung - Unterklassen - tendiert dazu Arbeit gegen die primäre technische Imperativ haben Sie als Programmierer, Um Komplexität zu kontrollieren, sollten Sie eine starke Voreingenommenheit gegen Vererbung beibehalten.
Die einzige zulässige Verwendung der Vererbung besteht darin, einen bestimmten Fall einer Basisklasse zu definieren, z. B. wenn er von Shape erbt, um Circle abzuleiten. Um diesen Blick auf die Beziehung in umgekehrter Richtung zu überprüfen: Ist eine Form eine Verallgemeinerung von Kreis? Wenn die Antwort ja ist, ist es in Ordnung, Vererbung zu verwenden.
Wenn Sie also eine Klasse haben, für die es keine speziellen Fälle geben kann, die ihr Verhalten spezialisieren, sollte sie versiegelt werden.
Auch aufgrund von LSP (Liskov Substitution Principle) kann man die abgeleitete Klasse verwenden, in der die Basisklasse erwartet wird. Dies hat den größten Einfluss auf die Verwendung der Vererbung: Code mit der Basisklasse kann eine geerbte Klasse erhalten muss immer noch wie erwartet funktionieren . Um externen Code zu schützen, wenn keine Notwendigkeit für Unterklassen besteht, versiegeln Sie die Klasse, und ihre Clients können sich darauf verlassen, dass ihr Verhalten nicht geändert wird. Andernfalls muss externer Code explizit entworfen werden, um mögliche Änderungen im Verhalten in Unterklassen zu erwarten.
Ein konkreteres Beispiel wäre das Singleton-Muster. Sie müssen Singleton versiegeln, um sicherzustellen, dass Sie die "Singletonness" nicht durchbrechen können.
Dies gilt möglicherweise nicht für Ihren Code, aber viele Klassen innerhalb des .NET-Frameworks sind absichtlich versiegelt, so dass niemand versucht, eine Unterklasse zu erstellen.
Es gibt bestimmte Situationen, in denen die Interna komplex sind und bestimmte Dinge sehr genau kontrolliert werden müssen, so dass der Designer entschieden hat, dass niemand die Klasse erben sollte, so dass niemand versehentlich die Funktionalität durch falsches Verwenden von etwas zerstört.
@jjnguy
Ein anderer Benutzer möchte Ihren Code möglicherweise erneut verwenden, indem er Ihre Klasse unterklassifiziert. Ich sehe keinen Grund, das aufzuhalten.
Wenn sie die Funktionalität meiner Klasse nutzen wollen, können sie das mit Containment erreichen, und sie werden als Ergebnis viel weniger spröden Code haben.
Die Komposition scheint oft übersehen zu werden; allzu oft wollen die Leute auf den Erbschaftszug aufspringen. Sie sollten nicht! Substituierbarkeit ist schwierig. Standard für die Zusammensetzung; Sie werden mir auf lange Sicht danken.
Ich stimme mit jjnguy überein ... Ich denke, die Gründe, eine Klasse zu besiegeln, sind selten. Ganz im Gegenteil, ich war mehr als einmal in der Situation, in der ich eine Klasse erweitern möchte, konnte es aber nicht, weil sie versiegelt war.
Als ein perfektes Beispiel habe ich kürzlich ein kleines Paket (Java, nicht C #, aber die gleichen Prinzipien) erstellt, um die Funktionalität um das Memcached-Tool zu erweitern. Ich wollte eine Schnittstelle, so dass ich in Tests die Memcached-Client-API, die ich benutzte, verleumden konnte, und auch, wenn wir Bedarf hatten (wir haben 2 Clients auf der memcached-Homepage). Außerdem wollte ich die Möglichkeit haben, die Funktionalität ganz zu ersetzen, wenn das Bedürfnis oder der Wunsch entstand (wenn beispielsweise die memcached-Server aus irgendeinem Grund ausfallen, könnten wir möglicherweise Hot-Swap mit einer lokalen Cache-Implementierung austauschen).
Ich habe eine minimale Schnittstelle zur Interaktion mit der Client-API verfügbar gemacht, und es wäre großartig gewesen, die Client-API-Klasse zu erweitern und dann einfach eine implements-Klausel mit meiner neuen Schnittstelle hinzuzufügen. Die Methoden, die ich in der Schnittstelle hatte, die mit der tatsächlichen Schnittstelle übereinstimmten, würden dann keine weiteren Details benötigen, und ich würde sie nicht explizit implementieren müssen. Da die Klasse jedoch versiegelt war, musste ich stattdessen Aufrufe an eine interne Referenz für diese Klasse weiterleiten. Das Ergebnis: mehr Arbeit und viel mehr Code ohne triftigen Grund.
Das heißt, ich denke, es gibt potentielle Zeiten, in denen Sie eine Klasse versiegeln möchten ... und das Beste, was mir einfällt, ist eine API, die Sie direkt aufrufen, aber den Clients die Implementierung erlauben. Zum Beispiel, ein Spiel, wo Sie gegen das Spiel programmieren können ... wenn Ihre Klassen nicht versiegelt waren, dann könnten die Spieler, die Features hinzufügen, die API zu ihrem Vorteil nutzen. Dies ist jedoch ein sehr enger Fall, und ich denke, jedes Mal, wenn Sie die volle Kontrolle über die Codebasis haben, gibt es wirklich keinen Grund, eine Klasse versiegeln zu lassen.
Das ist einer der Gründe, warum ich die Ruby-Programmiersprache wirklich mag ... selbst die Kernklassen sind offen, nicht nur um zu erweitern, sondern um die Funktionalität dynamisch zu ADDIEREN und zu ÄNDERN, DER KLASSE SELBST! Es heißt monkeypatching und kann bei Missbrauch ein Albtraum sein, aber es macht verdammt viel Spaß damit zu spielen!
Aus objektorientierter Sicht dokumentiert das Versiegeln einer Klasse die Absicht des Autors ohne Kommentare. Wenn ich eine Klasse versiegele, versuche ich zu sagen, dass diese Klasse ein bestimmtes Wissen oder einen bestimmten Dienst kapseln soll. Es sollte nicht weiter verbessert oder unterklassifiziert werden.
Dies passt gut zum Entwurfsmuster der Mustermethode. Ich habe eine Schnittstelle, die sagt "Ich führe diesen Dienst". Ich habe dann eine Klasse, die diese Schnittstelle implementiert. Aber was ist, wenn die Ausführung dieses Dienstes von einem Kontext abhängt, von dem die Basisklasse nichts weiß (und nicht wissen sollte)? Was passiert, ist, dass die Basisklasse virtuelle Methoden bereitstellt, die entweder geschützt oder privat sind, und diese virtuellen Methoden sind die Hooks für Unterklassen, um die Information oder Aktion bereitzustellen, die die Basisklasse nicht kennt und nicht wissen kann. In der Zwischenzeit kann die Basisklasse Code enthalten, der allen untergeordneten Klassen gemeinsam ist. Diese Unterklassen würden versiegelt werden, weil sie nur eine konkrete Implementierung des Dienstes bewerkstelligen sollen.
Können Sie argumentieren, dass diese Unterklassen weiter unterklassifiziert sein sollten, um sie zu verbessern? Ich würde nein sagen, denn wenn diese Unterklasse den Job überhaupt nicht erledigen könnte, hätte sie niemals von der Basisklasse abgeleitet werden sollen. Wenn Sie es nicht mögen, dann haben Sie die ursprüngliche Schnittstelle, schreiben Sie Ihre eigene Implementierungsklasse.
Wenn Sie diese Unterklassen abdichten, werden auch tiefe Vererbungsgrade vermieden, was für GUI-Frameworks gut funktioniert, aber für Business-Logik-Layer schlecht funktioniert.
Weil Sie immer einen Verweis auf die Klasse und nicht auf eine abgeleitete aus verschiedenen Gründen haben wollen:
ich. Invarianten, die Sie in einem anderen Teil Ihres Codes haben
ii. Sicherheit
usw.
Da es auch eine sichere Wette in Bezug auf die Abwärtskompatibilität ist, können Sie diese Klasse niemals für die Vererbung schließen, wenn sie freigegeben ist.
Vielleicht hatten Sie auch nicht genug Zeit, um die Schnittstelle zu testen, die die Klasse verfügbar macht, um sicherzustellen, dass Sie anderen erlauben können, von ihr zu erben.
Oder vielleicht gibt es keinen Sinn (dass Sie jetzt sehen), eine Unterklasse zu haben.
Oder Sie wollen keine Fehlerberichte, wenn Leute versuchen, Unterklassen zu bilden und nicht alle Details zu bekommen - die Supportkosten senken.
Manchmal ist Ihre Klassenschnittstelle nicht dazu gedacht, verpönt zu werden. Die öffentliche Schnittstelle ist einfach nicht virtuell und während jemand die Funktionalität, die vorhanden ist, außer Kraft setzen könnte, wäre es einfach falsch. Ja, im Allgemeinen sollten sie die öffentliche Schnittstelle nicht überschreiben, aber Sie können sicherstellen, dass dies nicht der Fall ist, indem Sie die Klasse nicht vererbbar machen.
Das Beispiel, an das ich mich jetzt erinnern kann, sind angepasste Klassen mit tiefen Klonen in .Net. Wenn Sie von ihnen erben, verlieren Sie die tiefe Klonfähigkeit. [Ich bin in diesem Beispiel ziemlich unscharf, es ist eine Weile her, seit ich mit IClonable gearbeitet habe] Wenn Sie eine echte Singelton-Klasse haben, wollen Sie wahrscheinlich keine vererbten Formen von es herum, und eine Datenpersistenz-Schicht ist nicht in der Regel Platz wollen Sie eine Menge Vererbung.
Nicht alles, was in einer Klasse wichtig ist, wird leicht im Code behauptet. Es kann Semantiken und Beziehungen geben, die leicht durch erben und überschreiben von Methoden gebrochen werden können. Das Überschreiben einer Methode auf einmal ist eine einfache Möglichkeit, dies zu tun. Sie entwerfen eine Klasse / ein Objekt als eine einzige sinnvolle Einheit und dann kommt jemand und denkt, wenn eine oder zwei Methoden "besser" wären, würde das keinen Schaden anrichten. Das kann oder darf nicht wahr sein. Vielleicht können Sie alle Methoden zwischen privat und nicht privat oder virtuell und nicht virtuell trennen, aber das ist vielleicht noch nicht genug. Die anspruchsvolle Vererbung aller Klassen bedeutet für den ursprünglichen Entwickler eine enorme zusätzliche Belastung, um alle Möglichkeiten vorherzusehen, wie eine erbende Klasse Dinge vermasseln könnte.
Ich kenne keine perfekte Lösung. Ich habe Verständnis dafür, Vererbung zu verhindern, aber das ist auch ein Problem, weil es Komponententests behindert.
Ich habe eine minimale Schnittstelle zur Interaktion mit der Client-API verfügbar gemacht, und es wäre großartig gewesen, die Client-API-Klasse zu erweitern und dann einfach eine implements-Klausel mit meiner neuen Schnittstelle hinzuzufügen. Die Methoden, die ich in der Schnittstelle hatte, die mit der tatsächlichen Schnittstelle übereinstimmten, würden dann keine weiteren Details benötigen, und ich würde sie nicht explizit implementieren müssen. Da die Klasse jedoch versiegelt war, musste ich stattdessen Aufrufe an eine interne Referenz für diese Klasse weiterleiten. Das Ergebnis: mehr Arbeit und viel mehr Code ohne triftigen Grund.
Nun, es gibt einen Grund: Ihr Code ist jetzt etwas von Änderungen an der memcached-Schnittstelle isoliert.
Leistung: (...) Wenn der JIT-Compiler einen Aufruf einer virtuellen Methode mit versiegelten Typen sieht, kann der JIT-Compiler effizienteren Code erzeugen, indem er die Methode nicht virtuell aufruft. (...)
Das ist in der Tat ein guter Grund. Daher sind sealed
und Freunde für performancekritische Klassen sinnvoll.
All die anderen Gründe, die ich bisher erwähnt habe, gehen auf "Niemand berührt meine Klasse" zurück. Wenn Sie sich Sorgen machen, dass jemand seine Interna missversteht, haben Sie es schlecht dokumentiert. Sie können möglicherweise nicht wissen, dass es nichts Nützliches gibt, um Ihrer Klasse hinzuzufügen, oder dass Sie bereits alle denkbaren Anwendungsfälle dafür kennen. Selbst wenn Sie Recht haben und der andere Entwickler Ihre Klasse nicht zur Lösung ihres Problems verwendet haben sollte, ist die Verwendung eines Schlüsselworts keine gute Möglichkeit, einen solchen Fehler zu verhindern. Dokumentation ist. Wenn sie die Dokumentation ignorieren, ihren Verlust.
Die meisten Antworten (wenn abstrahiert) geben an, dass versiegelte / abgeschlossene Klassen ein Werkzeug sind, um andere Programmierer vor möglichen Fehlern zu schützen. Es gibt eine unscharfe Grenze zwischen sinnvollem Schutz und sinnloser Einschränkung. Aber solange der Programmierer derjenige ist, von dem erwartet wird, dass er das Programm versteht, sehe ich kaum Gründe, ihn daran zu hindern, Teile einer Klasse wiederzuverwenden. Die meisten von euch sprechen über Klassen. Aber es geht nur um Objekte!
In seinem ersten Beitrag behauptet DrPizza, dass das Entwerfen einer vererbbaren Klasse bedeutet, mögliche Erweiterungen zu antizipieren. Habe ich richtig verstanden, dass Sie meinen, dass Klasse nur dann vererbbar sein sollte, wenn sie wahrscheinlich gut verlängert wird? Sieht so aus, als ob Sie gewohnt waren, Software aus den abstraktesten Klassen zu entwickeln. Erlauben Sie mir eine kurze Erklärung, wie ich beim Entwerfen denke:
Ausgehend von den sehr konkreten Objekten finde ich Charakteristiken und [also] Funktionalität, die sie gemeinsam haben, und ich abstrahiere sie zur Superklasse dieser bestimmten Objekte. Dies ist eine Möglichkeit, die Code-Duplizität zu reduzieren.
Wenn ich nicht ein bestimmtes Produkt wie ein Framework entwickle, sollte mir mein Code und nicht anderer (virtueller) Code wichtig sein. Die Tatsache, dass andere es nützlich finden, meinen Code wiederzuverwenden, ist ein netter Bonus, nicht mein primäres Ziel. Wenn sie sich dafür entscheiden, liegt es in ihrer Verantwortung, die Gültigkeit der Erweiterungen sicherzustellen. Dies gilt für das gesamte Unternehmen. Up-Front-Design ist entscheidend für die Produktivität.
Zurück zu meiner Idee: Ihre Objekte sollten in erster Linie Ihren Zwecken dienen, nicht einer möglichen Funktion der Subtypen. Ihr Ziel ist es, das gegebene Problem zu lösen. Objektorientierte Sprachen verwenden die Tatsache, dass viele Probleme (oder wahrscheinlicher ihre Teilprobleme) ähnlich sind, und daher kann bestehender Code verwendet werden, um die weitere Entwicklung zu beschleunigen.
Das Abdichten einer Klasse zwingt Leute, die eventuell vorhandenen Code nutzen könnten, ohne Ihr Produkt AKTUELL ZU ÄNDERN, um das Rad neu zu erfinden. (Dies ist eine entscheidende Idee meiner These: Die Vererbung einer Klasse verändert sie nicht! Das scheint ziemlich fussläufig und offensichtlich zu sein, wird aber häufig ignoriert).
Die Leute haben oft Angst, dass ihre "offenen" Klassen zu etwas verdreht werden, das ihre Vorfahren nicht ersetzen kann. Na und? Warum sollte es dich interessieren? Kein Werkzeug kann verhindern, dass ein schlechter Programmierer schlechte Software erstellt!
Ich bemühe mich nicht, vererbbare Klassen als den ultimativ korrekten Entwurf zu bezeichnen, sondern sehe das eher als Erklärung meiner Neigung zu vererbbaren Klassen. Das ist die Schönheit der Programmierung - praktisch unendliche Menge von richtigen Lösungen, jede mit ihren eigenen Vor- und Nachteilen. Ihre Kommentare und Argumente sind willkommen.
Und schließlich, meine Antwort auf die ursprüngliche Frage: Ich würde eine Klasse abschließen, um andere wissen zu lassen, dass ich die Klasse ein Blatt des hierarchischen Klassenbaums betrachte, und ich sehe absolut keine Möglichkeit, dass es ein Elternknoten werden könnte. (Und wenn jemand denkt, dass es tatsächlich möglich ist, dann lag ich entweder falsch oder sie haben mich nicht verstanden).
Tags und Links oop