Wie strukturiere ich ein Projekt in Winform richtig?

26

Vor einiger Zeit habe ich angefangen, eine Winform-Anwendung zu erstellen, die zu dieser Zeit noch klein war, und ich habe nicht darüber nachgedacht, wie das Projekt strukturiert werden soll.

Seitdem habe ich nach Bedarf zusätzliche Funktionen hinzugefügt, und der Projektordner wird immer größer. Jetzt ist es wohl an der Zeit, das Projekt in irgendeiner Weise zu strukturieren, aber ich bin mir nicht sicher, wie es richtig ist. Daher habe ich einige Fragen.

Wie restrukturiere ich den Projektordner richtig?

Im Moment denke ich an so etwas:

  • Ordner für Formulare erstellen
  • Ordner für Utility-Klassen erstellen
  • Erstellen Sie einen Ordner für Klassen, die nur Daten enthalten

Was ist die Namenskonvention beim Hinzufügen von Klassen?

Sollte ich Klassen auch umbenennen, damit ihre Funktionalität durch einfaches Betrachten ihres Namens identifiziert werden kann? Zum Beispiel alle Formularklassen umbenennen, sodass ihr Name mit Form endet . Oder ist dies nicht erforderlich, wenn spezielle Ordner für sie erstellt werden?

Was ist zu tun, damit nicht der gesamte Code für das Hauptformular in Form1.cs endet?

Ein weiteres Problem, auf das ich gestoßen bin, ist, dass die Codedatei (Form1.cs) sehr groß wird, da das Hauptformular mit jedem hinzugefügten Feature umfangreicher wird. Ich habe zum Beispiel ein TabControl und jede Registerkarte hat eine Reihe von Steuerelementen und der gesamte Code endete in Form1.cs. Wie vermeide ich das?

Kennen Sie auch Artikel oder Bücher, die sich mit diesen Problemen befassen?

user850010
quelle

Antworten:

24

Es sieht so aus, als wären Sie in einige der häufigsten Fallen geraten, aber keine Sorge, sie können behoben werden :)

Zuerst müssen Sie Ihre Anwendung etwas anders betrachten und sie in Teile zerlegen. Wir können die Stücke in zwei Richtungen teilen. Zuerst können wir die Steuerungslogik (die Geschäftsregeln, den Datenzugriffscode, den Benutzerrechtecode, all diese Dinge) vom UI-Code trennen. Zweitens können wir den UI-Code in Teile zerlegen.

Also werden wir den letzten Teil zuerst machen und die Benutzeroberfläche in Teile zerlegen. Der einfachste Weg, dies zu tun, besteht darin, ein einziges Host-Formular zu haben, auf dem Sie Ihre Benutzeroberfläche mit Benutzersteuerelementen zusammenstellen. Jedes Benutzersteuerelement ist für einen Bereich des Formulars verantwortlich. Stellen Sie sich vor, Ihre Anwendung hatte eine Liste von Benutzern, und wenn Sie auf einen Benutzer klicken, wird ein Textfeld darunter mit dessen Details gefüllt. Sie können die Anzeige der Benutzerliste von einem Benutzer und die Anzeige der Benutzerdetails von einem zweiten Benutzer steuern lassen.

Der eigentliche Trick dabei ist, wie Sie die Kommunikation zwischen den Steuerungen verwalten. Sie möchten nicht, dass 30 Benutzersteuerelemente im Formular zufällig aufeinander verweisen und Methoden aufrufen.

Sie erstellen also eine Schnittstelle für jedes Steuerelement. Die Schnittstelle enthält die Operationen, die das Steuerelement akzeptiert, und alle Ereignisse, die es auslöst. Wenn Sie an diese App denken, ist es Ihnen egal, ob sich die Listenauswahl im Listenfeld ändert. Sie interessieren sich für die Tatsache, dass sich ein neuer Benutzer geändert hat.

Unter Verwendung unserer Beispiel-App würde die erste Schnittstelle für das Steuerelement, das die Listbox von Benutzern hostet, ein Ereignis mit dem Namen UserChanged enthalten, das ein Benutzerobjekt ausgibt.

Das ist großartig, denn wenn Ihnen die Listbox langweilig wird und Sie eine magische 3D-Zoom-Augensteuerung wünschen, codieren Sie sie einfach auf derselben Oberfläche und schließen Sie sie an :)

Ok, so Teil zwei, die UI-Logik von der Domain-Logik zu trennen. Nun, dies ist ein abgenutzter Pfad und ich würde empfehlen, dass Sie sich das MVP-Muster hier ansehen. Es ist ganz einfach.

