Projektaufbau für die Verwendung von DI

132 views
Skip to first unread message

Max Raab

unread,
Oct 27, 2014, 3:28:06 PM10/27/14
to clean-code...@googlegroups.com
Hallo, 

ich arbeite an einem Projekt und versuche mich dabei an die saubere Programmierung zu halten.
Bei der Umsetzung bin ich auf ein strukturelles Problem gestoßen, dass ich auch nach intensiven
Einlesen in die Thematik nicht allein lösen konnte:

Meine Projektmappe ist derzeit wie folgt aufgebaut:

-- App (Projekt)
- Program.cs

-- Data (Projekt)
- IncidentReport.cs
- IncidentReportRepositoryBase.cs (abstrakt)
- IncidentReportRepository.cs 
- ApplicationManager.cs
- ApplicationFactory.cs
- LongTermFactory.cs
- ShortTimeFactory.cs
- ...

-- Shared (Projekt)
- IIncidentReport.cs
- IIncidentReportRepository.cs 
- IApplicationManager.cs
- IApplicationFactory.cs
- ILongTermFactory.cs
- IShortTimeFactory.cs
- ...

-- WinForms (Projekt) 
- IncidentReportForm.cs
- ...

In der Program.cs wird über die ApplicationFactory ein IApplicationManager erstellt und über die Run-Methode gestartet.
Der ApplicationManger erhält über den Konstruktor eine ILongTermFactory, über welche ich die Objekte erstellen möchte, 
die eine geringer Laufzeit haben als der ApplicationManager (bleibt während der gesamten Anwendung aktiv).

Die LongTermFactory kann u.a. ein IncidentReportRepository erstellen (liefert den Interfacetyp zurück).
Nun brauche ich aber auch die Möglichkeit, anstelle des Interfacetypen IIncidenReportRepository die abstakte Implementierung
der IncidentReportRepositoryBase erstellen zu können - diese implementiert das Interface.

(Die abstrakte Basisklasse ist nötig, da in dem Interface zu den Properties nur die Getter sichtbar sind, weil nur diese von anderen
Programmieren verwendet werden sollen. Die abstrakte Klasse hat zusätzlich noch die Setter für den internen Gebrauch) <-- gibt 
es hier eine besser Lösung?

In diesem Beispiel kann ich aber in dem Interface ILongTermFactory die Basisklasse nicht als Typ verwenden, weil diese dort 
nicht bekannt ist. Da alle Projekte auf das Shared verweisen, aber von dem Shared Projekt keine Verweise zu anderen Projekten
bestehen.- IncidentReportRepository.cs

Casten möchte ich nicht, da diese Möglichkeit ja nach den ganzen Meinungen im Internet nicht sauber ist.
Statische Klassen sind dies ebenfalls nicht und einen IoC Container in einer extra Assembly müsste ich dann ja wie jetzt die Factories
auch immer weiter reichen (was ja auf das gleiche hinauslaufen sollte?)

Oder halte ich hier zu sehr an der bisherigen Struktur fest und es gibt einen viel besseren Weg der Umsetzung für mein Problem?

Vielen Dank,
Gruß Max
 
 

Ralf Westphal

unread,
Oct 28, 2014, 3:02:12 AM10/28/14
to clean-code...@googlegroups.com
Hallo, Max!

