C# Schnittstelle: Verständnis des Preisformularverkabelung (Tim Corey, Lektion 09)
In Tim Coreys "C# App Start To Finish"-Serie konzentriert sich Lektion 09 auf das Verkabeln eines Preisformulars. Auf den ersten Blick scheint dieses Formular einfach zu sein—es sammelt nur Benutzereingaben, validiert sie, erstellt ein Modell und speichert es. Aber Tim erklärt, dass die wahre Komplexität darin besteht, zu entscheiden, wo die Daten gespeichert werden sollen: eine Datenbank, eine Textdatei oder beides. Tims Video führt uns durch die Lösung, indem er ein Kernkonzept der C#-Programmierung einführt: Schnittstellen.
In diesem Artikel werden wir einen genaueren Blick auf Schnittstellen durch Tims Erklärung werfen, damit Sie besser verstehen können, wie sie helfen, skalierbare, wartbare Anwendungen zu erstellen.
Das Problem: Wo speichern wir die Daten?
Tim beginnt damit, den Zweck des Preisformulars zu erläutern: Es nimmt Eingaben an, validiert sie und speichert sie in einem Speicher. Aber er warnt davor, dass der schwierige Teil darin besteht, zu entscheiden, wo die Daten gespeichert werden sollen. Er betont, dass Tutorials dies oft überspringen, weil es nicht einfach ist, aber er möchte, dass Lernende es direkt angehen.
Er erklärt, dass Sie zunächst eine einfache Lösung versuchen könnten: Überprüfen, ob Sie SQL oder eine Textdatei verwenden, und dann den richtigen Speichervorgang ausführen. Aber Tim zeigt schnell, wie unschön und unwartbar das wird. Wenn jedes Formular überprüfen muss, welche Speicherart verwendet werden soll, wird der Code dupliziert, unübersichtlich und schwer zu ändern.
Der unschöne Weg: Hardcodierte Bedingungen
Tim skizziert ein Beispiel für Pseudocode. Er erklärt, dass Sie möglicherweise damit beginnen, einen booleschen Wert zu überprüfen, z.B. usingSQL == true, dann öffnen Sie eine Datenbankverbindung, speichern das Modell und geben es mit einer ID zurück. Dann könnten Sie dasselbe für Textdateien tun, indem Sie manuell eine ID generieren, weil Textdateien dies nicht automatisch tun.
Er weist darauf hin, dass dies schnell repetitiv wird. Mehrere Formulare benötigen diese Logik, und jedes Mal, wenn Sie eine neue Datenquelle wie MySQL hinzufügen, müssen Sie jedes Formular aktualisieren. Tim nennt dies "nicht skalierbar" und betont, dass es gegen das "DRY"-Prinzip verstößt (Don't Repeat Yourself). Er sagt klar: "Es muss einen besseren Weg geben."
Den Faden ziehen: Der bessere Ansatz
Tim führt seine Strategie ein: Den Faden ziehen. Er beginnt damit zu fragen, welche Informationen der Code benötigt und woher sie kommen. Er identifiziert zwei Schlüsselfragen:
Wie wissen wir, welche Datenquelle verwendet werden soll?
Wie verbinden wir verschiedene Datenquellen, um die gleiche Aufgabe zu erledigen?
Tim erklärt, dass der eigentliche Vorgang des Speicherns das Einzige ist, was sich unterscheidet. Aus Sicht des Formulars muss es nur sagen: "Hier ist das Modell. Speicher es." Das Formular sollte nicht interessieren, ob es in SQL oder in eine Textdatei speichert.
Die Lösung: Globale Konfiguration + Schnittstelle
Tim schlägt ein globales Konfigurationssystem vor. Er sagt, dass, um zu wissen, welche Datenquelle zu verwenden ist, die Anwendung global zugängliche Daten benötigt, und er schlägt vor, eine statische Klasse zu verwenden, um diese Informationen zu speichern. Er erkennt an, dass globale Variablen normalerweise vermieden werden, aber in diesem Fall genau das nötig sind.
Als nächstes erklärt Tim das Schlüsselkonzept: Schnittstellen. Er definiert eine Schnittstelle als Vertrag—ein Versprechen, dass jede Klasse, die sie implementiert, bestimmte Methoden oder Eigenschaften enthalten wird. Tim betont, dass dies der Anwendung ermöglicht, dieselbe Methode unabhängig von der Datenquelle aufzurufen. Das Formular kümmert sich nicht darum, ob es sich um SQL oder eine Textdatei handelt; es interessiert sich nur für den Aufruf der Methode.
Tim sagt: "Wenn Sie dieselbe Aufgabe ausführen müssen, jedoch hinter den Kulissen auf zwei verschiedene Arten, benötigen Sie eine Schnittstelle."
Erstellen der Schnittstelle
Tim geht zur praktischen Implementierung über, indem er eine Schnittstelle in der Tracker Library erstellt. Er nennt sie IDataConnection und erklärt die Konvention, Schnittstellen mit einem "I" zu beginnen. Er hebt hervor, dass dies wichtig ist, um sie klar als Schnittstelle zu identifizieren.
Tim fügt der Schnittstelle eine einzige Methode hinzu:
PrizeModel ErstellenPreis(PrizeModel model);
Er erklärt, dass diese Methode ein Vertrag ist: Sie muss in jeder Klasse vorhanden sein, die IDataConnection implementiert. Das Formular wird diese Methode aufrufen und erwarten, ein PrizeModel mit einer ID zurückzubekommen. Tim erklärt, dass dies der Grund ist, warum das Formular von der Speicherart unabhängig bleibt.
Erstellen der Global Config Statischen Klasse
Als Nächstes erstellt Tim eine statische Klasse namens GlobalConfig. Er erklärt, dass eine statische Klasse nicht instanziiert werden kann und global zugänglich ist. Hier wird die Anwendung die Liste der verfügbaren Datenverbindungen speichern.
Er definiert eine Eigenschaft:
public static List<IDataConnection> Verbindungen { get; private set; }
Tim erklärt die Verwendung von private set, so dass nur die Klasse selbst die Liste ändern kann, während andere Teile der Anwendung sie nur lesen können.
Dann erstellt er die Methode:
public static void InitialisiereVerbindungen(bool datenbank, bool textDateien)
Diese Methode richtet die verfügbaren Datenverbindungen ein. Tim betont, dass die Liste mehrere Verbindungen zulässt, was bedeutet, dass die Anwendung in SQL, Textdateien oder beides speichern kann.
Verstehen von Schnittstellen: Ein Praxisbeispiel
Tim macht eine Pause, um die Lernenden zu beruhigen, dass dies komplexes Material ist, aber machbar. Er empfiehlt, das Video einmal anzusehen und dann erneut anzuschauen, während man parallel mitcodiert.
Er erklärt, dass die Schnittstelle ein Vertrag ist und jede Klasse, die sie implementiert, dem Vertrag folgen muss. Er demonstriert dies, indem er eine SQLConnector-Klasse erstellt, die IDataConnection implementiert.
Wenn die Klasse erstellt wird, warnt Visual Studio davor, dass der Vertrag nicht erfüllt ist. Tim zeigt, wie man "Schnittstelle implementieren" verwendet, um automatisch die Methode CreatePrize zu generieren. Er erklärt auch das NotImplementedException-Gerüst und warum es existiert—es ermöglicht dem Code zu kompilieren, ohne vorzutäuschen, dass die Methode funktioniert.
Erstellen der SQL- und Text-Connectoren
Tim fügt die SQLConnector-Klasse und die TextConnector-Klasse hinzu, die beide IDataConnection implementieren. Er erklärt, dass obwohl das Speichern in einer SQL-Datenbank und das Speichern in eine Textdatei sehr unterschiedliche Prozesse sind, sie dennoch denselben Schnittstellenvertrag erfüllen.
Er fügt für den Moment einen einfachen Beispielwert hinzu und platziert TODO-Kommentare, um sich selbst daran zu erinnern, die eigentliche Speicherung später zu implementieren. Das hält die Anwendung funktional während des Fortschreitens durch die Lektion.
Letzte Einrichtung: Verkabeln der Global Config
Tim kehrt zur GlobalConfig-Klasse zurück und verkabelt die tatsächlichen Verbindungen. Er zeigt, wie man die Connections-Liste initialisiert und Instanzen von SQLConnector und TextConnector hinzufügt.
Er erklärt, warum zwei separate if-Anweisungen notwendig sind, anstelle von if-else—weil der Benutzer möglicherweise gleichzeitig in beide Datenquellen speichern möchte.
Wo sollte InitializeConnections aufgerufen werden?
Tim erklärt, dass InitializeConnections beim Start der Anwendung aufgerufen werden muss. Er ändert Program.cs und ruft auf:
GlobalConfig.InitialisiereVerbindungen(true, true);
bevor das Formular gestartet wird. Das stellt sicher, dass die Verbindungs-Liste bereit und im gesamten Anwendungslauf erreichbar ist.
Er ändert dann das Startformular zu CreatePrizeForm, um die Funktionalität sofort zu testen.
Validieren des Preisformulars
Tim öffnet das Formular und erklärt die erste Aufgabe: die vier Felder zu validieren. Er zieht es vor, Ereignishandler sauber zu halten, also erstellt er eine private Methode namens ValidateForm().
Tim erklärt, dass diese Methode von überall aufgerufen werden kann, nicht nur beim Klicken des Buttons. Sie gibt einen Booleschen Wert zurück, der anzeigt, ob das Formular gültig ist oder nicht. Er zeigt sein Muster, eine Ausgabevariable zu verwenden:
bool ergebnis = true; return ergebnis;
Er sagt, dass er es mag, mit true zu beginnen, weil es einfacher zu false zu ändern ist, wenn etwas falsch ist, als es nach jeder Überprüfung auf true zu setzen.
Überprüfung der Platznummer
Tim erklärt die erste Validierung: Die Platznummer muss eine ganze Zahl größer als null sein.
Er verwendet int.TryParse, um die PlaceNumberValue.Text (ein String) in eine ganze Zahl zu konvertieren. Tim erklärt im Detail, wie TryParse funktioniert:
-
Es nimmt einen String und versucht, ihn in eine Zahl zu konvertieren.
-
Es gibt einen booleschen Wert zurück, der Erfolg oder Misserfolg anzeigt.
- Es verwendet ein out-Parameter, um den konvertierten Wert auszugeben.
Tim betont, dass TryParse sicherer als Parse ist, weil es bei fehlerhafter Eingabe nicht abstürzt—es gibt false zurück und setzt die Ausgabe auf null.
Dann erklärt er die Logik:
-
Wenn placeNumberValidNumber false ist, setze output = false.
- Wenn placeNumber < 1, setze output = false.
Tim warnt davor, hier else-Anweisungen zu verwenden, da diese Methode mehrere Prüfungen hat. Wenn eine Prüfung fehlschlägt, sollte die Methode trotzdem die anderen prüfen, um alle Fehler zu sammeln.
Überprüfung des Platznamens
Tim geht zur nächsten Validierung über: Der Platzname darf nicht leer sein.
Er prüft:
if (placeNameValue.Text.Length == 0) { ergebnis = false; }
Tim erklärt, dass in einer echten Anwendung Fehlermeldungen für jede fehlgeschlagene Validierung angezeigt würden. Aber im Moment hält er es einfach und gibt nur true/false zurück.
Validieren des Preisbetrags vs. Preis-Prozentsatz
Tim erklärt, dass das Formular entweder einen Preisbetrag oder einen Preis-Prozentsatz enthalten muss (einer muss größer als null sein). Er weist auf den wichtigen Unterschied hin:
-
Preis-Prozentsatz ist eine ganze Zahl (int)
- Preisbetrag ist ein Dezimalwert (decimal), da Geld Cent enthalten kann.
Er erstellt Variablen:
decimal preisBetrag = 0; int preisProzentsatz = 0;
Dann verwendet er TryParse für beides:
bool preisBetragGültig = decimal.TryParse(prizeAmountValue.Text, out preisBetrag); bool preisProzentsatzGültig = int.TryParse(prizePercentageValue.Text, out preisProzentsatz);
Tim erklärt, dass beide gültige Zahlen sein müssen. Wenn eine ungültig ist, ist das Formular ungültig.
Als Nächstes überprüft er, dass mindestens einer größer als null ist:
if (preisBetrag <= 0 && preisProzentsatz <= 0) { ergebnis = false; }
Tim fügt auch eine Prüfung hinzu, um sicherzustellen, dass der Prozentsatz zwischen 0 und 100 liegt:
if (prizePercentage < 0||preisProzentsatz > 100) { ergebnis = false; }
Er erklärt warum: 150 % würde bedeuten, dass Sie mehr als den Preistopf verschenken, was unmöglich ist.
Verwendung des Validierungsergebnisses
Nachdem alle Überprüfungen abgeschlossen sind, erklärt Tim, wie das Ergebnis verwendet wird:
if (ValidateForm()) { // Modell erstellen und speichern } else { MessageBox.Show("Dieses Formular enthält ungültige Informationen. Bitte überprüfen Sie es und versuchen Sie es erneut."); }
Tim weist darauf hin, dass man bei einem ersten Fehler frühzeitig zurückkehren könnte, aber er entscheidet sich dafür, alle Überprüfungen durchzuführen, damit Benutzer alle Validierungsfehler auf einmal sehen können. Dies reduziert Frustration, da sie alles auf einmal korrigieren können.
Erstellung des PrizeModel
Tim erklärt, dass der nächste Schritt darin besteht, ein PrizeModel zu erstellen, sobald das Formular gültig ist.
Er zeigt, wie ein Modell instanziiert wird:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; // Problem: Dies ist ein String
Tim hebt das Problem hervor: PlaceNumber ist ein int, aber der Formularwert ist ein String. Um dies zu lösen, erklärt er zwei Optionen:
-
Jedes Wert erneut im Formular parsen (repetitiv).
- Fügen Sie einen Konstruktor-Overload in PrizeModel hinzu.
Tim wählt Option 2.
Überladener Konstruktor in PrizeModel
Tim fügt einen überladenen Konstruktor hinzu, der vier Zeichenfolgen akzeptiert:
public PrizeModel(string placeName, string placeNumber, string prizeAmount, string prizePercentage)
{
PlaceName = placeName;
PlaceNumber = int.TryParse(placeNumber, out int placeNumberValue) ? placeNumberValue : 0;
PrizeAmount = decimal.TryParse(prizeAmount, out decimal prizeAmountValue) ? prizeAmountValue : 0;
PrizePercentage = double.TryParse(prizePercentage, out double prizePercentageValue) ? prizePercentageValue : 0;
}
Tim erklärt, dass es ihm egal ist, ob das Parsen fehlschlägt, da es standardmäßig auf Null gesetzt wird, was ohnehin der Standardwert für Zahlen ist.
Dieser Konstruktor ermöglicht es dem Formular, ein PrizeModel direkt mit Zeichenfolgen-Eingaben zu erstellen und das Model übernimmt das Parsen.
Speichern des Modells unter Verwendung von IDataConnection
Nun, da das Model existiert, erklärt Tim, wie es unter Verwendung der globalen Liste von Verbindungen gespeichert wird.
Er verwendet eine foreach-Schleife:
foreach (IDataConnection db in GlobalConfig.Verbindungen) { db.ErstellenPreis(model); }
Tim erklärt, dass diese Schleife CreatePrize() bei jeder Verbindung (SQL und Textdatei) aufruft. Obwohl die Methoden noch nicht implementiert sind, funktioniert das Formular und gibt vor, die Daten zu speichern. Dies beweist, dass das Interface- und globale Konfigurationsmuster funktioniert.
Testen des Formulars
Tim betont die Bedeutung des frühen Testens. Er fügt Breakpoints hinzu und führt die Anwendung aus.
-
Zuerst testet er ein leeres Formular.
-
Er geht durch ValidateForm().
-
Er sieht, dass die Ausgabe falsch ist und die Validierung wie erwartet fehlschlägt.
-
Dann füllt er gültige Daten ein und bestätigt, dass der Konstruktor das Model korrekt ausfüllt.
- Er bestätigt auch, dass die Schleife durch beide Verbindungen läuft.
Tim demonstriert, dass das Formular funktional ist und das Muster validiert ist.
Abschließende Bereinigung: Löschen des Formulars
Tim nimmt ein paar letzte Anpassungen vor:
-
Nach erfolgreicher Erstellung eines Preises werden die Formularfelder gelöscht.
- Setzen Sie Standardwerte von 0 für Preisbetrag und Prozentsatz, damit Benutzer nicht jedes Mal eine Null eingeben müssen.
Er bestätigt dann, dass das Formular nach einer gültigen Übermittlung korrekt gelöscht wird.
Was kommt als Nächstes?
Tim beendet das Video, indem er sagt, dass der nächste Schritt darin besteht, die SQL- und Textverbindungsklassen zu verkabeln, damit sie tatsächlich die Daten speichern.
Er erinnert die Zuschauer, sich für die nächste Lektion bereit zu halten, in der er den SQL-Connector implementieren und tatsächlich mit der Datenbank verbinden wird.