Jedes Steuerelement wird jetzt als Ansicht (V in MVP) bezeichnet, und wir haben bereits das meiste behandelt, was oben benötigt wird. In diesem Fall die Steuerung und eine Schnittstelle dafür.

Wir fügen lediglich das Modell und den Moderator hinzu.

Das Modell enthält die Logik, die Ihren Anwendungsstatus verwaltet. Sie kennen das Zeug, es würde in die Datenbank gehen, um die Benutzer zu bekommen, in die Datenbank zu schreiben, wenn Sie einen Benutzer hinzufügen, und so weiter. Die Idee ist, dass Sie dies alles in völliger Isolation von allem anderen testen können.

Der Moderator ist etwas kniffliger zu erklären. Es ist eine Klasse, die sich zwischen dem Modell und der Ansicht befindet. Es wird von der Ansicht erstellt, und die Ansicht wird über die zuvor beschriebene Schnittstelle an den Präsentator übergeben.

Der Moderator muss kein eigenes Interface haben, aber ich erstelle sowieso gerne eines. Legt fest, was der Präsentator explizit tun soll.

Der Präsentator würde also Methoden wie ListOfAllUsers verfügbar machen, mit denen die Ansicht die Liste der Benutzer abruft. Alternativ könnten Sie der Ansicht eine AddUser-Methode hinzufügen und diese vom Präsentator aufrufen. Ich bevorzuge letzteres. Auf diese Weise kann der Präsentator der Listbox jederzeit einen Benutzer hinzufügen.

Der Presenter verfügt auch über Eigenschaften wie CanEditUser, die true zurückgeben, wenn der ausgewählte Benutzer bearbeitet werden kann. Die Ansicht fragt dies dann jedes Mal ab, wenn sie es wissen muss. Möglicherweise möchten Sie bearbeitbare in Schwarz und schreibgeschützte in Grau. Technisch gesehen ist dies eine Entscheidung für die Ansicht, da sie sich auf die Benutzeroberfläche konzentriert. Ob der Benutzer überhaupt bearbeitet werden kann, liegt beim Präsentator. Der Moderator weiß es, weil er mit dem Model spricht.

Verwenden Sie also zusammenfassend MVP. Microsoft bietet SCSF (Smart Client Software Factory) an, das MVP in der von mir beschriebenen Weise verwendet. Es macht auch viele andere Dinge. Es ist ziemlich komplex und mir gefällt nicht, wie sie alles machen, aber es kann helfen.

Ian
quelle
8

Ich persönlich bevorzuge es, verschiedene Problembereiche zwischen mehreren Assemblys zu trennen, anstatt alles in einer einzigen ausführbaren Datei zusammenzufassen.

Normalerweise bevorzuge ich es, eine absolut minimale Menge an Code im Einstiegspunkt der Anwendung zu behalten - Keine Geschäftslogik, kein GUI-Code und kein Datenzugriff (Datenbanken / Dateizugriff / Netzwerkverbindungen / usw.); Normalerweise beschränke ich den Einstiegspunktcode (dh die ausführbare Datei) auf etwas in der Art von

  • Erstellen und Initialisieren der verschiedenen Anwendungskomponenten aus allen abhängigen Baugruppen
  • Konfiguration von Komponenten von Drittanbietern, von denen die gesamte Anwendung abhängig ist (z. B. Log4Net für Diagnoseausgabe)
  • Außerdem werde ich wahrscheinlich ein Code-Bit vom Typ "Alle Ausnahmen abfangen und den Stack-Trace aufzeichnen" in die Hauptfunktion aufnehmen, um die Umstände von unvorhergesehenen kritischen / schwerwiegenden Fehlern zu protokollieren.

