Wann sollte sich eine Klasse oder ein Modul in einer separaten Assembly / DLL befinden?

22

Gibt es Richtlinien für die Entscheidung, wann sich eine Klasse in einer eigenen Assembly / DLL befinden soll? Ich sehe oft zwei Denkschulen:

1) Jede "Gruppierung" von Klassen gehört zu einer eigenen DLL, z. B. Repositorys, Services, DTOs, Infrastruktur usw.

2) Alles sollte sich in einer einzigen DLL befinden, aber über Namespaces / Ordner getrennt sein, z. B. eine "Core" -DLL mit zusätzlichen Namespaces, z. B. Core.Repositories, Core.Services, Core.DTO usw.

Bei der Arbeit fassen wir einfach alles in einer einzigen Versammlung namens "Business" zusammen. Es gibt einige Ordner, aber keine wirkliche Trennung - Geschäftsobjekte (mit Logik, von denen einige nicht einmal Klassen sein sollten) werden in einem "BusinessObjects" -Ordner ohne Sorgfalt zusammengefasst. Dinge, die in mehr als einer Klasse verwendet werden, befinden sich in einem "Core" -Ordner. Dienstprogramme befinden sich in einem Ordner "Dienstprogramme", die Datenzugriffsinfrastruktur ist ein Ordner "Daten" - Sie haben die Idee.

Für ein neues Modul, an dem ich arbeite, möchte / muss ich eine separate Datenzugriffsebene haben (denken Sie an eine rudimentäre Repository-Implementierung), aber ich möchte es nicht einfach mit den anderen 160 (!) In den Ordner "BusinessObjects" werfen. Klassen dort. Gleichzeitig mache ich mir Sorgen um die Erstellung einer neuen Klassenbibliothek, da jeder daran gewöhnt ist, eine Klasse in der einzelnen Bibliothek zu stopfen. Ein Ordner / Namespace könnte jedoch funktionieren.

Wayne Molina
quelle

Antworten:

12

Ich finde, es ist besser, mehr Projekte (dh Assemblies) mit Klassen zu haben, die in jedem Projekt nach Kategorien unterteilt sind, als ein Projekt mit all diesen Klassen in separaten Namespaces. Sie sollten darauf abzielen, dass Ihre Projekte wiederverwendbar sind und verschiedene Ebenen in einer Anwendung darstellen. Sie können diese Projekte dann in zukünftigen Anwendungen problemlos wiederverwenden, ohne eine ganze Reihe unnötiger Klassen einschließen zu müssen.

Zum Beispiel, basierend auf dem, was Sie erwähnt haben, hätte ich definitiv die folgenden Projekte:

  • Ader
  • Daten
  • Domain (oder BusinessObjects)
  • Dienstleistungen
  • Dienstprogramme (oder Helfer)
Bernard
quelle
2
Leider kann ich die Antwort nicht runterstimmen. Dies ist genau das, was in unserer Firma empfohlen wird. Infolgedessen können Sie mit 6 oder mehr Projekten, z. B. für mittelgroße Websites, keine funktionsbasierte Hierarchie von Verzeichnissen usw. pflegen. Infolgedessen sind die sechs Projekte mit jeweils einem großen Durcheinander verbunden, das das Navigieren in einem Stapel von Dateien unmöglich macht. Normalerweise haben Leute, die dies raten, die merkmalsbasierte Struktur von Projekten bei relativ großen Projekten nie ausprobiert (Entschuldigung für die direkte Beurteilung, aber der Umgang mit den Ergebnissen eines solchen Überschneidens ist ein echtes Problem). Die Antwort von jammycakes rät zum richtigen Vorgehen.
Alehro
Das Teilen von Klassen nach Kategorien führt in großen Projekten zu Chaos. Teilen Sie sie nach Merkmal / Aspekt. Dann spiegeln Namespaces nur diese Aufteilung. Und die gesamte Struktur spiegelt das Lexikon der Spezifikation wider. Das Teilen von Klassen nach Kategorien entspricht dem Hinzufügen von Typabkürzungen zu Variablennamen - normalerweise ist dies ein übler Geruch.
Alehro
Es ist leicht, beide Lösungsvorschläge zu missbrauchen (dh zu viele oder zu wenige Projekte). Ein ausgewogenes Verhältnis zwischen Wiederverwendbarkeit und geringer Kopplung sollte zu mehr Testbarkeit und Wartungsfreundlichkeit führen.
Bernard
16

