Menu
CoddyTech

不可变性模式

本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 34 课,共 70 课。

不可变性将封装推向了合乎逻辑的极致:对象一旦创建,就永远不会改变。这样可以消除许多类型的错误,因为你再也不用担心数据被意外修改。

创建不可变类最简单的方法是使用 readonly 字段,并通过构造函数设置所有值,同时不提供 setter:

public class Point
{
    public readonly int X;
    public readonly int Y;
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

创建 Point 后,它的坐标就不能更改了。但如果你需要一个修改后的版本,该怎么办?你可以返回一个新对象,而不是更改现有对象:

public Point Move(int deltaX, int deltaY)
{
    return new Point(X + deltaX, Y + deltaY);
}

原始点保持不变,同时你会得到一个值已更新的新点。这种模式在 .NET 中随处可见,例如,字符串是不可变的。调用 ToUpper() 时,你会得到一个新的字符串,而不是修改原来的字符串。

不可变对象本身就是线程安全的,因为创建后没有代码能修改它们。它们也更容易推理,因为你知道它们的状态永远不会意外改变。

challenge icon

挑战

简单

让我们构建一个不可变的货币系统,展示如何设计对象,使其创建后永远不会改变。你将创建一个 Money 类,每种操作都会返回一个新对象,而不是修改原对象。这种模式能让代码更安全,也更容易理解。

你将在两个文件中组织代码:

  • Money.cs:在 Finance 命名空间中创建一个不可变的 Money 类。你的货币对象应包含:
    • 两个 readonly 字段:Amount(decimal)和 Currency(string)
    • 一个用于设置这两个值的构造函数。对象创建后,Money 对象永远不会改变
    • 一个 Add(decimal value) 方法,返回一个金额增加后的新 Money 对象(货币相同)
    • 一个 Subtract(decimal value) 方法,返回一个金额减少后的新 Money 对象(货币相同)
    • 一个 Convert(string newCurrency, decimal rate) 方法,返回一个新 Money 对象,其金额乘以汇率,并使用新的货币
    • 一个 GetDisplay() 方法,以 "{Amount} {Currency}" 格式返回字符串
    记住:这些方法都不应修改原对象,而是返回全新的 Money 实例!
  • Program.cs:在主文件中演示不可变性的实际效果。创建一个初始 Money 对象,然后对它执行操作,同时保留对原对象和操作结果的引用。这样就能证明,即使执行了多次操作,原对象也不会改变。

你将收到四个输入:

  • 初始金额(decimal)
  • 初始货币(string)
  • 要添加的金额(decimal)
  • 换算为 EUR 的汇率(decimal)

按以下格式输出:

Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}

例如,如果输入是 100.00、USD、50.00 和 0.85,输出应为:

Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EUR

注意,即使调用了 Add(),原来的 Money 对象仍显示 100.00 USD。这就是不可变性!Add() 方法创建了一个完全新的对象,金额为 150.00 USD,而原对象保持不变。货币换算是在添加操作的结果上进行的。

亲手试试

using System;
using Finance;

class Program
{
    public static void Main(string[] args)
    {
        // 读取输入
        decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
        string initialCurrency = Console.ReadLine();
        decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
        decimal conversionRate = Convert.ToDecimal(Console.ReadLine());

        // TODO: 用初始金额和货币创建一个 Money 对象

        // TODO: 使用 GetDisplay() 输出原始金额

        // TODO: 对原始对象调用 Add(),并将结果存入新变量

        // TODO: 使用 GetDisplay() 输出相加后的结果

        // TODO: 再次输出原始金额,证明它没有改变

        // TODO: 使用汇率将相加后的结果转换为 EUR

        // TODO: 使用 GetDisplay() 输出转换后的金额
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C# 在线编译器