C ++ Singleton Klasse - Vererbung gute Praxis

7

In einem bestehenden Projekt soll ich eine Controller-Klasse (MVC) erben, die als Singleton deklariert ist, um meine eigene Behandlung zu definieren. Wie kann diese Singleton-Klasse richtig abgeleitet werden?

Zuerst erweitere ich den Kontext und brauche diese Vererbung.

Die Anwendung, die ich der vorhandenen Software hinzufüge, möchte ein MVC-Modul verwenden, das fast die gleiche Aufgabe ausführt wie die, die ich ausführen möchte. Es verwendet die gleichen Methoden bis zur Unterschrift und leichte Modifikationen. Das Umschreiben meines eigenen MVC-Moduls würde definitiv zur Duplizierung von Code führen. Das vorhandene Modul ist intrinsisch auf seine Anwendung auf einen anderen Teil der Software ausgerichtet, und ich kann nicht einfach dasselbe Modul verwenden. Wird jedoch als Model-View-Controller-Muster geschrieben, wobei Controller Singleton ist. Ich habe View bereits abgeleitet.

Zweitens habe ich Zweifel, dass ich die Singleton-Klasse klassifizieren kann.

Das Aufrufen des Konstruktors aus der geerbten Klasse würde einfach getinstance () für die Elternklasse aufrufen und ein Objekt nicht von der abgeleiteten Klasse (?) zurückgeben.

Drittens sehe ich einen Weg, mit dem ich umgehen muss. Bitte kommentiere / hilf mir, besser zu werden!

Ich kopiere die gesamte Singleton-Klasse in einer Klasse, die ich AbstractController nennen könnte. Ich leite diese Klasse zweimal ab. Das erste Kind ist Singleton und übernimmt die gesamte Behandlung der Elternklasse. Das zweite Kind ist der Controller für meinen Teil der Anwendung, mit eigener neu definierter Behandlung.

Danke!

    
octoback 21.02.2012, 01:06
quelle

2 Antworten

3

Ich bin mir nicht sicher, ob ich die Situation verstehe, mit der Sie es zu tun haben, und ob es möglich oder angemessen ist, aus dem Singleton abzuleiten, hängt sehr stark davon ab, wie das Singleton implementiert wird.

Aber da Sie "gute Praxis" erwähnt haben, gibt es einige allgemeine Punkte, die Ihnen beim Lesen der Frage einfallen:

  1. Vererbung ist normalerweise nicht das beste Werkzeug, um Code-Wiederverwendung zu erreichen. Siehe: Komposition vor Vererbung bevorzugen?

  2. Die Verwendung von Singleton und "Good Practice" geht in der Regel nicht zusammen! Siehe: Was ist so schlimm an Singletons?

Ich hoffe, das hilft.

    
je4d 21.02.2012, 01:25
quelle
22

Die Wahrheit ist, Singletons und Vererbung spielen nicht gut zusammen.

Ja, ja, die Singleton-Liebhaber und die GoF-Sekte werden über mich sein und sagen: "Nun, wenn Sie Ihren Konstrukteur beschützen ..." und "Sie nicht haben Habe eine getInstance -Methode für die Klasse, du kannst es sagen ... ", aber sie beweisen nur meinen Standpunkt. Singletons müssen durch eine Reihe von Ringen springen, um sowohl Singleton als auch Basisklasse zu sein.

Aber nur um die Frage zu beantworten, sagen wir, wir haben eine Singleton-Basisklasse. Es kann seine Singularität durch Vererbung sogar bis zu einem gewissen Grad durchsetzen. (Der Konstruktor führt eines der wenigen Dinge aus, die funktionieren können, wenn es nicht mehr privat sein kann: Es wird eine Ausnahme ausgelöst, wenn bereits ein anderes Base existiert.) Nehmen wir an, wir haben auch eine Klasse Derived , die von Base erbt. Da wir Vererbung zulassen, können wir auch sagen, dass es eine beliebige Anzahl anderer Unterklassen von Base geben kann, die von Derived erben können oder nicht.

