在多线程环境中克隆对象(例如:列表)以促进不变性是一种理想的做法。但是,如果我们这样做,它可能是API用户的谎言。我所说的是......
请考虑以下代码:
public class Teacher {
public List<Student> Students = new List<Student>();
public Student GetStudent(int index) {
return Students[index].Clone();
}
}
public class Student {
public DateTime LastAttended { get; set; }
}
并且API的用户可以这样做:
var teacher = new Teacher();
var student3 = teacher.GetStudent(3);
student3.LastAttended = DateTime.Now;
如果没有适当的文档,用户就无法知道他所获得的学生对象实际上是一个克隆对象,在这种情况下,对该对象所做的所有更改都不会反映原始对象。
如何改进上述代码,以便用户直观地了解GetStudent仅用于阅读而不是用于修改?有没有办法强制/限制修改从GetStudent方法返回的Student对象?
答案 0 :(得分:4)
您的Student对象根本不是一成不变的。如果您想要不变性,请创建一个不可变对象:
public sealed class Student {
private readonly DateTime _lastAttended;
public DateTime LastAttended { get { return _lastAttended; } }
public Student(DateTime lastAttended)
{
_lastAttended = lastAttended;
}
}
如果您不希望某人设置属性的值,则不要公开setter,只显示getter。
这当然需要围绕此构建应用程序。如果您确实需要更新LastAttended时间,那么您可以这样做,例如通过更新数据库并返回新的Student对象的存储库。此外,许多ORM无法自动处理不可变对象并需要一些转换代码。
请注意,当人们在内存中缓存对象然后传递它们时,例如,您的问题是非常常见的。查看操作它们的模型,在不知不觉中修改缓存中的主对象。这就是为什么经常建议克隆缓存的原因。克隆可以保护您免受代码修改“您的”对象 - 每次有人问,他们都会获得主对象的新实例。克隆不会阻止调用者弄乱他自己的版本。
请注意,如果字段的类型是可变类型,则将字段声明为只读也不会有太大作用 - 我仍然可以执行以下操作: Student.Course.Name = "Test";
即使课程为readonly
- 我无法更改学生对象中的引用,但我可以访问任何属性设置器。
真正的不变性在C#中有点痛苦,因为它有很多打字和很多工厂方法。在某些时候,可能只是留下一个正常的可变获取/设置,并相信呼叫者知道该怎么做,因为他们只能弄乱自己,而不是你。也就是说,任何实际操作数据库中数据的东西都需要进行适当的安全/业务规则检查。