Ich lerne OOP in C ++ und obwohl mir die Definitionen dieser 3 Konzepte bekannt sind, kann ich nicht wirklich erkennen, wann oder wie ich es verwenden soll.
Verwenden wir diese Klasse für das Beispiel:
class Person{
private:
string name;
int age;
public:
Person(string p1, int p2){this->name=p1; this->age=p2;}
~Person(){}
void set_name (string parameter){this->name=parameter;}
void set_age (int parameter){this->age=parameter;}
string get_name (){return this->name;}
int get_age (){return this->age;}
};
1. Singleton
WIE funktioniert die Einschränkung der Klasse, nur ein Objekt zu haben?
KÖNNEN Sie eine Klasse entwerfen, die NUR 2 Instanzen haben würde? Oder vielleicht 3?
WANN wird ein Singleton empfohlen / benötigt? Ist es eine gute Übung?
Soweit ich weiß, wird die Klasse abstrakt, wenn es nur eine rein virtuelle Funktion gibt. Also, Hinzufügen
virtual void print ()=0;
würde es tun, richtig?
WARUM brauchen Sie eine Klasse, deren Objekt nicht benötigt wird?
3.Interface
Wenn eine Schnittstelle eine abstrakte Klasse ist, in der alle Methoden reine virtuelle Funktionen sind, dann
WAS ist der Hauptunterschied zwischen den beiden?
Danke im Voraus!
Antworten:
1. Singleton
Sie schränken die Anzahl der Instanzen ein, da der Konstruktor privat ist, was bedeutet, dass nur statische Methoden Instanzen dieser Klasse erstellen können (es gibt andere schmutzige Tricks, um dies zu erreichen, aber wir lassen uns nicht mitreißen).
Das Erstellen einer Klasse mit nur 2 oder 3 Instanzen ist problemlos möglich. Sie sollten Singleton immer dann verwenden, wenn Sie der Meinung sind, dass nur eine Instanz dieser Klasse im gesamten System vorhanden sein muss. Das passiert normalerweise bei Klassen, die ein "Manager" -Verhalten haben.
Wenn Sie mehr über Singletons erfahren möchten, können Sie in diesem Beitrag in Wikipedia und speziell für C ++ beginnen .
Es gibt definitiv einige gute und schlechte Dinge an diesem Muster, aber diese Diskussion gehört woanders hin.
2. Abstrakte Klassen
Ja, das ist richtig. Nur eine einzelne virtuelle Methode markiert die Klasse als abstrakt.
Sie werden solche Klassen verwenden, wenn Sie eine größere Klassenhierarchie haben, in der die Hauptklassen nicht wirklich instanziiert werden sollten.
Nehmen wir an, Sie definieren eine Säugetierklasse und erben sie dann an Hund und Katze. Wenn Sie darüber nachdenken, hat es keinen Sinn, ein reines Exemplar eines Säugetiers zu haben, da Sie zuerst wissen müssen, was für ein Säugetier es wirklich ist.
Möglicherweise gibt es eine Methode namens MakeSound (), die nur in den geerbten Klassen Sinn macht, aber es gibt keinen gemeinsamen Klang, den alle Säugetiere erzeugen können (dies ist nur ein Beispiel, bei dem nicht versucht wird, die Geräusche von Säugetieren zu berücksichtigen).
Das bedeutet, dass Säugetiere eine abstrakte Klasse sein sollten, da sie ein für alle Säugetiere gemeinsames Verhalten aufweisen, aber nicht wirklich instanziiert werden sollten. Das ist das Grundkonzept hinter abstrakten Klassen, aber es gibt definitiv mehr, was Sie lernen sollten.
3. Schnittstellen
In C ++ gibt es keine reinen Schnittstellen in demselben Sinne wie in Java oder C #. Die einzige Möglichkeit, eine zu erstellen, besteht darin, eine reine abstrakte Klasse zu haben, die den größten Teil des Verhaltens nachahmt, das Sie von einer Schnittstelle erwarten.
Grundsätzlich besteht das gesuchte Verhalten darin, einen Vertrag zu definieren, mit dem andere Objekte interagieren können, ohne sich um die zugrunde liegende Implementierung zu kümmern. Wenn Sie eine Klasse rein abstrakt machen, bedeutet dies, dass die gesamte Implementierung an einen anderen Ort gehört. Der Zweck dieser Klasse besteht also nur in dem Vertrag, den sie definiert. Dies ist ein sehr leistungsfähiges Konzept in OO und Sie sollten es auf jeden Fall genauer untersuchen.
Weitere Informationen zur Schnittstellenspezifikation für C # in MSDN finden Sie unter:
http://msdn.microsoft.com/en-us/library/ms173156.aspx
C ++ bietet die gleiche Art von Verhalten, indem es eine reine abstrakte Klasse hat.
quelle
Die meisten Leute haben bereits erklärt, was Singletons / abstrakte Klassen sind. Hoffentlich gebe ich eine etwas andere Perspektive und gebe einige praktische Beispiele.
Singletons - Wenn Sie möchten, dass der gesamte aufrufende Code aus beliebigen Gründen eine einzige Instanz von Variablen verwendet, haben Sie die folgenden Optionen:
Wenn Sie globale Daten benötigen, ist Singleton von allen schlechten und bösen Optionen ein VIEL besserer Ansatz als die beiden vorherigen. Sie können Ihre Optionen auch offen halten, wenn Sie morgen Ihre Meinung ändern und statt globaler Daten die Umkehrung der Kontrolle verwenden.
Wo würden Sie einen Singleton verwenden? Hier sind einige Beispiele:
Protokollierung - Wenn Sie möchten, dass Ihr gesamter Prozess ein einziges Protokoll enthält, können Sie ein Protokollobjekt erstellen und es überall weitergeben. Aber was ist, wenn Sie 100.000 Zeilen Legacy-Anwendungscode haben? alle modifizieren? Oder stellen Sie einfach Folgendes vor und beginnen Sie damit, wo immer Sie möchten:
Serververbindungs-Cache - Dies musste ich in unserer Anwendung einführen. Unsere Codebasis, und es gab eine Menge davon, wurde verwendet, um eine Verbindung zu Servern herzustellen, wann immer es ihnen gefiel. Meistens war dies in Ordnung, es sei denn, im Netzwerk gab es Latenzzeiten. Wir brauchten eine Lösung und das Redesign einer 10 Jahre alten Anwendung stand nicht wirklich auf dem Tisch. Ich habe einen einzelnen CServerConnectionManager geschrieben. Dann durchsuchte ich den Code und ersetzte CoCreateInstanceWithAuth-Aufrufe durch einen identischen Signaturaufruf, der meine Klasse aufrief. Jetzt, nachdem der erste Verbindungsversuch zwischengespeichert wurde und der Rest der Zeit "Verbindungsversuche" sofort erfolgte. Einige sagen, Singletons sind böse. Ich sage, sie haben meinen Hintern gerettet.
Für das Debuggen erweisen sich globale laufende Objekttabellen häufig als sehr nützlich. Wir haben einige Kurse, die wir gerne nachverfolgen möchten. Sie stammen alle aus derselben Basisklasse. Während der Instanziierung rufen sie den Objekttabellen-Singleton auf und registrieren sich. Wenn sie zerstört werden, geben sie die Registrierung auf. Ich kann zu jeder Maschine gehen, mich an einen Prozess anhängen und eine Liste laufender Objekte erstellen. Seit über einem halben Jahrzehnt im Produkt und ich hatte nie das Gefühl, dass wir jemals zwei "globale" Objekttabellen benötigen würden.
Wir haben einige relativ komplexe String-Parser-Utility-Klassen, die auf regulären Ausdrücken basieren. Klassen für reguläre Ausdrücke müssen initialisiert werden, bevor Übereinstimmungen ausgeführt werden können. Die Initialisierung ist etwas aufwendig, da dann eine FSM auf der Basis eines Parsing-Strings generiert wird. Danach können jedoch 100 Threads sicher auf die Klasse für reguläre Ausdrücke zugreifen, da sich einmal erstellte FSMs niemals ändern. Diese Parser-Klassen verwenden intern Singletons, um sicherzustellen, dass diese Initialisierung nur einmal erfolgt. Dies verbesserte die Leistung erheblich und verursachte keine Probleme aufgrund von "bösen Singletons".
Wenn Sie dies alles gesagt haben, müssen Sie bedenken, wann und wo Sie Singletons verwenden. 9 von 10 Mal gibt es eine bessere Lösung und auf jeden Fall sollten Sie diese stattdessen verwenden. Es gibt jedoch Situationen, in denen Singleton die absolut richtige Wahl für das Design ist.
Nächstes Thema ... Schnittstellen und abstrakte Klassen. Wie andere bereits erwähnt haben, ist Interface eine abstrakte Klasse, aber es geht darüber hinaus, indem durchgesetzt wird, dass es absolut KEINE Implementierung hat. In einigen Sprachen ist das Schlüsselwort interface Teil der Sprache. In C ++ verwenden wir einfach abstrakte Klassen. Microsoft VC ++ hat einen Schritt unternommen, um dies intern zu definieren:
... Sie können also weiterhin das Schlüsselwort interface verwenden (es wird sogar als "echtes" Schlüsselwort hervorgehoben), aber für den eigentlichen Compiler ist es nur eine Struktur.
Wo würdest du das verwenden? Kehren wir zu meinem Beispiel einer laufenden Objekttabelle zurück. Angenommen, die Basisklasse hat ...
virtueller void print () = 0;
Es gibt deine abstrakte Klasse. Klassen, die Laufzeitobjekttabellen verwenden, werden alle von derselben Basisklasse abgeleitet. Die Basisklasse enthält allgemeinen Code zum Registrieren / Aufheben der Registrierung. Aber es wird niemals von selbst instanziiert. Jetzt kann ich Klassen ableiten (z. B. Anforderungen, Listener, Clientverbindungsobjekte ...), von denen jede print () implementiert, sodass jedes Objekt beim Anhängen an den Prozess und bei der Frage, was ausgeführt wird, seinen eigenen Status meldet.
Beispiele für abstrakte Klassen / Interfaces gibt es unzählige, und Sie verwenden sie definitiv viel häufiger (oder sollten es auch tun), als Sie Singletons verwenden würden. Kurz gesagt, sie ermöglichen es Ihnen, Code zu schreiben, der mit Basistypen funktioniert und nicht an die tatsächliche Implementierung gebunden ist. Auf diese Weise können Sie die Implementierung später ändern, ohne zu viel Code ändern zu müssen.
Hier ist ein weiteres Beispiel. Nehmen wir an, ich habe eine Klasse, die einen Logger implementiert, CLog. Diese Klasse schreibt in die Datei auf der lokalen Festplatte. Ich beginne mit der Verwendung dieser Klasse in meinen alten 100.000 Codezeilen. Überall. Das Leben ist gut, bis jemand sagt, hey lasst uns in die Datenbank schreiben anstatt in eine Datei. Jetzt erstelle ich eine neue Klasse, nenne sie CDbLog und schreibe in die Datenbank. Können Sie sich vorstellen, wie mühsam es ist, 100.000 Zeilen durchzugehen und alles von CLog zu CDbLog zu ändern? Alternativ könnte ich haben:
Wenn der gesamte Code die ILogger-Schnittstelle verwenden würde, müsste ich nur die interne Implementierung von CLogFactory :: GetLog () ändern. Der Rest des Codes würde nur automatisch funktionieren, ohne dass ich einen Finger rühren müsste.
Für weitere Informationen zu Benutzeroberflächen und gutem OO-Design empfehle ich dringend die agilen Prinzipien, Muster und Vorgehensweisen von Onkel Bob in C # . Das Buch ist mit Beispielen gefüllt, die Abstraktionen verwenden und einfache Erklärungen für alles liefern.
quelle
Noch nie. Schlimmer noch, sie sind eine absolute Hündin, die man loswerden muss. Wenn man diesen Fehler einmal macht, kann das viele, viele Jahre dauern.
Der Unterschied zwischen abstrakten Klassen und Interfaces ist in C ++ absolut nichts. Im Allgemeinen verfügen Sie über Schnittstellen, um ein bestimmtes Verhalten der abgeleiteten Klasse anzugeben, ohne jedoch alle angeben zu müssen. Dies macht Ihren Code flexibler, da Sie jede Klasse austauschen können, die der eingeschränkteren Spezifikation entspricht. Laufzeitschnittstellen werden verwendet, wenn Sie eine Laufzeitabstraktion benötigen.
quelle
Singleton ist nützlich, wenn Sie nicht mehrere Kopien eines bestimmten Objekts möchten. Es darf nur eine Instanz dieser Klasse geben. Es wird für Objekte verwendet, die den globalen Status beibehalten und in irgendeiner Weise mit nicht wiedereintretendem Code umgehen müssen.
Ein Singleton mit einer festen Anzahl von 2 oder mehr Instanzen ist ein Multiton , denken Sie an Datenbankverbindungspooling usw.
Interface gibt eine gut definierte API an, mit deren Hilfe die Interaktion zwischen Objekten modelliert werden kann. In einigen Fällen kann es eine Gruppe von Klassen geben, die einige gemeinsame Funktionen haben. In diesem Fall können Sie der Schnittstelle Methodendefinitionen hinzufügen, anstatt sie in Implementierungen zu duplizieren und sie in eine abstrakte Klasse umzuwandeln .
Sie können sogar eine abstrakte Klasse haben, in der alle Methoden implementiert sind, aber Sie markieren sie als abstrakt, um anzuzeigen, dass sie nicht im Istzustand ohne Unterklassen verwendet werden soll.
Hinweis: Interface- und Abstract-Klasse unterscheiden sich in der C ++ - Welt nicht wesentlich durch Mehrfachvererbung usw., haben jedoch in Java et al. Eine unterschiedliche Bedeutung.
quelle
Wenn Sie aufhören, darüber nachzudenken, dreht sich alles um den Polymorphismus. Sie möchten in der Lage sein, einen Code zu schreiben, der mehr kann, als man denkt, je nachdem, was Sie übergeben.
Angenommen, wir haben eine Funktion wie den folgenden Python-Code:
Das Gute an dieser Funktion ist, dass sie jede Liste von Objekten verarbeiten kann, sofern diese Objekte eine "printToScreen" -Methode implementieren. Sie können ihm eine Liste von fröhlichen Widgets, eine Liste von traurigen Widgets oder sogar eine Liste übergeben, die eine Mischung davon enthält, und die foo-Funktion wird immer noch in der Lage sein, ihre Sache richtig zu machen.
Wir verweisen auf diese Art der Einschränkung, dass eine Reihe von Methoden (in diesem Fall printToScreen) als Schnittstelle implementiert werden muss implementiert und Objekte, die alle Methoden implementieren, die Schnittstelle implementieren sollen.
Wenn wir über eine dynamische, entenartige Sprache wie Python sprechen würden, wären wir im Grunde jetzt vorbei. Das statische Typensystem von C ++ verlangt jedoch, dass wir den Objekten in unserer Funktion eine Klasse zuweisen, und es kann nur mit Unterklassen dieser Anfangsklasse gearbeitet werden.
In unserem Fall besteht der einzige Grund, warum die Printable-Klasse vorhanden ist, darin, einen Platz für die printToScreen-Methode anzugeben. Da es keine gemeinsame Implementierung zwischen den Klassen gibt, die die printToScreen-Methode implementieren, ist es sinnvoll, Printable in eine abstrakte Klasse zu verwandeln, die nur zum Gruppieren ähnlicher Klassen in einer gemeinsamen Hierarchie verwendet wird.
In C ++ sind die Konzepte für absctract-Klassen und -Schnittstellen etwas verschwommen. Wenn Sie sie besser definieren möchten, denken Sie über abstrakte Klassen nach, während Schnittstellen in der Regel die allgemeinere, sprachübergreifende Vorstellung von der Menge der sichtbaren Methoden bedeuten, die ein Objekt verfügbar macht. (Obwohl einige Sprachen wie Java den Schnittstellenbegriff verwenden, um sich direkter auf etwas wie eine abstrakte Basisklasse zu beziehen.)
Grundsätzlich geben konkrete Klassen an, wie Objekte implementiert werden, während abstrakte Klassen angeben, wie sie mit dem Rest des Codes interagieren. Um Ihre Funktionen mehr polymorph zu machen, sollten Sie versuchen, einen Zeiger auf die abstrakte Oberklasse zu erhalten, wann immer dies sinnvoll erscheint.
Was Singletons angeht, sind sie wirklich ziemlich nutzlos, da sie oft nur durch eine Gruppe von statischen Methoden oder einfachen alten Funktionen ersetzt werden können. Manchmal haben Sie jedoch eine Einschränkung, die Sie zwingt, ein Objekt zu verwenden, auch wenn Sie nicht wirklich eines verwenden möchten. Daher ist das Singleton-Muster angemessen.
Übrigens haben einige Leute vielleicht bemerkt, dass das Wort "Schnittstelle" in der Java-Sprache eine besondere Bedeutung hat. Ich denke, es ist besser, sich vorerst an die allgemeinere Definition zu halten.
quelle
Schnittstellen
Es ist schwierig, den Zweck eines Tools zu verstehen, das ein Problem löst, das Sie noch nie hatten. Nachdem ich mit dem Programmieren begonnen hatte, verstand ich einige Zeit lang keine Schnittstellen. Wir werden verstehen, was sie getan haben, aber ich wusste nicht, warum Sie eine verwenden möchten.
Hier ist das Problem - Sie wissen, was Sie tun möchten, aber Sie haben mehrere Möglichkeiten, dies zu tun, oder Sie können später ändern, wie Sie es tun möchten. Es wäre schön, wenn Sie die Rolle des ahnungslosen Managers spielen könnten - bellen Sie einige Befehle an und erzielen Sie die gewünschten Ergebnisse, ohne sich darum zu kümmern, wie es gemacht wird.
Angenommen, Sie haben eine winzige kleine Website und speichern alle Informationen Ihrer Benutzer in einer CSV-Datei. Nicht die raffinierteste Lösung, aber sie funktioniert gut genug, um die Benutzerdaten Ihrer Mutter zu speichern. Später hebt Ihre Site ab und Sie haben 10.000 Benutzer. Vielleicht ist es Zeit, eine richtige Datenbank zu verwenden.
Wenn Sie anfangs klug gewesen wären, hätten Sie dies kommen sehen und nicht die Aufrufe getätigt, um direkt in csv zu speichern. Stattdessen würden Sie sich überlegen, wozu Sie es benötigen, unabhängig davon, wie es implementiert wurde. Sagen wir
store()
undretrieve()
. Sie erstellen einePersister
Schnittstelle mit abstrakten Methoden fürstore()
undretrieve()
und erstellen eineCsvPersister
Unterklasse, die diese Methoden tatsächlich implementiert.Später können Sie eine erstellen
DbPersister
, die das Speichern und Abrufen von Daten ganz anders implementiert, als es Ihre CSV-Klasse getan hat.Das Tolle ist, alles, was Sie jetzt tun müssen, ist Veränderung
zu
und dann bist du fertig. Ihre Anrufe an
prst.store()
undprst.retrieve()
werden immer noch funktionieren, sie werden nur "hinter den Kulissen" anders gehandhabt.Jetzt mussten Sie noch die CVS- und DB-Implementierungen erstellen, sodass Sie den Luxus, der Chef zu sein, noch nicht erlebt haben. Die wirklichen Vorteile werden deutlich, wenn Sie Schnittstellen verwenden, die von einer anderen Person erstellt wurden. Wenn jemand anderes so freundlich war, ein
CsvPersister()
undDbPersister()
bereits zu erstellen , müssen Sie nur eines auswählen und die erforderlichen Methoden aufrufen. Wenn Sie sich entscheiden, den anderen später oder in einem anderen Projekt zu verwenden, wissen Sie bereits, wie es funktioniert.Ich bin auf meinem C ++ sehr verrostet, daher verwende ich nur einige allgemeine Programmierbeispiele. Container sind ein gutes Beispiel dafür, wie Schnittstellen Ihr Leben erleichtern.
Sie können haben
Array
,LinkedList
,BinaryTree
usw. alle Subklassen vonContainer
denen hat Methoden wieinsert()
,find()
,delete()
.Wenn Sie nun etwas in die Mitte einer verknüpften Liste einfügen, müssen Sie nicht einmal wissen, was eine verknüpfte Liste ist. Sie rufen einfach an
myLinkedList->insert(4)
und es durchläuft die Liste auf magische Weise und steckt sie dort ab. Selbst wenn Sie wissen, wie eine verknüpfte Liste funktioniert (was Sie wirklich sollten), müssen Sie ihre spezifischen Funktionen nicht nachschlagen, da Sie wahrscheinlich bereits wissen, wozu sie eine andereContainer
früher verwenden.Abstrakte Klassen
Abstrakte Klassen sind ziemlich ähnlich wie Schnittstellen (auch technisch Schnittstellen sind abstrakte Klasse, aber hier ich meine Basisklassen , die einen Teil ihrer Methoden konkretisiert haben.
Angenommen, Sie erstellen ein Spiel und müssen feststellen, ob sich Gegner in Reichweite des Spielers befinden. Sie können eine Basisklasse
Enemy
mit einer Methode erstelleninRange()
. Obwohl es bei den Feinden viele Unterschiede gibt, ist die Methode zur Überprüfung ihrer Reichweite konsistent. Daher wird IhreEnemy
Klasse eine detaillierte Methode zur Überprüfung der Reichweite haben, aber rein virtuelle Methoden für andere Dinge, die keine Ähnlichkeiten zwischen den feindlichen Typen aufweisen.Das Schöne daran ist, wenn Sie den Bereichserkennungscode durcheinander bringen oder ihn optimieren möchten, müssen Sie ihn nur an einer Stelle ändern.
Natürlich gibt es viele andere Gründe für Interfaces und abstrakte Basisklassen, aber dies sind einige Gründe, warum Sie sie möglicherweise verwenden.
Singletons
Ich benutze sie gelegentlich, und ich bin nie von ihnen verbrannt worden. Das heißt nicht, dass sie mein Leben nicht irgendwann ruinieren werden, basierend auf den Erfahrungen anderer Leute.
Hier ist eine gute Diskussion über den globalen Staat von erfahreneren und vorsichtigeren Leuten: Warum ist der globale Staat so böse?
quelle
Im Tierreich gibt es verschiedene Tiere, die Säugetiere sind. Hier ist Säugetier eine Basisklasse und verschiedene Tiere leiten sich davon ab.
Haben Sie jemals ein Säugetier gesehen, das vorbeiging? Ja, ich bin mir oft sicher - aber es waren alle Arten von Säugetieren, nicht wahr?
Sie haben noch nie etwas gesehen, das buchstäblich nur ein Säugetier war. Sie waren alle Arten von Säugetieren.
Das Klassensäugetier muss verschiedene Merkmale und Gruppen definieren, existiert jedoch nicht als physikalische Einheit.
Daher ist es eine abstrakte Basisklasse.
Wie bewegen sich Säugetiere? Gehen, schwimmen, fliegen sie usw.?
Auf Säugetierebene gibt es keine Möglichkeit zu wissen, aber alle Säugetiere müssen sich irgendwie bewegen (sagen wir, dies ist ein biologisches Gesetz, um das Beispiel zu vereinfachen).
Daher ist MoveAround () eine virtuelle Funktion, da jeder Säuger, der von dieser Klasse abstammt, sie anders implementieren muss.
Da jedoch jedes Säugetier MoveAround definieren MUSS, müssen sich alle Säugetiere bewegen, und es ist unmöglich, dies auf Säugetierebene zu tun. Es muss von allen untergeordneten Klassen implementiert werden, hat aber in der Basisklasse keine Bedeutung.
MoveAround ist daher eine rein virtuelle Funktion.
Wenn Sie eine ganze Klasse haben, die Aktivität zulässt, aber nicht in der Lage ist, auf oberster Ebene zu definieren, wie dies geschehen soll, sind alle Funktionen rein virtuell und dies ist eine Schnittstelle.
Zum Beispiel - wenn wir ein Spiel haben, in dem Sie einen Roboter codieren und mir zum Kämpfen auf einem Schlachtfeld vorlegen, muss ich die Funktionsnamen und Prototypen kennen, die aufgerufen werden sollen. Es ist mir egal, wie Sie es auf Ihrer Seite implementieren, solange die "Schnittstelle" klar ist. Daher kann ich Ihnen eine Interface-Klasse zur Verfügung stellen, aus der Sie Ihren Killer-Roboter schreiben können.
quelle