编程知识与实践综合指南
1. 状态码处理与开发建议
在开发过程中,对于返回状态码的处理需要谨慎比对规范。例如在某个开发场景中,若未对比两个规范,可能会遗漏
404 Not Found
返回的需求,从而向客户交付错误代码。在当前情况里,虽看到
201
和
500
状态码映射正确,但实现了
404
返回状态,而根据相关规范此返回并非必要。不过,考虑到开发者可能有保留该返回状态的需求,但通常最好严格遵循客户要求。所以,需要将
BookingController
修改为不返回
404
状态码(若遇到困难可查看源代码)。另外,Swagger 具备为返回码指定描述的功能,可进行研究并实现。
2. 开发总结
以下是一些开发中的关键要点总结:
| 要点 | 描述 |
| — | — |
| JSON 数据处理 | 从 HTTP 请求传入的 JSON 数据是经过序列化的,不能直接使用,需先反序列化才能操作。可使用
[FromBody]
参数属性或实现自定义模型绑定器来反序列化 JSON 数据,将传入的 JSON 或 XML 数据转换为可用的数据结构。 |
| 自定义模型绑定器 | 可使用
IModelBinder
接口实现自定义模型绑定器,便于对数据序列化到模型的过程进行更精细的控制。 |
| 模型绑定验证 | 通过
ModelState.IsValid
检查,可验证模型绑定过程中是否存在错误,结合自定义模型绑定器使用时尤为有用,能精确定义模型无效的情况。 |
| OpenAPI 规范生成 | 在配置中添加 Swagger 中间件,可在服务启动时生成 OpenAPI 规范,有助于验收测试并确保实现正确的端点。 |
3. 编码实战:“Welcome Aboard-a-Tron 3000”
3.1 问题背景
设定在 2173 年,Flying Dutchman 航空公司凭借低价专利占据航空业主导地位。公司购买了新的乘客问候系统,但未配备相应软件。需要编写一个简单程序,读取存储在设备上包含所有乘客姓名的 CSV 文件,并将姓名显示在屏幕上。由于设备是低功耗嵌入式设备,需注意内存分配。
3.2 任务要求
需要完成以下任务:
- 从设备自身存储的 CSV 文件中读取姓名。
- 将所有姓名写入显示屏。
- 注意内存使用。
3.3 涉及知识
此编程实战涵盖以下知识:
- 使用接口与策略设计模式。
- 使用
StringBuilder
类。
- 忽略字符串中的转义序列。
- 暂停线程。
4. 策略设计模式与组合
4.1 继承的问题
在面向对象设计中,过度使用继承链会导致维护困难。例如,有一个抽象基类
Instrument
包含抽象方法
PlayNote
,
DrumKit
和
Guitar
类继承自
Instrument
并实现
PlayNote
方法。当需要添加
Banjo
类时,其
PlayNote
方法实现与
Guitar
类相似,若让
Banjo
类继承自
Guitar
类会使结构混乱。
public abstract class Instrument
{
protected abstract void PlayNote();
}
public class DrumKit : Instrument
{
protected override void PlayNote()
{
…
}
}
public class Guitar : Instrument
{
protected override void PlayNote()
{
…
}
}
public class Banjo : Instrument
{
protected override void PlayNote()
{
…
}
}
4.2 策略设计模式解决方案
可使用
INotePlayer
接口来暴露播放音符的行为,创建
StringNotePlayer
和
PercussionNotePlayer
等具体类实现该接口。然后在
Instrument
类中添加
INotePlayer
字段,并在派生类中实例化正确的具体类。
public interface INotePlayer
{
void PlayNote();
}
public class StringNotePlayer : INotePlayer
{
public void PlayNote()
{
…
}
}
public class PercussionNotePlayer : INotePlayer
{
public void PlayNote()
{
…
}
}
public abstract class Instrument
{
protected INotePlayer notePlayer;
protected abstract void PlayNote();
}
public class DrumKit : Instrument
{
public DrumKit()
{
notePlayer = new PercussionNotePlayer();
}
protected override void PlayNote() => notePlayer.PlayNote();
}
public class Guitar : Instrument
{
public Guitar()
{
notePlayer = new StringNotePlayer();
}
protected override void PlayNote() => notePlayer.PlayNote();
}
public class Banjo : Instrument
{
public Banjo()
{
notePlayer = new StringNotePlayer();
}
protected override void PlayNote() => notePlayer.PlayNote();
}
通过这种方式,使用组合而非继承实现多态行为,还能在运行时更改“策略”。例如,若要将班卓琴像鼓组一样演奏,可在运行时将其
notePlayer
字段赋值为
PercussionNotePlayer
实例。
5. 创建接口和具体类
为了使代码具有更好的可复用性和适应性,我们需要创建接口和具体类来抽象设备的显示和文件系统操作。以下是具体步骤和代码示例:
5.1 定义接口
首先,我们定义两个接口:
IDataStorage
用于读取数据,
IDisplay
用于显示数据。
public interface IDataStorage
{
string Read();
}
public interface IDisplay
{
void Print(string text);
}
5.2 创建具体类
接着,创建实现这些接口的具体类。
InternalFileSystem
类实现
IDataStorage
接口,用于从设备内部文件系统读取数据;
WelcomeAboardATron3000
类实现
IDisplay
接口,用于在特定设备上显示数据。
public sealed class InternalFileSystem : IDataStorage
{
public string Read()
{
…
}
}
public sealed class WelcomeAboardATron3000 : IDisplay
{
public void Print(string text)
{
…
}
}
5.3 实例化类
最后,在
Main
方法中实例化这些类,并调用相应的方法来完成读取和显示操作。
public static void Main()
{
IDataStorage storage = new InternalFileSystem();
IDisplay display = new WelcomeAboardATron3000();
string text = storage.Read();
display.Print(text);
}
通过以上步骤,我们完成了接口和具体类的创建,并在
Main
方法中进行了实例化和调用。这样的设计使得代码更加模块化和可扩展,方便在不同设备上进行复用。
6. 读取文本文件并使用 StringBuilder
在处理存储乘客姓名的 CSV 文件时,我们需要考虑内存使用效率。以下是详细的操作步骤和代码示例:
6.1 确定文件路径
首先,我们需要确定 CSV 文件的存储位置。根据任务要求,文件会自动加载到设备的主存储中,我们假设文件路径为
C:\names.csv
(对于 macOS 或 Linux 用户,需使用适合自己操作系统的路径)。为了避免路径中的转义字符问题,我们使用
@
符号来创建逐字字符串。
public class InternalFileSystem : IDataStorage
{
private const string FILE_PATH = @"C:\names.csv";
public string Read() { … }
}
6.2 读取文件
接下来,我们使用
TextFieldParser
来读取 CSV 文件。以下是初始的读取代码:
public string Read()
{
using (TextFieldParser parser = new TextFieldParser(FILE_PATH))
{
parser.SetDelimiters(",");
List<string[]> parsedFields = new List<string[]>();
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
parsedFields.Add(fields);
}
}
}
然而,上述代码将读取的内容存储在
string[]
数组中,而我们需要返回一个包含所有乘客姓名的字符串。因此,我们可以使用简单的字符串拼接方法:
public string Read()
{
string passengerNames = string.Empty;
using (TextFieldParser parser = new TextFieldParser(FILE_PATH))
{
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
passengerNames += parser.ReadLine();
passengerNames += " | ";
}
}
return passengerNames;
}
6.3 内存优化:使用 StringBuilder
但是,由于字符串是不可变的,每次拼接都会在内存中分配新的字符串,这会导致内存使用效率低下。为了优化内存使用,我们可以使用
StringBuilder
类。
public string Read()
{
StringBuilder stringBuilder = new StringBuilder();
using (TextFieldParser parser = new TextFieldParser(FILE_PATH))
{
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
stringBuilder.Append(parser.ReadLine());
stringBuilder.Append(" | ");
}
}
return stringBuilder.ToString();
}
StringBuilder
类通过使用指针来更新字符串引用,避免了频繁的内存分配,从而提高了内存使用效率。特别是在需要进行多次字符串拼接的情况下,使用
StringBuilder
类可以显著减少内存开销。
综上所述,通过确定文件路径、读取文件并使用
StringBuilder
类进行字符串拼接,我们可以高效地处理存储乘客姓名的 CSV 文件,同时满足低内存使用的要求。
编程知识与实践综合指南
7. 相对路径的使用
在实际开发中,硬编码文件路径通常不是一个好的做法,因为它会将用户限制在特定的文件路径上,并且不同操作系统的路径格式也有所不同。为了解决这个问题,我们可以使用相对路径,即根据应用程序的执行位置动态生成路径。以下是使用相对路径的具体步骤:
-
获取执行程序集的位置
:使用
Assembly.GetExecutingAssembly().Location属性获取当前执行程序集的位置,然后使用Path.GetDirectoryName方法提取该位置的目录路径。
string basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
-
生成相对路径
:假设我们的资源文件(如
potato.png)总是存储在相对于执行程序集的./Resources目录下,我们可以使用字符串插值来生成相对路径。
string relativePath = $"{basePath}/Resources/potato.png";
通过使用相对路径,我们可以避免因目录结构变化而导致的问题,提高代码的可移植性和灵活性。
8. 编码实战流程总结
为了更清晰地展示“Welcome Aboard-a-Tron 3000” 编程实战的整体流程,我们可以使用 mermaid 格式的流程图来进行总结:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(创建接口和具体类):::process
B --> C(确定文件路径):::process
C --> D(读取 CSV 文件):::process
D --> E{是否使用 StringBuilder?}:::decision
E -- 是 --> F(使用 StringBuilder 拼接字符串):::process
E -- 否 --> G(使用普通字符串拼接):::process
F --> H(显示乘客姓名):::process
G --> H
H --> I([结束]):::startend
这个流程图展示了从创建接口和具体类开始,到最终显示乘客姓名的整个过程。其中,是否使用
StringBuilder
是一个决策点,根据实际情况进行选择。
9. 策略设计模式的优势与应用场景
策略设计模式在编程中具有许多优势,并且适用于多种应用场景。以下是对其优势和应用场景的详细分析:
| 优势 | 描述 |
|---|---|
| 可维护性 | 通过将不同的算法封装在具体的策略类中,使得代码的维护更加容易。当需要修改或添加新的算法时,只需要修改或添加相应的策略类,而不会影响到其他部分的代码。 |
| 可扩展性 |
可以方便地添加新的策略类,以支持不同的行为。例如,在
Instrument
示例中,我们可以轻松地添加新的乐器类,并为其指定合适的
INotePlayer
策略。
|
| 灵活性 |
可以在运行时动态地切换策略。例如,在
Banjo
类中,我们可以根据需要将其
notePlayer
字段从
StringNotePlayer
切换为
PercussionNotePlayer
,实现不同的演奏方式。
|
策略设计模式适用于以下场景:
-
算法族的替换
:当一个系统需要在多种算法中进行选择时,可以使用策略设计模式。例如,在图形绘制系统中,可以使用不同的算法来实现不同的图形渲染效果。
-
避免使用多重条件判断
:如果一个类中包含大量的条件判断语句,用于选择不同的行为,那么可以使用策略设计模式来替代这些条件判断,使代码更加清晰和易于维护。
10. 总结与建议
通过以上的学习和实践,我们掌握了许多重要的编程知识和技能,包括状态码处理、JSON 数据反序列化、策略设计模式、接口和具体类的创建、文件读取以及字符串拼接等。为了更好地应用这些知识,以下是一些总结和建议:
- 遵循规范 :在开发过程中,要严格遵循相关的规范和要求,如状态码的处理要与接口规范保持一致,避免因疏忽而导致的错误。
-
优化内存使用
:在处理大量数据时,要注意内存的使用效率。例如,使用
StringBuilder类来替代普通的字符串拼接,减少内存分配和垃圾回收的开销。 - 使用设计模式 :合理运用设计模式可以提高代码的可维护性、可扩展性和灵活性。策略设计模式是一种常用的设计模式,适用于多种场景,可以根据实际情况进行应用。
- 使用相对路径 :在处理文件路径时,尽量使用相对路径,避免硬编码,提高代码的可移植性。
希望这些总结和建议能够帮助你在编程实践中取得更好的效果。不断学习和实践,才能不断提升自己的编程能力。
超级会员免费看

被折叠的 条评论
为什么被折叠?