"Onkel Bob" Martin von Clean Code, SOLID Principles Fame hat hier drei Prinzipien umrissen :

  • Das Prinzip der Wiederverwendung von Freisetzungen: Das Granulat der Wiederverwendung ist das Granulat der Freisetzung.
  • Das Common Closure-Prinzip: Klassen, die sich gemeinsam ändern, werden zusammen verpackt.
  • Das Prinzip der gemeinsamen Wiederverwendung: Klassen, die zusammen verwendet werden, werden zusammen verpackt.

Als Faustregel gilt, dass Sie die Anzahl der Projekte in Ihrer Lösung so gering wie möglich halten sollten. Teilen Sie sie nur auf, wenn dies zur Implementierung einer oder mehrerer spezifischer User Stories erforderlich ist oder wenn eine einzelne Assembly messbare Leistungsprobleme verursacht (in der Regel, wenn sie mehrere Megabyte groß sind).

Marmeladenkuchen
quelle
2
+1 Diese Grundsätze sind gute Richtlinien. Durch das Aufteilen von Klassen in verschiedene Assemblys werden zusätzliche Überlegungen zum Entwurf (z. B. welche Versionen jeder Assembly zusammenarbeiten dürfen) angestellt, wodurch die Codebasis insgesamt und die Wartungskosten erhöht werden.
Ben
1
Zusätzlich zu diesen Prinzipien möchte ich jedoch hinzufügen, dass es oft eine gute Idee ist, Code zu verpacken, der wahrscheinlich in einer separaten Assembly ersetzt oder ausgetauscht wird. Dieser Code profitiert von den zusätzlichen Überlegungen, die zum Koppeln separater Baugruppen erforderlich sind, und die Trennung erleichtert das Testen und Vergleichen der verschiedenen Versionen.
Ben
4
Ja, aber stellen Sie sicher, dass das Ersetzen oder Auswechseln des Geräts unbedingt erforderlich ist. Beispielsweise müssen Frameworks und Bibliotheken von Drittanbietern - NuGet-Pakete und dergleichen - in der Regel mehrere IOC-Container und mehrere Protokollierungsframeworks unterstützen. Für Userland-Code hingegen sind solche Abstraktionen in der Regel rein spekulativ, unnötig und hinderlich und funktionieren letztendlich nie in den seltenen Fällen, in denen sie tatsächlich benötigt werden.
Jammycakes
"Teilen Sie sie nur auf, wenn dies zur Implementierung einer oder mehrerer spezifischer User Storys erforderlich ist oder wenn eine einzelne Assembly messbare Leistungsprobleme verursacht (in der Regel, wenn sie mehrere Megabyte groß sind)." - völlig zufällig und nicht in der Realität Rat begründet
Hyankov
1
Es tut mir leid, aber was genau ist "völlig zufällig und nicht in der Realität verankert" daran? Ich habe diese Antwort gepostet, nachdem ich jahrelang an Lösungen gearbeitet hatte, die in weit mehr Projekte als nötig aufgeteilt wurden, und zwar aus keinem anderen Grund als dem, was Sie tun sollen. Das Ergebnis? Gletscherkompilierungszeiten und ein Morast der Abhängigkeitshölle (besonders in den Tagen vor NuGet). Das Aufteilen von Dingen in separate Baugruppen ist mit Kosten verbunden, und wenn es keinen Vorteil gibt, diese Kosten auszugleichen, müssen Sie nur das Geschäft stehlen.
Jamycakes
4

Einige andere Leitprinzipien, mit denen ich arbeite:

  • Denken Sie, Sie werden diesen Code in anderen Projekten wiederverwenden? Für eine Gruppe verwandter Webanwendungen hatten wir ein Modul für Benutzerkonten, das von allen Anwendungen verwendet wurde, da sie alle dasselbe Modell für Benutzerkonten und Anmeldungen verwendeten. Ich habe ähnliche Dinge mit Geometrie- und Mathematikbibliotheken gemacht und sie in mehreren Anwendungen wiederverwendet, nur indem ich die DLL einbezog.

  • Möchten Sie in der Lage sein, diesen Code zu ändern / bereitzustellen, ohne das gesamte Projekt erneut bereitzustellen / neu zu kompilieren? Manchmal war es hilfreich, nur das Modul neu zu erstellen, die Webanwendung bereitzustellen und neu zu starten.

Es klingt so, als ob in Ihrem Fall ein einfaches und generisches Repository in Zukunft wieder nützlich sein könnte. Es könnte sich lohnen, es in eine neue DLL zu unterteilen, wenn Sie können.

FrustratedWithFormsDesigner
quelle
Ich denke, Ihr zweiter Punkt ist wirklich vernünftig. Die Aufteilung in Module soll die Entwicklungs- / Test- und Kompilierungszeiten erleichtern.
WM