Ich verstehe, dass eine C ++ - Bibliothek sollte einen Namensraum verwenden, um Namenskollisionen zu vermeiden, aber da muss ich schon:
#include
der richtige Header (oder forward deklariere die Klassen, die ich verwenden möchte) Folgern diese beiden Parameter nicht die gleichen Informationen, die von einem Namespace übermittelt werden. Die Verwendung eines Namespace führt nun einen dritten Parameter ein - den vollständig qualifizierten Namen. Wenn sich die Implementierung der Bibliothek ändert, gibt es jetzt drei mögliche Dinge, die ich ändern muss. Ist das nicht per Definition ein Anstieg der Kopplung zwischen dem Bibliothekscode und meinem Code?
Betrachten Sie zum Beispiel Xerces-C: Es definiert eine rein virtuelle Schnittstelle namens Parser
innerhalb des Namensraums XERCES_CPP_NAMESPACE
. Ich kann die Schnittstelle Parser
in meinem Code verwenden, indem ich die entsprechende Header-Datei einfüge und dann entweder den Namespace using namespace XERCES_CPP_NAMESPACE
oder die Deklarationen / Definitionen mit XERCES_CPP_NAMESPACE::
vorstelle.
Wenn der Code weiterentwickelt wird, muss möglicherweise Xerces zugunsten eines anderen Parsers fallen gelassen werden. Ich bin teilweise von der Änderung in der Bibliotheksimplementierung durch die rein virtuelle Schnittstelle "geschützt" (noch mehr, wenn ich eine Fabrik verwende, um meinen Parser zu konstruieren), aber sobald ich von Xerces zu etwas anderem wechseln muss, muss ich Durchkämmen Sie meinen Code und ändern Sie alle meine using namespace XERCES_CPP_NAMESPACE
und XERCES_CPP_NAMESPACE::Parser
Code.
Ich bin kürzlich auf dieses Problem gestoßen, als ich ein existierendes C ++ - Projekt umstrukturierte, um einige existierende nützliche Funktionen in eine Bibliothek aufzuteilen:
foo.h
%Vor%foo.cpp
%Vor% Zu dieser Zeit, hauptsächlich aus Unwissenheit (und teilweise aus Faulheit), wurde die gesamte Funktionalität von useful.lib
im globalen Namensraum platziert.
Als der Inhalt von useful.lib
wuchs (und mehr Clients begannen, die Funktionalität zu verwenden), wurde beschlossen, den gesamten Code von useful.lib
in seinen eigenen Namespace namens "useful"
zu verschieben.
Die Client .cpp
-Dateien waren einfach zu korrigieren, fügen Sie einfach using namespace useful
;
foo.cpp
%Vor% Aber die Dateien .h
waren wirklich arbeitsintensiv. Anstatt den globalen Namespace durch das Einfügen von using namespace useful;
in die Header-Dateien zu belasten, habe ich die vorhandenen Vorwärtsdeklarationen in den Namespace eingepackt:
foo.h
%Vor%Es gab Dutzende ( und Dutzende ) von Dateien und dies war ein großer Schmerz! Es hätte nicht so schwer sein sollen. Natürlich habe ich etwas falsch gemacht mit dem Design und / oder der Implementierung.
Obwohl ich weiß, dass der Bibliothekscode in einem eigenen Namespace sein sollte, wäre es vorteilhaft gewesen, wenn der Bibliothekscode im globalen Namespace verbleibt und stattdessen versucht, #includes
?
Es klingt für mich so, als ob Ihr Problem in erster Linie darauf zurückzuführen ist, wie Sie Namespaces verwenden (oder nicht), nicht aufgrund der Namespaces selbst.
Es hört sich so an, als würdest du eine Menge von minimal verwandtem "Zeug" in einen Namespace werfen, meistens (wenn du dazu kommst), weil sie zufällig von derselben Person entwickelt wurden. Zumindest IMO sollte ein Namensraum die logische Organisation des Codes widerspiegeln, nicht nur der Zufall, dass ein Bündel von Hilfsmitteln von derselben Person geschrieben wurde.
Der Name eines Namespaces sollte normalerweise ziemlich lang und aussagekräftig sein, um die Möglichkeit einer Kollision zu verhindern. Zum Beispiel gebe ich normalerweise meinen Namen, das geschriebene Datum und eine kurze Beschreibung der Funktionalität des Namensraums ein.
Der meiste Clientcode muss (und sollte oft nicht) den echten Namen des Namespace direkt verwenden. Stattdessen sollte es einen Namespace-Alias definieren und nur der Aliasname sollte in den meisten Codes verwendet werden.
Wenn wir die Punkte zwei und drei zusammensetzen, können wir mit einem Code wie diesem enden:
%Vor%Dadurch wird die Kopplung zwischen dem Client-Code und einer bestimmten Implementierung der Datums- / Zeitklassen entfernt (oder zumindest drastisch reduziert). Wenn ich zum Beispiel die gleichen Datums- / Zeitklassen neu implementieren möchte, könnte ich sie in einen anderen Namespace einfügen und zwischen ihnen wechseln, indem ich einfach den Aliasnamen ändere und neu kompiliere.
Tatsächlich habe ich das manchmal als eine Art Kompilierzeit-Polymorphismus-Mechanismus benutzt. Für ein Beispiel habe ich ein paar Versionen einer kleinen "Display" -Klasse geschrieben, eine, die die Ausgabe in einer Windows-Listbox anzeigt, und eine andere, die die Ausgabe über Iostreams anzeigt. Der Code verwendet dann einen Aliasnamen wie:
%Vor% Der Rest des Codes verwendet nur display::whatever
. Solange beide Namespaces die gesamte Schnittstelle implementieren, kann ich entweder eine verwenden, ohne den Rest des Codes zu ändern, und ohne Laufzeitaufwand durch Verwendung eines Verweises / Verweises auf eine Basisklasse mit virtuellen Funktionen für die Implementierungen.
Der Namespace hat nichts mit der Kopplung zu tun. Die gleiche Kopplung besteht, ob Sie es useful::UsefulClass
oder nur UsefulClass
nennen. Nun, die Tatsache, dass Sie alle diese Arbeit Refactoring tun mussten, sagt Ihnen nur, inwieweit Ihr Code von Ihrer Bibliothek abhängt.
Um die Weiterleitung zu erleichtern, hätten Sie einen forward
Header schreiben können (es gibt ein paar in der STL, Sie können es sicher in Bibliotheken finden) wie usefulfwd.h
, die nur die Bibliotheksschnittstelle (oder implementierende Klassen oder was auch immer) weiterleiten du brauchst). Aber das hat nichts mit der Kopplung zu tun.
Kopplungs- und Namespaces sind immer noch nicht verwandt. Eine Rose würde bei jedem anderen Namen so süß riechen, und deine Klassen sind in jedem anderen Namensraum so verbunden.
(a) Schnittstellen / Klassen / Funktionen aus der Bibliothek
Nicht mehr als du schon hast. Die Verwendung von namespace
-ed Bibliothekskomponenten hilft Ihnen bei der Namensraumverschmutzung.
(b) Implementierungsdetails, die vom Namespace abgeleitet werden?
Warum? Alles, was Sie einschließen sollten, ist eine Kopfzeile useful.h
. Die Implementierung sollte versteckt sein (und sich in useful.cpp
und wahrscheinlich in einer dynamischen Bibliotheksform befinden).
Sie können selektiv nur die Klassen hinzufügen, die Sie von useful.h
benötigen, indem Sie using useful::Useful
declarations haben.
Ich möchte den zweiten Absatz von David Rodríguez - dribeas 'Antwort (upvoted) erweitern:
Um die Weiterleitung zu erleichtern, könnten Sie einen Vorwärts-Header (es gibt ein paar in der STL, Sie können es sicherlich in Bibliotheken finden) wie nützlich fwd.h geschrieben haben, die nur die Bibliothek Schnittstelle (oder implementieren Klassen oder was auch immer Sie brauchen) ). Aber das hat nichts mit der Kopplung zu tun.
Ich denke, das deutet auf den Kern Ihres Problems hin. Namespaces sind hier ein Ablenkungsmanöver, Sie wurden gebissen, indem Sie die Notwendigkeit, syntaktische Abhängigkeiten zu enthalten, zu unterschätzen.
Ich kann Ihre "Faulheit" verstehen: Es ist nicht das Recht zu übersteuern ( Unternehmen HelloWorld.java), aber wenn Sie Ihren Code am Anfang niedrig halten (was nicht unbedingt falsch ist) und der Code ist erfolgreich, der Erfolg wird ihn über seine Liga bringen. Der Trick besteht darin, den richtigen Moment zu erkennen, um auf eine Technik umzuschalten, die Ihr Juckreiz auf eine vorwärtskompatible Weise kratzt .
Sparkling Vorwärtsdeklarationen über ein Projekt ist nur betteln für eine zweite und die folgenden Runden. Sie müssen kein C ++ - Programmierer sein, um den Ratschlag gelesen zu haben: "Deklarieren Sie keine Standard-Streams, verwenden Sie stattdessen <iosfwd>
" (obwohl es einige Jahre war, als dies relevant war, 1999, VC6-Ära, definitiv ). Sie können viele schmerzhafte Schreie von Programmierern hören, die den Ratschlag nicht beherzigt haben, wenn Sie ein wenig innehalten.
Ich kann den Drang verstehen, es niedrig zu halten, aber Sie müssen zugeben, dass #include <usefulfwd.h>
nicht mehr schmerzt als class Useful
und skaliert . Nur diese einfache Delegation würde Sie N-1
Änderungen von class Useful
zu class useful::Useful
ersparen.
Natürlich würde es Ihnen nicht bei allen Verwendungen im Client-Code helfen. Einfache Hilfe: Wenn Sie eine Bibliothek in einer großen Anwendung verwenden, sollten Sie die mit der Bibliothek gelieferten Weiterleitungsheader in anwendungsspezifischen Headern umbrechen. Die Bedeutung davon wächst mit dem Umfang der Abhängigkeit und der Flüchtigkeit der Bibliothek.
src / libuseful / nützlichfwd.h
%Vor%src / myapp / myapp-nützlichefwd.h
%Vor%Im Grunde geht es darum, den Code DRY zu behalten. Du magst vielleicht keine eingängigen TLAs, aber dieses hier beschreibt ein wirklich zentrales Programmierprinzip.
Wenn Sie mehrere Implementierungen Ihrer "nützlichen" Bibliothek haben, ist es nicht wahrscheinlich (wenn Sie nicht unter Ihrer Kontrolle sind), dass sie den gleichen Namespace verwenden, sei es der globale Namespace oder der nützliche Namespace?
Anders ausgedrückt, die Verwendung eines benannten Namespace im Vergleich zum globalen Namespace hat nichts damit zu tun, wie Sie mit einer Bibliothek / Implementierung "gekoppelt" sind.
Jede Entwicklungsstrategie für kohärente Bibliotheken sollte denselben Namespace für die API beibehalten. Die Implementierung könnte verschiedene Namespaces verwenden, die von Ihnen ausgeblendet sind und sich in verschiedenen Implementierungen ändern können. Nicht sicher, ob Sie das mit "Implementierungsdetails, die vom Namespace abgeleitet werden" meinen.
Nein, Sie erhöhen die Kopplung nicht. Wie andere gesagt haben - ich sehe nicht, wie der Namespace die Implementierung ausleckt
Der Verbraucher kann wählen,
zu tun %Vor%meine Stimme ist immer für die letzte - es ist die am wenigsten Verschmutzung
Der erste sollte dringend davon abgeraten werden (durch Erschießen)
Nun, die Wahrheit ist, dass es keine Möglichkeit gibt, die Verwicklung von Code in C ++ zu vermeiden. Die Verwendung von globalem Namespace ist jedoch die schlechteste Idee, da Sie dann nicht zwischen Implementierungen wählen können. Sie enden mit Objekt anstelle von Objekt. Das funktioniert in Ordnung, weil Sie die Quelle bearbeiten können, aber wenn jemand einen solchen Code an einen Kunden versendet, sollte er nicht erwarten, dass er lange Zeit bleibt.
Sobald Sie die using-Anweisung verwenden, können Sie auch in global sein, aber es kann in cpp-Dateien nett sein, sie zu verwenden. Also würde ich sagen, Sie sollten alles in einem Namensraum haben, aber für Inhouse sollte es alle der gleiche Namensraum sein. So können andere Benutzer Ihren Code immer noch ohne eine Katastrophe verwenden.
Tags und Links c++ namespaces design decoupling