整个程序可以不变吗?

时间:2018-06-22 09:36:41

标签: java immutability

Chain of immutable objects

  • 我对不变性很熟悉,可以设计不变性类,但是我大部分都是学术知识,缺乏实践经验
  • 请参考上面的链接图片(尚未嵌入)
  • 从下往上看
  • 学生需要一个新地址
  • 我们创建了一个新学生,而不是真正地改变学生
  • mutator方法返回这个新对象

问题:如果假设mutator调用来自一个不可变的对象,那么如何处理这个新对象?

  • 新学生不能保存在讲座中,因为讲座也是不可变的
  • 所以我们也需要一个新的讲座,其中包括新的学生
  • 但是在哪里保存新的演讲?
  • 当然,在新学期中,它会在哪里结束?
  • 至少可以通过使用组件外观模式来断开该链,该组件可以处理所有新对象的创建,而无需通过整个链转发呼叫。

问题:这在哪里停止?不必在某个地方至少有一个可变对象至少可以保存最顶层的实例吗?

2 个答案:

答案 0 :(得分:1)

这是函数式编程的思想。一切都是不可变的,不允许任何函数调用产生副作用。像您的示例一样,突变复杂对象的唯一方法是重新创建父对象。

现在的问题是如何更改程序状态。因此,我们首先考虑堆栈。它包含所有局部变量的值以及被调用函数的所有参数的值。我们可以通过调用新函数来创建新值。我们可以通过从函数返回来丢弃值。因此,我们可以通过调用函数来改变程序状态。但是,并非总是可以从函数中返回以丢弃其局部变量,因为我们可能只希望丢弃某些局部变量,而需要保留其他局部变量的值以进行进一步的操作。在这种情况下,我们根本无法返回,但是我们需要调用另一个函数并将仅一些局部变量传递给它。现在,为了防止堆栈溢出,功能语言具有称为尾调用优化的功能,该功能可以从调用堆栈中删除不必要的条目。如果关联函数唯一要做的就是返回自身调用的函数的值,则不需要调用堆栈。在这种情况下,没有必要保留调用堆栈条目。通过删除不必要的调用堆栈条目,将丢弃原本未使用的局部变量的值。您可能需要阅读here。另外,tail recursion与此相关。

同样,这是像Haskell这样的纯函数式编程语言的思想。一切都是不可变的,这真是太好了,但是这些语言只有它们自己的问题,并且有自己的处理方式。例如,Monad(以及更高种类的类型)在这些语言中可用,但是在命令式/面向对象的编程语言中很少见。

我喜欢在程序存储器的叶子处具有不变的值。但是,构成这些不可变值的代码实际上构成了应用程序逻辑,但确实包含可变状态。对我来说,这结合了两个世界的优势。但是,这似乎是一个优先事项。

答案 1 :(得分:0)

使用您现有的结构,这将非常困难,这可能是您应该从本练习中学到的东西。

我将从对象中删除对象之间的所有关系,并使用MapSet来实现这些关系。

这样的事情将是一个很好的起点。

// 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<>();

通过这种方式,您可以隔离对象并使它们保持不变,但是如果您确实需要更改任何学生的详细信息,则可以克隆原始学生并窃取其身份。

这显然还不是一个完整的解决方案,但我希望我要提出的概念很清楚。