跳至页脚内容
Iron Academy Logo
C# 应用程序
C# 应用程序

其他类别

C#接口:理解奖品表单接线(Tim Corey,第09课)

Tim Corey
1h 27m 22s

在 Tim Corey 的《C# 应用程序从头到尾》系列中,第 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解释使用私有设置,以便只有类本身可以修改列表,而应用程序的其他部分只能读取。

然后他创建了方法:

public static void InitializeConnections(bool database, bool textFiles)

此方法设置可用的数据连接。 Tim强调列表允许多个连接,这意味着应用程序可以保存到SQL、文本文件或两者。

理解接口:一个现实世界的例子

Tim停下来安抚学习者,这是复杂的材料,但可以实现。 他建议先看一次视频,然后边看边编码。

他解释说,接口是一种合同,任何实现它的类都必须遵循合同。 他通过创建一个实现IDataConnection的SQLConnector类来演示这一点。

创建类时,Visual Studio会警告合同未履行。 Tim演示如何使用"Implement Interface"自动生成CreatePrize方法。他还解释了NotImplementedException框架及其存在的原因——它允许代码编译,而不假装方法有效。

创建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("此表单包含无效信息。 请检查并重试。"); }

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 小时在线。
聊天
电子邮件
打电话给我