Mehrere Klassen mit demselben Namen, aber unterschiedlichen Namespaces?


11

Ich bin auf Code gestoßen (C #, wenn es darauf ankommt), der Klassen hat, die denselben Namen haben, sich aber in ihren Namespaces unterscheiden. Sie alle neigen dazu, dasselbe logische Ding darzustellen, sind aber oft unterschiedliche "Ansichten" desselben Objekts. Die verschiedenen Namespaces sind manchmal Teil derselben Lösung und sogar derselben DLL.

Ich habe meine Gedanken, konnte aber nichts Bestimmtes über diese Praxis finden, das als kluger Einsatz der verfügbaren Werkzeuge oder als zu vermeidendes Muster angesehen werden könnte.

Diese Frage zur Schlumpfbenennung berührt dies, aber aus der anderen Richtung. Die eindeutige Bezeichnung der Namen würde wahrscheinlich ein willkürliches und allgegenwärtiges Präfix erfordern, das mit dieser Frage beseitigt werden sollte.

Was sind die Richtlinien für diese Praxis?


1
In OOP gibt es viele Ausnahmen, aber zunächst betrachte ich dieses schlechte Design, insbesondere wenn alle Klassen den gleichen Grad an Zugänglichkeit haben. Wenn ich einen Klassennamen in einer beliebigen Datei lese, möchte ich sofort wissen, in welchem ​​Namespace sich die Klasse befindet, ohne auf die Namespace-Anweisungen verweisen zu müssen.
Leopold Asperger

Es gibt einen gültigen Fall für mehrere verwandte Objekte, die sich mit demselben Status befassen, aber normalerweise sind sie nach Funktionen getrennt. Vielleicht haben Sie ein Objekt, das ein Modell für eine Ansicht anpasst. Ein anderer könnte eine Berechnung für das Objekt durchführen. Ein anderer könnte ein Adapter oder eine Fassade sein. So haben Sie vielleicht FooAdapter, FooViewer, FooCalculatoretc. , aber sicherlich das Gleiche nicht genannt. Ohne weitere Informationen zu den spezifischen Klassen, über die Sie sprechen, ist es jedoch schwierig, eine Antwort zu geben.

@ JohnGaughan - Employeeals Beispiel betrachten. Es gibt einen Mitarbeiter, der für andere Mitarbeiter bestimmt ist, einen für die Personalabteilung und einen für die externe Exposition. Sie heißen alle "Mitarbeiter" und haben verschiedene Helfer (EmployeeRepository, EmployeeView usw.). Sie befinden sich alle in derselben DLL und haben zwar einige gemeinsame Eigenschaften (Name, Titel), unterscheiden sich jedoch alle (Telefonnummer, Gehalt).
Telastyn

Wenn EmployeeSie mehrmals mit Systemen gearbeitet haben, die ein Modell haben, klingt dies so, als ob Sie ein Modell mit der Vereinigung aller Attribute benötigen und ein typeoder department-Attribut einschließen . Andernfalls wird der Code unnötig dupliziert.

5
War dies nicht einer der Gründe für Namespaces? Namenskollisionen zulassen?
Dan Pichelman

Antworten:


5

Ich werde versuchen, eine Antwort gegen eine solche Verwendung zu geben, nachdem ich dies auch in einem meiner Projekte gesehen und daran gearbeitet habe.

Lesbarkeit des Codes

Bedenken Sie zunächst, dass die Lesbarkeit des Codes wichtig ist und diese Vorgehensweise dem widerspricht. Jemand liest einen Code und sagt einfach, dass er eine Funktion hat doSomething(Employee e). Dies ist nicht mehr lesbar, da Sie aufgrund von 10 verschiedenen EmployeeKlassen in verschiedenen Paketen zuerst auf die Importdeklaration zurückgreifen müssen, um herauszufinden, was Ihre Eingabe wirklich ist.

Dies ist jedoch eine Ansicht auf hoher Ebene, und wir haben oft scheinbar zufällige Namenskonflikte, die niemanden interessieren oder gar finden, da die Bedeutung aus dem verbleibenden Code abgeleitet werden kann und in welchem ​​Paket Sie sich befinden. Man könnte also sogar argumentieren dass es vor Ort kein Problem gibt, denn wenn Sie Employeein einem hrPaket sehen, müssen Sie natürlich wissen, dass es sich um eine HR-Sicht des Mitarbeiters handelt.

Die Dinge brechen jedoch auseinander, sobald Sie diese Pakete verlassen. Sobald Sie in einem anderen Modul / Paket / usw. arbeiten und mit einem Mitarbeiter zusammenarbeiten müssen, beeinträchtigen Sie bereits die Lesbarkeit, wenn Sie den Typ nicht vollständig qualifizieren. Wenn Sie 10 verschiedene EmployeeKlassen haben, funktioniert die automatische Vervollständigung Ihrer IDE nicht mehr und Sie müssen manuell aus dem Mitarbeitertyp auswählen.

Codeduplizierung

Aufgrund der Natur jeder dieser Klassen, die miteinander in Beziehung stehen, wird sich Ihr Code aufgrund vieler Duplikate zwangsläufig verschlechtern. In den meisten Fällen haben Sie so etwas wie den Namen eines Mitarbeiters oder eine Identifikationsnummer, die jede Klasse implementieren muss. Auch wenn jede Klasse ihre eigene Ansicht hinzufügt, werden Sie eine große Menge nutzlosen, aber kostspieligen Codes erhalten, wenn sie die zugrunde liegenden Mitarbeiterdaten nicht gemeinsam nutzen.

Code-Komplexität

Was kann so komplex sein? Schließlich kann jede Klasse es so einfach halten, wie sie will. Was wirklich zu einem Problem wird, ist, wie Sie Änderungen verbreiten. In einer einigermaßen funktionsreichen Software können Sie möglicherweise Mitarbeiterdaten ändern - und das möchten Sie überall widerspiegeln. Sagen Sie, dass eine Dame gerade verheiratet , und Sie müssen sie umbenennen von XzuYaus diesem Grund. Schwer genug, dies überall richtig zu machen, aber noch schwieriger, wenn Sie all diese unterschiedlichen Klassen haben. Abhängig von Ihren anderen Entwurfsoptionen kann dies leicht bedeuten, dass jede der Klassen einen eigenen Listener oder einen solchen Listener implementieren muss, um über Änderungen informiert zu werden. Dies bedeutet im Grunde, dass ein Faktor auf die Anzahl der Klassen angewendet wird, mit denen Sie sich befassen müssen . Und natürlich mehr Codeduplizierung und weniger Lesbarkeit des Codes. Faktoren wie diese sind in der Komplexitätsanalyse möglicherweise nicht zu erkennen, aber sie sind sicher ärgerlich, wenn sie auf die Größe Ihrer Codebasis angewendet werden.

Code-Kommunikation

Abgesehen von den oben genannten Problemen mit der Codekomplexität, die auch mit Entwurfsentscheidungen zusammenhängen, verringern Sie auch Ihre übergeordnete Entwurfsklarheit und verlieren die Fähigkeit, ordnungsgemäß mit Domain-Experten zu kommunizieren. Wenn Sie über Architektur, Designs oder Anforderungen sprechen, können Sie keine einfachen Aussagen mehr treffen wie given an employee, you can do .... Ein Entwickler wird employeezu diesem Zeitpunkt nicht mehr wissen, was wirklich bedeutet. Obwohl ein Domain-Experte es natürlich wissen wird. Das machen wir alle. irgendwie. In Bezug auf die Software ist die Kommunikation jedoch nicht mehr so ​​einfach.

Wie man das Problem loswird

Nachdem er sich dessen bewusst und wenn Ihr Team stimmt zu, dass es ein Problem groß genug ist, in Angriff zu nehmen , dann werden Sie zur Arbeit einen Weg mit ihm zu tun haben. Normalerweise können Sie Ihren Manager nicht bitten, dem gesamten Team eine Woche frei zu geben, damit es den Müll rausbringen kann. Im Wesentlichen müssen Sie also einen Weg finden, um diese Klassen nacheinander teilweise zu eliminieren. Das Wichtigste dabei ist, mit dem gesamten Team zu entscheiden, was ein Employeewirklich ist. Integrieren Sie nicht alle einzelnen Attribute in einen Gott-Mitarbeiter. Denken Sie mehr an einen Hauptmitarbeiter und entscheiden Sie vor allem, wo diese EmployeeKlasse wohnen wird.

Wenn Sie Codeüberprüfungen durchführen, ist es besonders einfach sicherzustellen, dass das Problem zumindest nicht weiter zunimmt, dh alle auf der Strecke zu halten, wenn sie erneut ein neues hinzufügen möchten Employee. Achten Sie auch darauf, dass neue Subsysteme die vereinbarten einhalten Employeeund nicht auf die alten Versionen zugreifen dürfen.

Abhängig von Ihrer Programmiersprache möchten Sie möglicherweise auch die Klassen markieren, die eventuell eliminiert werden @Deprecated, um Ihrem Team zu helfen, sofort zu erkennen, dass sie mit etwas arbeiten, das geändert werden muss.

Um die veralteten Mitarbeiterklassen loszuwerden, können Sie für jeden Einzelfall entscheiden, wie sie am besten beseitigt werden sollen, oder sich einfach auf ein gemeinsames Muster einigen. Sie können den Klassennamen anbringen und um den tatsächlichen Mitarbeiter wickeln, Sie können Muster verwenden (Dekorator oder Adapter kommen in den Sinn) oder oder oder oder.

Um es kurz zu machen: Diese "Praxis" ist technisch einwandfrei, aber voller versteckter Kosten, die erst später anfallen werden. Während Sie das Problem möglicherweise nicht sofort beseitigen können, können Sie sofort damit beginnen, die schädlichen Auswirkungen einzudämmen.


Eine Core-Klasse zu haben scheint eine gute Lösung zu sein. Andere Klassen können sich daraus erstrecken. In Bezug auf die IDE ist die automatische Vervollständigung intelligent genug, um zu wissen, welche Klasse Ihnen vorgeschlagen werden soll, um am besten zum Kontext zu passen. Insgesamt verstehe ich nicht, warum dies ein so großes Problem ist, insbesondere wenn die Codebasis intern verwendet wird.
Informiert

1
Es ist nicht klug genug, wenn Sie sich außerhalb eines Kontexts befinden. Stellen Sie sich eine Klasse vor, die tatsächlich beide problematischen Klassen benötigt - die automatische Vervollständigung hilft überhaupt nicht. Und es wird diese nicht einmal von den anderen zehn Klassen unterscheiden. Das eigentliche Problem ist jedoch das neue Teammitglied, das nicht einmal weiß, was all diese Paketdomänen tatsächlich sind und welche es wählen sollte.
Frank

Wenn mehrere Klassen mit demselben Namen im selben Kontext verwendet werden, ist dies schwierig. Ich habe diesen Fall nicht gesehen. Ich habe mehrere Klassen mit demselben Namen gesehen, aber sie werden nicht oft im selben Kontext verwendet, wie Sie es erwähnt haben.
Informiert

@randomA - Leider ist dieser Fall in dieser Codebasis ziemlich häufig.
Telastyn

Gute Punkte, aber Sie haben nicht wirklich eine Lösung für das Kernproblem gegeben, sondern nur eine Methodik, sobald das Kernproblem gelöst ist ("was ein Mitarbeiter wirklich ist"). Die einzig offensichtliche "Lösung" ("alle einzelnen Attribute in einen Gott-Angestellten integrieren"), die Sie zu Recht verworfen haben. Angenommen, Sie haben DB.Employee, Marshalling.Employee, ViewModels.Employee, GUI.Views.Employee usw., was ist Ihre Lösung?
Pablo H

8

Das Scala Collection Framework ist ein gutes Beispiel dafür. Es gibt veränderbare und unveränderliche Sammlungen sowie serielle und parallele (und in Zukunft möglicherweise auch verteilte) Sammlungen. So gibt es zum Beispiel vier MapMerkmale:

scala.collection.Map
scala.collection.immutable.Map
scala.collection.mutable.Map
scala.collection.concurrent.Map

plus drei ParMaps:

scala.collecion.parallel.ParMap
scala.collecion.parallel.immutable.ParMap
scala.collecion.parallel.mutable.ParMap

Noch interessanter:

scala.collection.immutable.Map            extends scala.collection.Map
scala.collection.mutable.Map              extends scala.collection.Map
scala.collection.concurrent.Map           extends scala.collection.mutable.Map

scala.collecion.parallel.ParMap           extends scala.collection.Map
scala.collecion.parallel.immutable.ParMap extends scala.collecion.parallel.ParMap
scala.collecion.parallel.mutable.ParMap   extends scala.collecion.parallel.ParMap

So ParMaperstreckt ParMapsich erstreckt sich Mapund Maperstreckt sich Maperweitert Map.

Aber seien wir ehrlich: Wie würden Sie sie sonst nennen? Das macht durchaus Sinn. Dafür sind Namespaces da!


3
"Dafür sind Namespaces da!" => +1
svidgen

Ich mag das, aber in der C # /. NET-Welt, wenn ich parallele Klassen in einen parallelen Sub-Namespace verschoben habe, kollidiert dies mit der Hilfsklasse System.Threading.Parallel, die ich zufällig stark benutze, um Parallelität zu erhalten. Ich wollte stattdessen nicht den Namespace 'Concurrent' verwenden, da dieser bereits als 'Concurrent Access' für Sammlungsklassen bezeichnet wird. Als Kompromiss habe ich mich für den Sub-Namespace 'Parallelized' entschieden.
Redcalx

2

Dies ist eine wirklich interessante Frage, bei der die beiden im Wesentlichen gegensätzlichen Antworten richtig sind. Hier ist ein reales Beispiel für diese Diskussion, die wir über ein Projekt von mir führen.

Ich denke, es gibt bisher zwei sehr wichtige Überlegungen, die Sie dazu bringen würden, in die eine oder andere Richtung zu gehen, wobei Sie sich auf die beiden Antworten von @Jorg und @Frank stützen:

  1. Wenn Sie eine Situation erwarten, in der möglicherweise mehr als eine der gleichnamigen Klassen im selben Codekontext verwendet werden muss, ist dies ein Grund, nicht denselben Namen zu verwenden. Das heißt, wenn Sie mit arbeiten müssen, hr.Employeeaber gleichzeitig die Berechtigungen über prüfen müssen security.Employee, wird es sehr schnell nervig.
  2. Umgekehrt, wenn Ihre Klassen mit dem gleichen Namen alle die gleichen oder sehr ähnliche APIs haben, dann ist das ein gutes Beispiel , wenn Sie sollten den gleichen Namen mit unterschiedlichen Namensräumen verwenden. Das Scala-Beispiel ist genau dies, wo alle MapKlassen dieselbe Schnittstelle implementieren oder Unterklassen voneinander sind. In diesem Fall kann die Mehrdeutigkeit oder "Verwirrung" wohl als eine gute Sache bezeichnet werden, da der Benutzer alle Implementierungen der Klasse oder Schnittstelle zu Recht gleich behandeln kann ... das ist der Punkt von Schnittstellen und Unterklassen.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.