问题:如果假设mutator调用来自一个不可变的对象,那么如何处理这个新对象?
问题:这在哪里停止?不必在某个地方至少有一个可变对象至少可以保存最顶层的实例吗?
答案 0 :(得分:1)
这是函数式编程的思想。一切都是不可变的,不允许任何函数调用产生副作用。像您的示例一样,突变复杂对象的唯一方法是重新创建父对象。
现在的问题是如何更改程序状态。因此,我们首先考虑堆栈。它包含所有局部变量的值以及被调用函数的所有参数的值。我们可以通过调用新函数来创建新值。我们可以通过从函数返回来丢弃值。因此,我们可以通过调用函数来改变程序状态。但是,并非总是可以从函数中返回以丢弃其局部变量,因为我们可能只希望丢弃某些局部变量,而需要保留其他局部变量的值以进行进一步的操作。在这种情况下,我们根本无法返回,但是我们需要调用另一个函数并将仅一些局部变量传递给它。现在,为了防止堆栈溢出,功能语言具有称为尾调用优化的功能,该功能可以从调用堆栈中删除不必要的条目。如果关联函数唯一要做的就是返回自身调用的函数的值,则不需要调用堆栈。在这种情况下,没有必要保留调用堆栈条目。通过删除不必要的调用堆栈条目,将丢弃原本未使用的局部变量的值。您可能需要阅读here。另外,tail recursion与此相关。
同样,这是像Haskell这样的纯函数式编程语言的思想。一切都是不可变的,这真是太好了,但是这些语言只有它们自己的问题,并且有自己的处理方式。例如,Monad(以及更高种类的类型)在这些语言中可用,但是在命令式/面向对象的编程语言中很少见。
我喜欢在程序存储器的叶子处具有不变的值。但是,构成这些不可变值的代码实际上构成了应用程序逻辑,但确实包含可变状态。对我来说,这结合了两个世界的优势。但是,这似乎是一个优先事项。
答案 1 :(得分:0)
使用您现有的结构,这将非常困难,这可能是您应该从本练习中学到的东西。
我将从对象中删除对象之间的所有关系,并使用Map
和Set
来实现这些关系。
这样的事情将是一个很好的起点。
// Make sure all objects can be uniquely identified.
interface Id {
public Long getId();
}
class HasId implements Id {
private final Long id;
// Normal constructor.
public HasId(Long id) {
this.id = id;
}
// Copy constructor.
public HasId(HasId copyFrom) {
this(copyFrom.id);
}
@Override
public Long getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HasId hasId = (HasId) o;
return Objects.equals(id, hasId.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
class Semester extends HasId {
public Semester(Long id) {
super(id);
}
public Semester(Semester copyFrom) {
super(copyFrom);
// TODO: Copy all the other fields of Semester to mine.
}
// Do NOT hold a list of Lectures for this semester.
}
class Lecture extends HasId {
// ...
// Do NOT hold a list of Students for this lecture.
}
class Student extends HasId {
// ...
}
// Core structures.
Map<Id, List<Lecture>> semesters = new HashMap<>();
Map<Id, List<Student>> lectures = new HashMap<>();
Set<Id> students = new HashSet<>();
// Utility structures that need to be maintained.
Map<Id, Lecture> studentsInLecture = new HashMap<>();
Map<Id, Semester> lecturesInSemester = new HashMap<>();
通过这种方式,您可以隔离对象并使它们保持不变,但是如果您确实需要更改任何学生的详细信息,则可以克隆原始学生并窃取其身份。
这显然还不是一个完整的解决方案,但我希望我要提出的概念很清楚。