Was die Anwendungskomponenten selbst anbelangt, strebe ich normalerweise mindestens drei in einer kleinen Anwendung an

  • Datenzugriffsebene (Datenbankverbindungen, Dateizugriff usw.) - Abhängig von der Komplexität der persistenten / gespeicherten Daten, die von der Anwendung verwendet werden, kann es mehrere dieser Assemblys geben. Ich würde wahrscheinlich eine separate Assembly für die Datenbankverarbeitung erstellen (möglicherweise sogar mehrere) Assemblys, wenn die Interaktion mit der Datenbank etwas Komplexes beinhaltet - z. B. wenn Sie mit einer schlecht gestalteten Datenbank arbeiten, müssen Sie möglicherweise DB-Beziehungen im Code behandeln, daher ist es möglicherweise sinnvoll, mehrere Module zum Einfügen und Abrufen zu schreiben.

  • Logic Layer - das wichtigste "Fleisch", das alle Entscheidungen und Algorithmen enthält, mit denen Ihre Anwendung funktioniert. Diese Entscheidungen sollten absolut nichts über die GUI wissen (wer sagt, dass es eine GUI gibt?) Und sollten absolut nichts über die Datenbank wissen (Huh? Es gibt eine Datenbank? Warum nicht eine Datei?). Eine gut gestaltete Logikebene kann hoffentlich "herausgerissen" und in eine andere Anwendung verschoben werden, ohne dass sie neu kompiliert werden muss. In einer komplizierten Anwendung gibt es möglicherweise eine ganze Reihe dieser Logikbaugruppen (weil Sie möglicherweise nur "Teile" herausreißen möchten, ohne den Rest der Anwendung zu verschieben).

  • Präsentationsschicht (dh die GUI); In einer kleinen Anwendung gibt es möglicherweise nur ein einziges "Hauptformular" mit mehreren Dialogfeldern, die alle zu einer einzigen Baugruppe gehören. In einer größeren Anwendung gibt es möglicherweise separate Baugruppen für ganze Funktionsteile der GUI. Die Klassen hier werden kaum mehr tun, als die Benutzerinteraktion zum Laufen zu bringen - es wird kaum mehr sein als eine Shell mit einer grundlegenden Validierung der Eingaben, der Handhabung von Animationen usw. Alle Ereignisse / Schaltflächenklicks, die "etwas tun", werden an weitergeleitet Die Logik-Ebene (meine Präsentations-Ebene enthält streng genommen keinerlei Anwendungslogik, belastet aber auch nicht die Logik-Ebene mit GUI-Code). In der Präsentations-Assembly befinden sich also auch Fortschrittsbalken oder andere ausgefallene Elemente. ies)

Mein Hauptgrund für die Aufteilung der Ebenen "Presentation", "Logic" und "Data" in separate Baugruppen ist folgender: Ich bin der Meinung, dass es besser ist, die Hauptanwendungslogik ohne Ihre Datenbank oder Ihre GUI auszuführen.

Um es anders zu sagen; Wenn ich eine andere ausführbare Datei schreiben möchte, die sich genauso verhält wie Ihre Anwendung, aber eine Befehlszeilenschnittstelle oder eine Webschnittstelle verwendet. und tauscht den Datenbankspeicher gegen einen Dateispeicher (oder eine andere Art von Datenbank) aus, dann kann ich dies tun, ohne die Hauptanwendungslogik überhaupt berühren zu müssen - alles, was ich tun müsste, ist ein kleines Befehlszeilentool zu schreiben und ein anderes Datenmodell, dann "alles zusammenstecken", und ich bin bereit zu gehen.

Sie denken vielleicht: "Nun, ich werde das nie tun wollen , es ist also egal, ob ich diese Dinge nicht austauschen kann." Der eigentliche Punkt ist, dass eines der Kennzeichen einer modularen Anwendung das ist Möglichkeit, 'Chunks' zu extrahieren (ohne dass etwas neu kompiliert werden muss) und diese Chunks an anderer Stelle wiederzuverwenden. Um Code wie diesen zu schreiben, müssen Sie im Allgemeinen lange und gründlich über Entwurfsprinzipien nachdenken. Sie müssen überlegen, ob Sie viel mehr Schnittstellen schreiben und die Kompromisse zwischen verschiedenen SOLID-Prinzipien sorgfältig abwägen müssen wie bei Behavior-Driven-Development (TDD)

Manchmal ist es ein wenig schmerzhaft, diese Trennung von einem vorhandenen monolithischen Codeklumpen zu erreichen, und erfordert eine sorgfältige Überarbeitung. Das ist in Ordnung, Sie sollten in der Lage sein, dies schrittweise zu tun. Möglicherweise erreichen Sie sogar einen Punkt, an dem es zu viele Assemblys gibt, und Sie entscheiden in die andere Richtung zurückgehen und die Dinge wieder zusammenpacken (wenn man zu weit in die entgegengesetzte Richtung geht, kann dies dazu führen, dass diese Baugruppen für sich genommen eher unbrauchbar werden)

Ben Cottrell
quelle
4

Gemäß der Ordnerstruktur ist das, was Sie vorgeschlagen haben, im Allgemeinen in Ordnung. Möglicherweise müssen Sie Ordner für Ressourcen hinzufügen (manchmal erstellen Benutzer Ressourcen so, dass jeder Ressourcensatz unter einem ISO-Sprachcode gruppiert wird, um mehrere Sprachen zu unterstützen), Bilder, Datenbankskripts, Benutzereinstellungen (sofern nicht als Ressourcen behandelt), Schriftarten , externe DLLs, lokale DLLs usw.

Was ist die Namenskonvention beim Hinzufügen von Klassen?

