"Changing" complex immutable object

Viewed 115

This is the code:

class A{
prop1, prop2, prop3, prop4, ...

 private A(ABuilder b){
   this.prop1 = b.prop1;
   ...
 }

 A changeProp2(){
   //easiest way to return new immutable A?
 }

 class ABuilder{
   withProp1()
   withProp2()
   withProp3()
   ...
   build()
 }

}


A a = new ABuilder().withProp1().withProp2().build();
A newA = a.changeProp2();

I have immutable object (A in this case), that is constructed using Builder ABuilder. Now, when I want new A object from existing complex A object, in my case I can call method changeProp2(). This method should copy all the inner properties of object a, change only property2 to new value, and return new object newA.

What is the best way to do this?

Options I found so far are:

Inside changeProp2() method, I could copy all the properties - but this seems too much, and also not reusable if I have in future changeProp3() method.

//option1
A changeProp2(){
  return new ABuilder().withProp1(this.prop1).withProp2("newProp2")....build();
}

Adding copy constructor to Builder, that will init Builder with values from existing object A, like this:

//option2
class ABuilder{
  ABuilder(A a){
    this.prop1 = a.prop1;
    ...
  }
}

A changeProp2(){
  return new ABuilder(this).withProp2("newProp2").build();
}

This seems more reasonable for me in this case.

Are there any more options than this?

1 Answers

Instead of returning a fully-constructed A value from changeProp2, you can create a method such as createCopyFrom that returns a builder for A. For example:

public class A {

    private final String prop1;
    private final String prop2;
    private final String prop3;

    public A(String prop1, String prop2, String prop3) {
        this.prop1 = prop1;
        this.prop2 = prop2;
        this.prop3 = prop3;
    }

    public ABuilder createCopyFrom() {
        return new ABuilder()
            .withProp1(prop1)
            .withProp2(prop2)
            .withProp3(prop3);
    }

    // ...getters...
}

public class ABuilder {

    private String prop1;
    private String prop2;
    private String prop3;

    public ABuilder withProp1(String prop1) {
        this.prop1 = prop1;
        return this;
    }

    public ABuilder withProp2(String prop2) {
        this.prop2 = prop2;
        return this;
    }

    public ABuilder withProp3(String prop3) {
        this.prop3 = prop3;
        return this;
    }

    public A build() {
        return new A(prop1, prop2, prop3)
    }
}

A few important things of note: In the example above, I used a fluent interface for ABuilder, but that is not required. It makes returning the ABuilder from createCopyFrom easier, but it can just as easily be done if the ABuilder methods do not return this. Instead, you would set each property (e.g. withProp1, withProp2, etc.) and then return the builder as follows:

public ABuilder createCopyFrom() {
    ABuilder builder = new ABuilder();
    builder.withProp1(prop1)
    builder.withProp2(prop2)
    builder.withProp3(prop3);
    return builder;
}

Also, if you want to have a method such as changeProp2, you can utilize the createCopyFrom method to change just the property of interest:

public class A {

    // ...same methods and fields as before...

    public A changeProp2(String prop2) {
        return createCopyFrom().withProp2(prop2).build();
    }
}
Related