跳過到頁腳內容
Iron Academy Logo
C# 應用程式
C# 應用程式

其他分類

C# 介面:理解獎品表單的連接(Tim Corey, 第 09 課)

Tim Corey
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" 原則(不要重複自己)。 他明確指出:"必須有更好的方法"。

抓住線索:更好的方法

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連接器並實際連接到資料庫。

Hero Worlddot related to C# 介面:理解獎品表單的連接(Tim Corey, 第 09 課)
Hero Affiliate related to C# 介面:理解獎品表單的連接(Tim Corey, 第 09 課)

通過分享您所愛的東西賺得更多

您是否在為使用.NET、C#、Java、Python或Node.js的開發者創建內容?將您的專業知識轉化為額外收入!

鋼鐵支援團隊

我們每週 5 天,每天 24 小時在線上。
聊天
電子郵件
打電話給我