Aber es gibt ein Problem - das, in das Sie entweder schon geraten sind, oder bald. Wenn wir Base::getInstance aufrufen, ohne bereits ein Objekt konstruiert zu haben, erhalten wir einen Nullzeiger. Wir möchten gerne zurückbekommen, welches Singleton-Objekt existiert (es kann ein Base und / oder ein Derived und / oder ein Other sein). Aber es ist schwer, dies zu tun und trotzdem alle Regeln zu befolgen, denn es gibt nur ein paar Möglichkeiten - und alle haben einige Nachteile.

  • Wir könnten einfach ein Base erstellen und es zurückgeben. Schraube Derived und Other . Endergebnis: Base::getInstance() gibt immer genau ein Base zurück. Die Kinderklassen können nie spielen. Kinda besiegt den Zweck, IMO.

  • Wir könnten ein getInstance von uns selbst in unsere abgeleitete Klasse setzen und den Aufrufer Derived::getInstance() sagen lassen, wenn sie speziell ein Derived wollen. Dies erhöht die Kopplung erheblich (weil ein Aufrufer nun wissen muss, dass er spezifisch Derived anfordert, und sich letztendlich an diese Implementierung bindet).

  • Wir könnten eine Variante des letzten machen - aber anstatt die Instanz zu bekommen, erstellt die Funktion nur eine. (Während wir gerade dabei sind, benennen wir die Funktion in initInstance um, da es uns nicht besonders interessiert, was sie bekommt - wir rufen sie einfach so an, dass sie ein neues Derived erstellt und dieses als Eins setzt Wahre Instanz.)

Also (abgesehen von irgendwelchen Ungereimtheiten, die noch unbekannt sind), funktioniert es irgendwie so ...

%Vor%

Und in deinem init-Code sagst du Base::initInstance(); oder Derived::initInstance(); , je nachdem, welchen Typ du für den Singleton haben willst. Sie müssen den Rückgabewert aus Base::getInstance() umwandeln, um natürlich alle Derived -spezifischen Funktionen zu verwenden, aber ohne Casting können Sie alle Funktionen verwenden, die von Base definiert sind, einschließlich virtueller Funktionen, die von Derived überschrieben werden. .

Beachten Sie, dass diese Vorgehensweise auch einige Nachteile hat:

  • Es stellt die meiste Last dar, Singularität in der Basisklasse durchzusetzen. Wenn die Basis diese oder ähnliche Funktionalität nicht hat und Sie sie nicht ändern können, sind Sie irgendwie verrückt.

  • Die Basisklasse kann jedoch nicht alle von der Verantwortung übernehmen. Jede Klasse muss einen geschützten Destruktor deklarieren, oder jemand könnte mitkommen und die eine Instanz löschen, nachdem er sie geworfen hat (in) angemessen, und die ganze Sache geht zur Hölle. Was noch schlimmer ist, kann vom Compiler nicht erzwungen werden.

  • Da wir geschützte Destruktoren verwenden, um zu verhindern, dass irgendein zufälliger Fehler unsere Instanz löscht, wenn der Compiler nicht schlauer ist, als ich befürchte, kann die Laufzeitumgebung Ihre Instanz nicht ordnungsgemäß löschen, wenn das Programm endet . Tschüss, RAII ... hallo "Speicherleck erkannt" Warnungen. (Natürlich wird der Speicher irgendwann von jedem vernünftigen Betriebssystem zurückgewonnen. Aber wenn der Destruktor nicht läuft, können Sie sich nicht darauf verlassen, dass er für Sie aufräumt. Sie müssen eine Aufräumfunktion vor Ihnen aufrufen Beenden, und das wird Ihnen nicht annähernd die gleichen Sicherheiten geben, die RAII Ihnen geben kann.)

  • Sie macht eine initInstance -Methode verfügbar, die, IMO, nicht wirklich in eine API gehört, die jeder sehen kann. Wenn Sie möchten, könnten Sie initInstance private setzen und Ihre init-Funktion wäre ein friend , aber dann macht Ihre Klasse Annahmen über Code außerhalb von sich selbst, und die Kopplung Sache kommt wieder ins Spiel.

Beachten Sie auch, dass der obige Code überhaupt nicht Thread-sicher ist. Wenn Sie das brauchen, sind Sie allein.

Im Ernst, der weniger schmerzhafte Weg besteht darin, den Versuch zu vergessen, die Singularität zu erzwingen. Der einfachste Weg, um sicherzustellen, dass es nur eine Instanz gibt, besteht darin, nur eine zu erstellen . Wenn Sie mehrere Stellen verwenden müssen, sollten Sie die Abhängigkeitsinjektion in Betracht ziehen. (Die Nicht-Framework-Version von dem läuft darauf hinaus, "das Objekt an Dinge zu übergeben, die es brauchen".) Ich ging und entwarf das oben genannte Zeug, nur um zu beweisen, dass ich mich über Singles und Vererbung irrte und nur wieder bestätigte, wie böse die Kombination ist. Ich würde nicht empfehlen, es tatsächlich in echtem Code zu tun.

    
cHao 21.02.2012 05:39
quelle