Java unterscheidet klar zwischen class
und interface
. (Ich glaube, C # auch, aber ich habe keine Erfahrung damit). Beim Schreiben von C ++ gibt es jedoch keine sprachlich erzwungene Unterscheidung zwischen Klasse und Schnittstelle.
Folglich habe ich die Benutzeroberfläche immer als Problemumgehung für das Fehlen einer Mehrfachvererbung in Java angesehen. Eine solche Unterscheidung ist in C ++ willkürlich und bedeutungslos.
Ich habe mich immer für den Ansatz "Dinge auf die offensichtlichste Weise schreiben" entschieden. Wenn ich also in C ++ eine so genannte Schnittstelle in Java habe, z.
class Foo {
public:
virtual void doStuff() = 0;
~Foo() = 0;
};
und ich entschied dann, dass die meisten Implementierer von Foo
einige gemeinsame Funktionen teilen wollten, die ich wahrscheinlich schreiben würde:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() {}
protected:
// If it needs this to do its thing:
int internalHelperThing(int);
// Or if it doesn't need the this pointer:
static int someOtherHelper(int);
};
Das macht dies dann nicht mehr zu einer Schnittstelle im Java-Sinne.
Stattdessen hat C ++ zwei wichtige Konzepte, die sich auf dasselbe zugrunde liegende Vererbungsproblem beziehen:
virtual
ErbschaftKlassen ohne Mitgliedsvariablen können keinen zusätzlichen Speicherplatz belegen, wenn sie als Basis verwendet werden
"Unterobjekte der Basisklasse können eine Größe von Null haben"
Von denen versuche ich, Nummer 1 zu vermeiden, wo immer dies möglich ist - es kommt selten vor, dass dies wirklich das "sauberste" Design ist. # 2 ist jedoch ein subtiler, aber wichtiger Unterschied zwischen meinem Verständnis des Begriffs "Schnittstelle" und den C ++ - Sprachfunktionen. Infolgedessen bezeichne ich Dinge derzeit (fast) nie als "Schnittstellen" in C ++ und spreche in Bezug auf Basisklassen und deren Größen. Ich würde sagen, dass im Kontext von C ++ "Schnittstelle" eine Fehlbezeichnung ist.
Es ist mir jedoch aufgefallen, dass nicht viele Menschen einen solchen Unterschied machen.
- Kann ich irgendetwas verlieren, indem ich zulasse, dass (z. B.
protected
) Nichtfunktionenvirtual
innerhalb einer "Schnittstelle" in C ++ existieren? (Mein Gefühl ist genau das Gegenteil - ein natürlicherer Ort für gemeinsam genutzten Code) - Ist der Begriff "Schnittstelle" in C ++ sinnvoll - impliziert er nur reine
virtual
oder wäre es fair, C ++ - Klassen ohne Mitgliedsvariablen noch als Schnittstelle zu bezeichnen?
quelle
internalHelperThing
in fast allen Fällen simuliert werden.~Foo() {}
in einer abstrakten Klasse ist unter (fast) allen Umständen ein Fehler.Antworten:
In C ++ hat der Begriff "Schnittstelle" nicht nur eine allgemein akzeptierte Definition. Wenn Sie ihn also verwenden möchten, sollten Sie genau sagen, was Sie meinen - eine virtuelle Basisklasse mit oder ohne Standardimplementierungen, eine Header-Datei, die öffentlichen Mitglieder einer beliebigen Klasse und so weiter.
In Bezug auf Ihr Beispiel: In Java (und ähnlich in C #) würde Ihr Code wahrscheinlich eine Trennung der Bedenken bedeuten:
In C ++ können Sie dies tun, sind aber nicht dazu gezwungen. Wenn Sie eine Klasse als Schnittstelle bezeichnen möchten, wenn sie keine Mitgliedsvariablen enthält, aber Standardimplementierungen für einige Methoden enthält, tun Sie dies einfach, aber stellen Sie sicher, dass jeder, mit dem Sie sprechen, weiß, was Sie meinen.
quelle
public
Teile der.h
Datei.Es hört sich ein wenig so an, als wären Sie in die Falle geraten, die Bedeutung dessen zu verwechseln, was es bedeutet, dass eine Schnittstelle konzeptionell sowohl eine Implementierung (die Schnittstelle - Kleinbuchstabe 'i') als auch eine Abstraktion (eine Schnittstelle - Großbuchstaben 'I') ist. ).
In Bezug auf Ihre Beispiele ist Ihr erstes Codebit lediglich eine Klasse. Während Ihre Klasse über eine Schnittstelle in dem Sinne verfügt, dass sie Methoden zum Ermöglichen des Zugriffs auf ihr Verhalten bereitstellt, handelt es sich nicht um eine Schnittstelle im Sinne einer Schnittstellendeklaration, die eine Abstraktionsebene bereitstellt, die einen Verhaltenstyp darstellt, den Klassen möglicherweise möchten implementieren. Die Antwort von Doc Brown auf Ihren Beitrag zeigt Ihnen genau, wovon ich hier spreche.
Schnittstellen werden oft als "Workaround" für Sprachen angepriesen, die keine Mehrfachvererbung unterstützen, aber ich bin der Meinung, dass dies eher ein Missverständnis als eine harte Wahrheit ist (und ich vermute, dass ich dafür in Flammen stehen kann!). Schnittstellen haben wirklich nichts mit Mehrfachvererbung zu tun, da sie weder vererbt noch Vorfahr sein müssen, um funktionale Kompatibilität zwischen Klassen oder Implementierungsabstraktion für Klassen zu gewährleisten. In der Tat können sie es Ihnen ermöglichen, die Vererbung effektiv vollständig zu beseitigen, wenn Sie Ihren Code auf diese Weise implementieren möchten - nicht, dass ich ihn vollständig empfehlen würde, aber ich sage nur, dass Sie dies könntenTu es. Die Realität ist also, dass Schnittstellen unabhängig von Vererbungsfragen ein Mittel bieten, mit dem Klassentypen definiert werden können, und die Regeln festlegen, die bestimmen, wie Objekte miteinander kommunizieren dürfen, sodass Sie das Verhalten bestimmen können, ohne das Ihre Klassen unterstützen sollten Festlegen der spezifischen Methode zur Implementierung dieses Verhaltens.
Eine reine Schnittstelle soll vollständig abstrakt sein, da sie der Definition einen Kompatibilitätsvertrag zwischen Klassen ermöglicht, die ihr Verhalten möglicherweise nicht unbedingt von einem gemeinsamen Vorfahren geerbt haben. Bei der Implementierung möchten Sie auswählen, ob das Implementierungsverhalten in einer Unterklasse erweitert werden soll. Wenn dies nicht der Fall ist
virtual
, können Sie dieses Verhalten später nicht mehr erweitern, wenn Sie entscheiden, dass Sie Nachkommenklassen erstellen müssen. Unabhängig davon, ob die Implementierung virtuell ist oder nicht, definiert die Schnittstelle die Verhaltenskompatibilität an und für sich, während die Klasse die Implementierung dieses Verhaltens für die Instanzen bereitstellt, die die Klasse darstellt.Verzeih mir, aber es ist lange her, dass ich wirklich eine ernsthafte Anwendung in C ++ geschrieben habe. Ich erinnere mich, dass Interface ein Schlüsselwort für die Zwecke der Abstraktion ist, wie ich sie hier beschrieben habe. Ich würde C ++ - Klassen keiner Art als Schnittstelle bezeichnen, sondern stattdessen sagen, dass die Klasse eine Schnittstelle mit den oben beschriebenen Bedeutungen hat. In diesem Sinne ist der Begriff bedeutungsvoll, aber das hängt wirklich vom Kontext ab.
quelle
Java-Schnittstellen sind keine "Problemumgehung", sondern eine bewusste Entwurfsentscheidung, um einige der Probleme bei der Mehrfachvererbung wie der Diamantvererbung zu vermeiden und Entwurfspraktiken zu fördern, die die Kopplung minimieren.
Ihre Schnittstelle mit geschützten Methoden ist ein Lehrbuchfall, bei dem Sie "Komposition gegenüber Vererbung bevorzugen" müssen. Um aus Sutter und Alexandrescu in dem Abschnitt mit diesem Namen aus ihren hervorragenden C ++ - Codierungsstandards zu zitieren :
Indem Sie Ihre Hilfsfunktionen in Ihre Benutzeroberfläche aufnehmen, sparen Sie möglicherweise ein wenig Tipparbeit, aber Sie führen eine Kopplung ein, die Sie später verletzen wird. Langfristig ist es fast immer besser, die Funktionen Ihres Helfers zu trennen und zu übergeben
Foo*
.Die STL ist ein ziemlich gutes Beispiel dafür. Es werden so viele Hilfsfunktionen wie möglich herausgezogen,
<algorithm>
anstatt in den Containerklassen zu sein. Da Siesort()
beispielsweise die öffentliche Container-API für ihre Arbeit verwenden, wissen Sie, dass Sie Ihren eigenen Sortieralgorithmus implementieren können, ohne STL-Code ändern zu müssen. Dieses Design ermöglicht Bibliotheken wie Boost, die die STL verbessern, anstatt sie zu ersetzen, ohne dass die STL etwas über Boost wissen muss.quelle
Sie würden das denken, aber das Einfügen einer geschützten, nicht virtuellen Methode in eine ansonsten abstrakte Klasse diktiert die Implementierung für jeden, der eine Unterklasse schreibt. Dadurch wird der Zweck einer Schnittstelle im reinen Sinne zunichte gemacht, nämlich ein Furnier bereitzustellen, das die darunter liegenden Elemente verbirgt.
Dies ist einer der Fälle, in denen es keine einheitliche Antwort gibt und Sie Ihre Erfahrung und Ihr Urteilsvermögen einsetzen müssen, um eine Entscheidung zu treffen. Wenn Sie mit 100% iger Sicherheit sagen können, dass jede mögliche Unterklasse Ihrer ansonsten vollständig virtuellen Klasse
Foo
immer eine Implementierung der geschützten Methode benötigtbar()
, dannFoo
ist dies der richtige Ort dafür. Sobald Sie eine Unterklasse habenBaz
, die Sie nicht benötigenbar()
, müssen Sie entweder mit der Tatsache leben, dass SieBaz
Zugriff auf Code haben, den Sie nicht benötigen , oder Sie müssen Ihre Klassenhierarchie neu ordnen. Ersteres ist keine gute Übung, und letzteres kann länger dauern als die wenigen Minuten, die nötig gewesen wären, um die Dinge überhaupt richtig zu arrangieren.In Abschnitt 10.4 des C ++ - Standards wird die Verwendung abstrakter Klassen zur Implementierung von Schnittstellen vorübergehend erwähnt, aber nicht formal definiert. Der Begriff ist in einem allgemeinen Informatikkontext von Bedeutung, und jeder, der kompetent ist, sollte verstehen, dass "
Foo
eine Schnittstelle für (was auch immer) ist" eine Form der Abstraktion impliziert. Diejenigen, die mit Sprachen mit definierten Schnittstellenkonstrukten vertraut sind, denken vielleicht rein virtuell, aber jeder, der tatsächlich damit arbeiten muss,Foo
wird sich die Definition ansehen, bevor er fortfährt.quelle
baz
kann immer eine Implementierung wiereturn false;
oder was auch immer geben. Wenn die Methode nicht für alle Unterklassen gilt, gehört sie in keiner Form zur abstrakten Basisklasse.