Leider muss ich sagen, dass ich nach der Hälfte deiner Beschreibung ausgestiegen bin :-(
Ich verstehe nicht, worum es geht.

Da gibt es eine Factory. Warum?
Da unterscheidest du Klasse nach Lebenszeit der Objekte. Warum?
Da hast du abstrakte Basisklassen. Warum?

Das ist alles technisches Feuerwerk von dem ich nicht verstehe, ob es im Dienste irgendeiner Anforderung steht.
Funktionalität kann es nicht sein.
Oder ist eine Qualitätsanforderung "geringer Speicherverbrauch" formuliert, die ausschließlich (!) erfüllt werden kann, wenn du so sorgfältig auf Objektlebenszeiten achtest?

Also musst du den ganzen Aufwand treiben, um entweder Wandelbarkeit oder Produktivität herzustellen.
Letzteres scheint mir gerade nach hinten loszugehen.

Es bleibt wohl nur Wandelbarkeit als Ziel. Aber da verstehe ich nicht, inwiefern deine Struktur dienlich sein soll.
Versuch das doch mal zu erklären. Ok?

Ralf Westphal

unread,
Oct 28, 2014, 3:08:24 AM10/28/14
to clean-code...@googlegroups.com
Ach, noch eines: Dass es Klassen mit Interfaces gibt, die Properties haben, scheint mir auf einen Widerspruch zum SRP hinzudeuten.
Das hört sich an, als würdest du hybride Klassen bauen, die Daten für öffentlichen Zugriff halten und gleichzeitig erhebliche Logik enthalten.
Keine gute Sache. Da hilft auch IoC/DI am Ende nichts. Du müllst dir deinen Code voll mit funktionalen Abhängigkeiten und auch noch Datenabhängigkeiten.

Am Montag, 27. Oktober 2014 20:28:06 UTC+1 schrieb Max:

Stephan Roth

unread,
Oct 28, 2014, 3:11:16 AM10/28/14
to clean-code...@googlegroups.com

Guten Morgen,

mir geht es ähnlich wie Ralf, es ist sehr schwierig zu folgen und Dein Problem zu verstehen, ohne die Fachlichkeit Deiner Anwendung und die zu Grunde liegenden Anforderungen zu kennen.

Da wären wir auch sogleich bei einer Auffälligkeit: die Namen der Verzeichnisse in Deiner Projektmappe legen Nahe, dass Du keine fachlich motivierte Struktur dafür gewählt hast. Das würde ich als erstes hinterfragen.

Liebe Grüße,
Stephan

--
Sie erhalten diese Nachricht, weil Sie in Google Groups E-Mails von der Gruppe "Clean Code Developer" abonniert haben.
Wenn Sie sich von dieser Gruppe abmelden und keine E-Mails mehr von dieser Gruppe erhalten möchten, senden Sie eine E-Mail an clean-code-devel...@googlegroups.com.
Wenn Sie in dieser Gruppe einen Beitrag posten möchten, senden Sie eine E-Mail an clean-code...@googlegroups.com.
Gruppe besuchen: http://groups.google.com/group/clean-code-developer
Weitere Optionen finden Sie unter https://groups.google.com/d/optout.

Max

unread,
Oct 28, 2014, 5:00:53 AM10/28/14
to clean-code...@googlegroups.com
Guten Morgen, 

es ist schwer für mich das ganze (vermutlich falsche) Geflecht an Klassen und Strukturen zu beschreiben, deswegen beschreibe ich hier 
nochmal mein Vorhaben ohne konkrete Struktur. 

Es dreht sich um einen Fehlerreport.
Ein Fehler besteht aus einer Identifikationsnummer, einer Beschreibung und einem Datum.
Dieses Fehlerobjekt (IncidentReport) wird erstellt und einer Klasse für die Verarbeitung/Speicherung (IncidentReportRepository) übergeben.
Dort wird das Fehlerobjekt für die Speicherung aufgearbeitet und in das entsprechende Format für die Speicherung überführt.
Wie das Objekt gespeichert wird, also ob in einer Datenbank, in einer Datei oder in der Cloud etc. übernimmt eine von der IncidentReportRepository
parametrierte Klasse, welche über ein Interface bekannt ist.

Die bekannten IncidentReports sollten auch über das IncidentReportRepository abrufbar sein.

Zusätzlich gibt es als erstes Objekt im "Construction-Graph" einen ApplicationManger, welche die Anwendung "zusammenhält" und die Form
erstellt und alle vermittelnden Tätigkeiten übernimmt.

Vorgehen aus Sicht des Anwenders:

Das Programm wird gestartet, es erscheint eine Form mit einer Liste aller bisherigen Fehler und es gibt z.B. einen Button um eine neuen Fehler
anzulegen. Wird der Button gedrückt, geht eine Eingabemaske auf, in welche ich die 3 Informationen zum Fehler eintragen und diesen per "OK"
Button speichern kann.

Der eben angelegte Fehler erscheint in der Liste.

Datenfluss beim Anlegen eines Fehlers:

Das IncidentReportElement wird über die Maske erzeugt und an das IncidentReportRepository übergeben. Dort wird es in ein definiertes Format übertragen
und anhand einer weiteren Klasse z.b. in der Datenbank gespeichert.

Vorgehen beim Start der Anwendung:

In der static void main wird über eine ApplicationFactory ein ApplicationManager erstellt und über eine RunApplication-Methode ausgeführt.
Der ApplicationManager erstellt dort eine Form (ist via Interface bekannt) und erstellt das IncidentReportRepository (ebenfalls über ein Interface bekannt).
Dann kann der ApplicationManager (gibt es auch als Interfacetyp) die bisherigen Fehler beim IncidentReportRepository abfragen und er Form mitteilen.

bisherige Gedanken dazu:

Der ApplicationManager bekommt über den Konstruktor Factories mitgeteilt, über welche er z.B. die MainForm und den IncidentReportManager
erstellen kann. In eine weitere Factory (weil die Lebensdauer der Elemente geringer ist), kann der ApplicationManager IncidentReports erstellen.

"Anforderungen":

  • Die MainForm soll über ein Interface verfügen
  • Die IncidentReport-Klasse soll über ein Interface verfügen
  • Das IncidentReportRepository soll über ein Interface verfügen
  • Es soll ein Interface für die Speicherung der vom IncidentReportRepository aufbereiteten Daten geben (um dann div. Implementierungen anlegen zu können)
  • Ich möchte später einfach umschalten können, wie die Daten gespeichert werden (nach meinem aktuellen Verständnis ist dazu nur ein anpassen der Factories nötig)
offene Punkte:

  • wie transportiere ich z.B. eine Fehler beim Erstellen oder beim Speichern eines IncidentReportRepository an die Form, damit sie dem Benutzer gezeigt werden kann? 
  • Ich wollte die Projektmappe so strukturieren, dass ich ein Projekt rein für die Interfaces habe, damit ich diese (ohne Code preisgeben zu müssen) an dritte Entwickler weitergeben kann (hier weiß ich jedoch nicht ob das überhaupt notwendig bzw. sinnvoll oder umsetzbar ist)
  • Das Thema Interface + abstrakte Basisklasse:
    • für den Fall, ein IncidentReportObjekt über den ApplicationManager abgefragt wird, wollte ich verhindern, dass man dies bearbeiten kann. Deswegen war meine Idee dahinter, in dem öffentlichen Interface nur die Getter zur Verfügung zu stellen und intern mit einer abstrakten Klasse zu arbeiten (welche das Interface implementiert) und zusätzlich die Setter implementiert. Auch hier weiß ich nicht, ob das sinnvoll und überhaupt notwenig ist. Hierzu habe ich keine "best practices" gefunden.

Es handelt sich hierbei nur um ein Demoprojekt, um mir über die Struktur Gedanken zu machen.
Ich hoffe, ich konnte mein Problem jetzt etwas genauer beschreiben.

Wie würde ich so ein Projekt jetzt strukturell am saubersten aufbauen?

Gruß Max

Am Montag, 27. Oktober 2014 20:28:06 UTC+1 schrieb Max:

Ralf Westphal

unread,
Oct 29, 2014, 3:39:29 AM10/29/14
to clean-code...@googlegroups.com
Danke für die Erläuterungen. Jetzt verstehe ich das Ganze besser.

Das "Grundübel" scheint mir weiterhin zu sein, dass du von einer Form ausgehst. Da müssen Interfaces sein, da müssen Factories sein usw.
Dahinter verschwindet der gute Ansatz deiner Beschreibung, in der schon von "Fluss" die Rede ist :-)

Als einzig "variables Bauteil" sehe ich derzeit das Repository. Dafür ein Interface, klingt sinnig. Aber die anderen Funktionseinheiten... hm... da zweifle ich. (Was nicht bedeutet, dass DI nicht zum Einsatz kommen sollte.)

Ich biete dir mal an, dass wir über dein Szenario in einer online Videositzung plaudern. Dann kann ich dir flüssiger ;-) klar machen, was ich meine. Wenn du magst, schreib mir eine Email.
Reply all
Reply to author
Forward
0 new messages