33、编程知识与实践综合指南

编程知识与实践综合指南

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 类来替代普通的字符串拼接,减少内存分配和垃圾回收的开销。
  • 使用设计模式 :合理运用设计模式可以提高代码的可维护性、可扩展性和灵活性。策略设计模式是一种常用的设计模式,适用于多种场景,可以根据实际情况进行应用。
  • 使用相对路径 :在处理文件路径时,尽量使用相对路径,避免硬编码,提高代码的可移植性。

希望这些总结和建议能够帮助你在编程实践中取得更好的效果。不断学习和实践,才能不断提升自己的编程能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值