C# 介面:理解獎品表單的連接(Tim Corey, 第 09 課)
在Tim Corey的"C# App Start To Finish"系列中,第09課專注於連接獎品表單。 表面上看,這個表單似乎很簡單——只是收集使用者輸入、驗證它、創建模型並保存。 但Tim解釋說,真正的複雜性在於決定將數據存儲在哪裡:資料庫、文本文件或兩者。 Tim的視頻通過介紹C#編程中的一個核心概念:介面,帶我們了解了解決方案。
在本文中,我們將通過Tim的解釋更深入地瞭解介面,以便您能更好地了解它們如何幫助製作可擴展、可維護的應用程式。
問題:我們將數據存儲在哪裡?
Tim首先陳述了獎品表單的目的:它接受輸入,驗證並保存到存儲中。 但他警告說,棘手部分是決定將數據存儲在哪裡。 他強調教程通常會跳過這一部分,因為它不容易,但他希望學習者能正面迎接這一挑戰。
他解釋說,起初,您可能會嘗試一個簡單的解決方案:檢查您是使用SQL還是文本文件,然後執行正確的保存過程。 但Tim迅速展示了這種做法有多麼笨拙和難以維護。 如果每個表單都必須檢查使用哪種存儲類型,代碼就會變得重複、混亂且難以更改。
笨拙的方法:硬編碼條件
Tim擬出了一個偽代碼示例。 他解釋說,您可能會從檢查一個布爾值開始,比如usingSQL == true,然後打開資料庫連接,保存模型並返回ID。 然後您可能會對文本文件做同樣的事,因為文本文件不會自動生成ID,您需要手動生成。
他指出,這很快就會變得重複。多個表單需要這個邏輯,每次添加新的數據源如MySQL時,您必須更新每個表單。 Tim稱其為"不可擴展",並強調其違反了"DRY" 原則(不要重複自己)。 他明確指出:"必須有更好的方法"。
抓住線索:更好的方法
Tim引入了他的策略:抓住線索。 他開始詢問代碼需要什麼信息以及它來自何處。 他識別了兩個關鍵問題:
我們如何知道使用哪個數據源?
我們如何連接到兩個不同的數據源來完成同樣的任務?
Tim解釋說,實際的保存行為是唯一的不同。 從表單的角度來看,它只需說:"這是模型。 保存它。"表單不應該在意它是保存到SQL還是文本文件。
解決方案:全局配置+介面
Tim建議使用全局配置系統。 他說,要知道使用哪個數據源,應用程式需要全局可訪問的數據,他建議使用靜態類來存儲這些信息。 他承認通常會避免使用全局變量,但在這種情況下,正是需要全局數據。
接著,Tim解釋了關鍵概念:接口。 他將介面定義為一種約定——即任何實現它的類都將包含某些方法或屬性。 Tim強調,這讓應用程式無論數據來源如何都可以調用同一個方法。 表單不關心它是SQL還是文本文件;它只關心調用該方法即可。
Tim說:"如果您需要執行相同的任務,但在幕後將以兩種不同的方式完成,您需要一個介面。"
創建介面
Tim通過在Tracker程式庫中創建一個介面進行實際實現。 他將其命名為IDataConnection,並解釋在介面名稱前加上"I"的習慣。 他強調這一點很重要,以便清楚地識別它是個介面。
Tim在介面中添加了一個方法:
PrizeModel CreatePrize(PrizeModel model);
他解釋說,這個方法是一個約定:它必須在任何實現IDataConnection的類中存在。 表單會調用這個方法,並期望返回一個帶ID的PrizeModel。 Tim解釋說,這就是表單能夠對存儲類型保持中立的方式。
創建全局配置靜態類
接下來,Tim創建了一個名為GlobalConfig的靜態類。他解釋說,靜態類無法實例化,且可以全局訪問。 這是應用程式將儲存可用數據連接列表的地方。
他定義了一個屬性:
public static List<IDataConnection> Connections { get; private set; }
Tim解釋了private set的使用,以便只有類本身才能修改該列表,而應用程式的其他部分只能讀取它。
然後他創建方法:
public static void InitializeConnections(bool database, bool textFiles)
這個方法設置了可用的數據連接。 Tim強調,列表允許多個連接,這意味著應用程式可以保存到SQL、文本文件或兩者皆有。
理解接口:實際範例
Tim暫停以向學習者保證,這是复杂的材料,但能够达到的。 他建議先觀看一次視頻,然後在編碼時重看。
他解釋說,接口是一份合約,任何實現它的類都必須遵循這份合約。 他通過創建一個實現IDataConnection的SQLConnector類來演示這一點。
創建類時,Visual Studio警告合約未被履行。 Tim展示如何使用"實現接口"自動生成CreatePrize方法。他還解釋NotImplementedException scaffold為什麼存在——它允許代碼在不假裝方法工作時編譯。
創建SQL和文本連接器
Tim添加了SQLConnector類和TextConnector類,這兩者都實現了IDataConnection。 他解釋說,儘管保存到SQL資料庫和文本文件是非常不同的過程,但它們都滿足相同的接口約定。
他現在添加了一個簡單的樣本返回值,並放置TODO註釋,以提醒自己稍後實現真正的保存邏輯。 這讓應用程式保持功能的同時能夠繼續完成課程。
最後設置:連接全局配置
Tim回到GlobalConfig類,並連接實際的連接。 他展示如何初始化Connections列表並添加SQLConnector和TextConnector的實例。
他解釋為什麼需要兩個單獨的if語句而不是if-else——因為用戶可能希望同時保存到兩個數據源。
在何處調用InitializeConnections?
Tim解釋說,InitializeConnections必須在應用程式啟動時調用。 他修改了Program.cs並調用:
GlobalConfig.InitializeConnections(true, true);
在加載表單之前。 這確保了連接列表已準備好且在整個應用程式中可訪問。
然後他將啟動表單更改為CreatePrizeForm,以便能立即測試功能。
驗證獎品表單
Tim打開表單並解釋第一項任務:驗證四個字段。 他喜歡保持事件處理器簡潔,因此他創建了一個名為ValidateForm()的私有方法。
Tim解釋說,這個方法可以從任何地方調用,而不僅僅是按鈕點擊。 它返回一個布爾值以指示表單是否有效。 他展示了他的使用輸出變量的模式:
bool output = true; return output;
他說他喜歡從true開始,因為當出了問題時更改為false比在每次檢查後設置為true更容易。
檢查名次編號
Tim解釋了第一個驗證:名次編號必須是大於零的整數。
他使用int.TryParse將PlaceNumberValue.Text(字符串)轉換為整數。 Tim分解了TryParse的工作原理:
-
它接受一個字符串並嘗試將其轉換為數字。
-
它返回一個布爾值以指示成功或失敗。
- 它使用輸出參數來輸出轉換後的值。
Tim強調,TryParse比Parse更安全,因為它不會因不好的輸入而崩潰——它返回false並將輸出設為零。
然後他解釋了邏輯:
-
如果placeNumberValidNumber為false,則設置output = false。
- 如果placeNumber < 1,則設置output = false。
Tim警告在這裡避免使用else語句,因為這個方法有多個檢查。 如果一個檢查失敗,該方法仍然應該評估其他檢查以收集所有錯誤。
驗證名次名稱
Tim進行下一個驗證:名次名稱不得為空。
他檢查:
if (placeNameValue.Text.Length == 0) { output = false; }
Tim解釋說在一個真正的應用程式中,您會顯示每個失敗驗證的錯誤消息。 但目前,他保持簡單,只返回true/false。
驗證獎金金額與獎金比例
Tim解釋說,表單中必須包含獎金金額或獎金比例(其中一項必須大於零)。 他注意到一個重要的區別:
-
獎金比例是一個整數(int)
- 獎金金額是一個小數(decimal),因為金錢可以包含分。
他創建了變量:
decimal prizeAmount = 0; int prizePercentage = 0;
然後他對兩者都使用TryParse:
bool prizeAmountValid = decimal.TryParse(prizeAmountValue.Text, out prizeAmount); bool prizePercentageValid = int.TryParse(prizePercentageValue.Text, out prizePercentage);
Tim解釋說,兩者都必須是有效的數字。 如果其中之一無效,則表單無效。
接下來,他檢查至少有一項大於零:
if (prizeAmount <= 0 && prizePercentage <= 0) { output = false; }
Tim還添加了一個檢查以確保百分比在0到100之間:
if (prizePercentage < 0 || prizePercentage > 100) { output = false; }
他解釋為什麼:150%將意味著您將比獎品池贈送得更多,這是不可能的。
使用驗證結果
完成所有檢查後,Tim解釋了如何使用結果:
if (ValidateForm()) { // create model and save } else { MessageBox.Show("This form has invalid information. Please check it and try again."); }
Tim指出您可以在第一次失敗時提前返回,但他選擇運行所有檢查,以便用戶可以一次看到所有驗證錯誤。 這減少了挫敗感,因為他們可以一蹴而就地修復所有問題。
創建PrizeModel
Tim解釋說,一旦表單有效,下一步就是創建PrizeModel。
他演示如何實例化一個模型:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; // 問題:這是一個字符串
Tim強調了問題所在:PlaceNumber是int,但表單值是一個字符串。 為了解決這個問題,他解釋了兩個選擇:
-
在表單中再次解析每個值(重複)。
- 在PrizeModel中添加構造函數重載。
Tim選擇了選擇2。
PrizeModel中的重載構造函數
Tim添加了一個接受四個字符串的重載構造函數:
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解釋說,他不在乎解析失敗與否,因為它將默認為零,這本就是數字的默認值。
這個構造函數允許表單直接使用字串輸入創建PrizeModel,並讓模型處理解析。
使用IDataConnection保存模型
模型已存在,Tim現在解釋如何使用全局連接列表來保存它。
他使用了一個foreach循環:
foreach (IDataConnection db in GlobalConfig.Connections) { db.CreatePrize(model); }
Tim解釋說,這個循環會在每個連接(SQL和文本文件)上調用CreatePrize()。 儘管方法尚未實現,但表單運行並假裝保存了數據。 這證明了接口和全局配置模式是可行的。
測試表單
Tim強調及早測試的重要性。 他添加斷點並運行應用程式。
-
他首先測試一個空白表單。
-
他步驟走過ValidateForm()。
-
他看到輸出為false,驗證按預期失敗。
-
然後他填寫有效數據並確認構造函數正確填充模型。
- 他還確認循環遍歷了兩個連接。
Tim證明了表單是功能性的和模式是經過驗證的。
最後清理:清除表單
Tim做了一些最終調整:
-
成功創建獎品後,清除表單字段。
- 對獎金金額和比例設置默認值為0,以便用戶每次不必輸入零。
然後他確認在有效提交後表單正確清除。
接下來會發生什麼?
Tim結束了視頻,表示下一步是連接SQL和文本連接器類,以便它們真正保存數據。
他提醒觀眾關注下一課,屆時他將實現SQL連接器並實際連接到資料庫。