Natürlich möchten Sie jede Klasse außerhalb des Formulars trennen. Ich würde auch empfehlen, eine Datei pro Klasse (obwohl MS dies nicht in Code tut, der zum Beispiel für EF generiert wurde).

Viele Leute verwenden aussagekräftige kurze Substantive im Plural (z. B. Kunden). Einige geben den Namen so ein, dass er in der Nähe des Singularnamens für die entsprechende Datenbanktabelle liegt (wenn Sie eine 1: 1-Zuordnung zwischen Objekten und Tabellen verwenden).

Für die Benennung von Klassen gibt es viele Quellen. Schauen Sie sich beispielsweise folgende Quellen an: .net-Benennungskonventionen und Programmierstandards - Best Practices und / oder STOVF-C # -Codierungsrichtlinien

Was ist zu tun, damit nicht der gesamte Code für das Hauptformular in Form1.cs endet?

Der Hilfscode sollte an eine oder mehrere Hilfsklassen gehen. Anderer Code, der für GUI-Steuerelemente sehr häufig ist, z. B. das Anwenden von Benutzereinstellungen auf ein Raster, kann aus dem Formular entfernt und entweder zu Hilfsklassen hinzugefügt werden oder indem das betreffende Steuerelement in Unterklassen unterteilt und die erforderlichen Methoden dort erstellt werden.

Aufgrund der Ereignisaktivität von MS Windows Forms ist mir nichts bekannt, das Ihnen dabei helfen könnte, Code aus dem Hauptformular zu entfernen, ohne Mehrdeutigkeit und Aufwand zu verursachen. MVVM kann jedoch eine Option sein (in zukünftigen Projekten), siehe zum Beispiel: MVVM für Windows Forms .

Keine Chance
quelle
2

Ziehen Sie MVP als Option in Betracht, da es Ihnen dabei hilft, die Präsentationslogik zu organisieren, die alles in großen Geschäftsanwendungen ist. In der Praxis befindet sich die Anwendungslogik hauptsächlich in der Datenbank. In seltenen Fällen müssen Sie eine Business-Schicht in systemeigenen Code schreiben, sodass nur eine gut strukturierte Präsentationsfunktionalität erforderlich ist.

Eine MVP-ähnliche Struktur führt zu einer Reihe von Präsentatoren oder Controllern, die sich gegenseitig koordinieren und sich nicht mit der Benutzeroberfläche oder dem Code hinter den Kulissen vermischen. Sie können sogar verschiedene Benutzeroberflächen mit demselben Controller verwenden.

Schließlich erhalten Sie durch einfaches Organisieren von Ressourcen, Entkoppeln von Komponenten und Festlegen von Abhängigkeiten mit IoC und DI sowie durch einen MVP-Ansatz die Schlüssel zum Aufbau eines Systems, das häufige Fehler und Komplexität vermeidet, rechtzeitig geliefert wird und für Änderungen offen ist.

Panos Roditakis
quelle
1

Die Struktur eines Projekts hängt vollständig vom Projekt und seiner Größe ab. Sie können jedoch nur wenige Ordner hinzufügen, z

  • Common (Enthält Klassen, zB Utilities)
  • DataAccess (Klassen für den Zugriff auf Daten mit SQL oder einem anderen Datenbankserver, den Sie verwenden)
  • Stile (Wenn Sie CSS-Dateien in Ihrem Projekt haben)
  • Ressourcen (zB Bilder, Ressourcendateien)
  • Workflow (Klassen, die sich auf den Workflow beziehen, sofern vorhanden)

Sie müssen Formulare nicht in einem Ordner ablegen. Benennen Sie Ihre Formulare einfach entsprechend um. Ich meine, es ist gesunder Menschenverstand, dass niemand weiß, welcher Name besser als Sie selbst Ihre Formen sein sollte.

Die Namenskonvention sieht so aus, als würde Ihre Klasse eine "Hello World" -Nachricht drucken. Der Klassenname sollte sich auf die Aufgabe beziehen und der entsprechende Name der Klasse sollte HelloWorld.cs lauten.

Sie können auch Regionen erstellen, z

#region Hello und dann raus endregionam Ende.

Sie können Klassen für Registerkarten erstellen, ich bin mir ziemlich sicher, dass Sie dies können, und eine letzte Sache ist die Wiederverwendung Ihres Codes, wo immer dies möglich ist. Die beste Vorgehensweise besteht darin, Methoden zu erstellen und sie bei Bedarf erneut zu verwenden.

Bücher? erm.

Es gibt keine Bücher, in denen die Struktur von Projekten beschrieben wird, da jedes Projekt anders ist. Solche Dinge lernt man aus Erfahrung.

Hoffe es hat geholfen!

Muhammad Raja
quelle