不可变性模式
本课属于 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() 时,你会得到一个新的字符串,而不是修改原来的字符串。
不可变对象本身就是线程安全的,因为创建后没有代码能修改它们。它们也更容易推理,因为你知道它们的状态永远不会意外改变。
挑战
简单让我们构建一个不可变的货币系统,展示如何设计对象,使其创建后永远不会改变。你将创建一个 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() 输出转换后的金额
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器