带有Java中周期的不变对象图
#java #graph #immutability

您正在编写Java程序。您可以用互相引用的对象对您的域实体进行建模。您希望您的对象是不变的,因此在课堂中使用final成员。

现在您检测到一个问题:您的对象图包含周期。您如何实例化对象?

要说明,让我们假设您有一个Parent和class Child,并且每个都使用最终成员引用彼此:

class Parent {
        final Child child;

        final String name;

        Parent(Child child, String name) {
            this.child = child
            this.name = name;
        }

        public String toString() {
            return  name + " is the parent of " + this.child.name;
        }
    }

    static class Child {
        final Parent parent;

        final String name;

        Child(Parent parent, String name) {
            this.parent = parent;
            this.name = name;
        }

        public String toString() {
            return name + " is the child of " + this.parent.name;
        }

    }

当您尝试实例化父母/子对时,您将无法做到这一点。

问题在于,您需要孩子的实例来实例化父母和父母实例来实例化孩子,并且由于课堂内的这些参考是最终的,因此您不能在拥有另一个之前就拥有一个。您必须以某种方式同时实例化...或某种情况。

但是,可以做到这一点,这就是如何(省略所有钟声和哨子,要使它成为线程安全或其他好的哨子,只是提出主要想法):

  1. 使用构建器模式创建您的对象
  2. 在构建器中,请参考您在呼叫上返回build()的实例,并允许通过公共方法设置此实例,例如setProduct(T instance)
  3. 将X类的构建器传递到x
  4. 的构造函数中
  5. 在构造函数的第一行中,将构建器的实例设置为this,即。 builder.setProduct(this)
  6. 而不是将类实例传递给构造函数,而是通过返回build()的实例的构建器。

这是代码:


public class ImmutableCyclicObjectGraphExperiment {


    static class Parent {
        final Child child;

        final String name;

        Parent(ParentBuilder builder, ChildBuilder childBuilder, String name) {
            builder.setInstance(this);
            this.child = childBuilder.build();
            this.name = name;
        }

        public String toString() {
            return  name + " is the parent of " + this.child.name;
        }
    }

    static class Child {
        final Parent parent;

        final String name;

        Child(ChildBuilder builder, ParentBuilder parentBuilder, String name) {
            builder.setInstance(this);
            this.parent = parentBuilder.build();
            this.name = name;
        }

        public String toString() {
            return name + " is the child of " + this.parent.name;
        }

    }

    static class ParentBuilder {
        ChildBuilder childBuilder;
        String name;
        Parent instance = null;

        public ParentBuilder() {
        }

        void setInstance(Parent instance){
            this.instance = instance;
        }

        Parent build() {
            if (this.instance == null) {
                this.instance = new Parent(this, this.childBuilder, this.name);
            }
            return this.instance;
        }

        public ParentBuilder child(ChildBuilder childBuilder) {
            this.childBuilder = childBuilder;
            return this;
        }

        public ParentBuilder name(String name) {
            this.name = name;
            return this;
        }
    }

    static class ChildBuilder {
        ParentBuilder parentBuilder;
        String name;
        Child instance = null;

        Child build() {
            if (this.instance == null) {
                this.instance = new Child(this, parentBuilder, name);
            }
            return this.instance;
        }

        void setInstance(Child instance) {
            this.instance = instance;
        }

        public ChildBuilder parent(ParentBuilder parentBuilder) {
            this.parentBuilder = parentBuilder;
            return this;
        }

        public ChildBuilder name(String name) {
            this.name = name;
            return this;
        }
    }


    public static void main(String[] args) {
        ParentBuilder pb = new ParentBuilder();
        ChildBuilder cb = new ChildBuilder();
        pb
            .name("Anakin")
            .child(cb);
        cb
            .name("Luke")
            .parent(pb);
        Parent p = pb.build();
        Child c = cb.build();
        System.out.println(p);
        System.out.println(c);
    }
}

发生的事情是,当ParentBuilder.build()首先在main()函数中调用时,构建器将Parent的构造函数和引用this称为所创建的对象的构造函数立即将其走私到构建器中。然后,在同一构造函数的下一行中,childBuilder作为构造函数参数传递,用于获得Child实例。 childBuilder称为Child的构造函数,传递了一个ParentBuilder实例,这与我们在main()函数中已经使用的相同来开始实例化Parent,并且已经在呼叫堆栈上仍将对Parent的走私引用保持了几个级别。因此,在Child构造函数中,parentBuilder.build()返回走私参考,即使其引用的对象尚未完全实例化,也可以分配给最终成员。当Child c = cb.build()main()函数中调用时,该实例已经完全实例化并返回而无需调用构造函数。

只要发生在同一线程中,您保证不对这些实例做任何事情,而是引用它们,您应该安全。

正如预期的,调用main()方法打印以下内容:

Anakin is the parent of Luke
Luke is the child of Anakin

不变和循环,所有必要的间接是在实例化时完成的。那不是很好吗?