C#介面:理解獎金表單的接線(Tim Corey,第09課)
[[academy-video-youtube({"vid": "5oHfcyrlHeE", "start_time": "0", "title": "C# App Start To Finish Lesson 09 - Prize Form Wire Up", "creator": "Tim Corey", "length": "1h 27m 22s"})]]
在Tim Corey的"C# App Start To Finish"系列中,第 09 課重點是連接獎項表單。 表面上,這個表單看起來很簡單—只需收集使用者輸入、驗證、建立模型並保存。 但Tim解釋,真正的複雜性在於決定將資料儲存在哪裡:資料庫、文字檔案,或者兩者。 Tim的影片透過介紹C#編程中的一個核心概念:介面,帶我們了解解決方案。
在本文中,我們將透過Tim的解釋深入了解介面,以便您能夠更好地瞭解它們如何幫助建立可擴展且易於維護的應用程式。
問題:我們應將資料儲存在哪裡?
Tim首先說明獎項表單的目的:接收輸入、驗證並儲存到儲存空間。 但他告誡說,困難之處在於決定將資料儲存在哪裡。 他強調教程常常跳過這一點,因為這不容易,但他希望學習者能直接面對挑戰。
他解釋,起初您可能會嘗試一個簡單的解決方案:檢查是否使用SQL或文字檔案,然後執行正確的保存過程。 但Tim迅速展示這樣做會變得多麼笨拙和難以維護。 如果每個表單都必須檢查使用哪種型別的儲存空間,程式碼將變得重複、混亂且難以更改。
笨拙的方法:硬編碼條件
Tim畫出一個偽程式碼範例。 他解釋,您可能會從檢查布林值開始,比如usingSQL == true,然後打開資料庫連線,保存模型並返回ID。 接著您可能會對文字檔案做同樣的操作,因為文字檔案不會自動生成ID。
他指出,這很快就變得重複了。多個表單需要這種邏輯,每次新增資料來源(如MySQL)時您都必須更新每個表單。 Tim稱這樣做為"不可擴展",並強調這違背"DRY"原則(Don't Repeat Yourself)。 他明確指出:"一定有更好的方法。"
拆線:更好的方法
Tim介紹了他的策略:拆線。 他首先問程式碼需要什麼資訊以及它從哪裡來。 他識別出兩個關鍵問題:
我們怎麼知道要使用哪個資料來源?
我們如何連接到兩個不同的資料來源以執行相同任務?
Tim解釋,實際保存行為是唯一不同的。 從表單的角度,只需要說:"這是模型。 儲存它。"表單不應該關心是否要保存到SQL還是文字檔案。
解決方案:全域配置+介面
Tim建議使用一個全域配置系統。 他說,要知道使用哪個資料來源,應用需要全域可存取的資料,他建議使用靜態類來儲存這些訊息。 他承認通常會避免使用全域變數,但在此情況下,全域資料正是所需的。
接下來,Tim解釋了一個關鍵概念:介面。 他將介面定義為一種合約—任何實現它的類別都會包含某些方法或屬性。 Tim強調,這使得應用可以無論資料來源如何都能調用相同的方法。 表單不關心是SQL還是文字檔案;它只關心調用方法。
Tim說:"如果您需要執行相同的任務但在幕後以兩種不同方式完成,您需要一個介面。"
建立介面
Tim將注意力轉向實際實現,通過在Tracker Library中建立一個介面。 他命名為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樣板及其存在的原因—這允許程式碼編譯而不假裝方法已實現。
建立SQL和Text連接器
Tim新增了SQLConnector類和TextConnector類,兩者都實現IDataConnection。 他解釋,儘管保存到SQL資料庫和保存到文字檔案是非常不同的過程,但它們都滿足相同的介面合約。
他暫時新增了一個簡單的範例返回值,並放置TODO評論以提醒自己稍後實現實際的保存邏輯。 這樣在仍然進行課程的同時,應用仍然可操作。
最終設置:連接全域配置
Tim回到GlobalConfig類並連接實際的連接。 他演示如何初始化連接列表並向其中新增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解釋了第一個驗證:主持號碼必須為大於0的整數。
他使用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("此表單包含無效訊息。 請檢查並重試。"); }Tim指出,在首次失敗時您可能會返回,但他選擇運行所有檢查以便使用者可以一次看到所有驗證錯誤。 這減少了沮喪, 因為他們能一次解決所有問題。
建立PrizeModel
Tim解釋,一旦表單有效,下一步就是建立PrizeModel。
他演示如何實例化模型:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; //問題:這是一個字符串Tim強調問題:PlaceNumber是一個整數,但表單的值是一個字串。 為了解決這個問題,他解釋了兩個選項:
*在表單中再次解析每個值(重複)。
*在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解釋說,這個迴圈呼叫在每個連接上調用CreatePrize()(SQL和文字檔案)。 儘管這些方法尚未實現,但表單功能正常並假裝將資料保存。 這證明了介面和全域配置模式在運作。
測試表單
Tim強調早期測試的重要性。 他加入了斷點並運行應用。
*他首先測試了一個空的表單。
*他單步檢查了ValidateForm()。
*他看到輸出為false,驗證失敗。
*然後他填寫有效資料,並確認構造器正確填充模型。
*他還確認迴圈通過了兩個連接。
Tim展示了表單功能正常且模式被驗證。
最終清理:清除表單
Tim做了最後的調整:
*成功建立獎項後,清除表單欄位。
*為獎金金額和百分比設置預設值為0,這樣使用者不必每次輸入零。
然後他確認表單在有效提交後正確清除。
接下來是什麼?
Tim通過說明下一步是連接SQL和文字連接類以實際保存資料來結束影片。
他提醒觀眾關注下一課程,他將實現SQL連接器並實際連接到資料庫。

