noMAD
noMAD

Reputation: 7844

How to return a new object each time the old one is modified?

Some code:

public class Person {

  private Person(Builder builder) {
    //construct the person object
  }

  public static class Builder {
    public Builder withName(String name) {
      this.name = name;
      return this;
    }
    public Builder withAge(int age) {
      this.age = age;
      return this;
    }
    public Builder withPhone(int phone) {
      this.phone = phone;
      return this;
    }

    //How to add this method?
    public Builder withPerson(Person person) {
      this.person = person;
      return this;
    }

    public Person build() {
      return new Person(this);
    }
  }
}

So basically I have an object, a person in this case which has a builder. Now Say I initially build the person with only a name and an age and get the object PersonOne. Now I want the object PersonOneto be immutable. So if I want to add a phone to PersonOne I want to do it such that it returns a PersonTwo which has everything that PersonOne has and also the new phone. So, each time I modify the Person object I get a new one and the old one remains immutable. I am not sure how to make this work in java without just blindly copying all the existing attributes of a Person object into a new one and then add the new attribute and return a new Person. I don't want to do this cause the object I am having has a lot of attributes and a lot of if conditions would make the code look bad. Is there a better way to do this?

Upvotes: 2

Views: 630

Answers (5)

Captain Man
Captain Man

Reputation: 7695

My personal preference on this is to add an additional constructor in the builder that takes a Person and copies the fields that way. This is very similar to Dici's answer but will differ in that you don't add in "change" methods.

public Builder(Person person) {
  this.name = person.name;
  this.age = person.age;
  this.phone = person.phone
}

The reason I prefer to not add "change" methods is because it just feels like it's mutable when you do that.

To change with my solution you would do this:

Person coolGuy = ...
coolGuy = new Person.Builder(coolGuy).withAge(-4);

Upvotes: 0

Marko Topolnik
Marko Topolnik

Reputation: 200168

I would suggest the following approach (I hope the code can do all the talking):

public class Person {
  private String name;
  private int age;
  private String phone;

  public Person(Person that) {
    this.name = that.name;
    this.age = that.age;
    this.phone = that.phone;
  }

  public Person withName(String name) {
    final Person changed = new Person(this);
    changed.name = name;
    return changed;
  }

  public Person withAge(int age) {
    final Person changed = new Person(this);
    changed.age = age;
    return changed;
  }

  public Person withPhone(String phone) {
    final Person changed = new Person(this);
    changed.phone = phone;
    return changed;
  }

  ...getters...

}

Just for fun (not that I'd actually go that far with a simple problem as this), you could implement the above in a slightly more concise manner by making use of lambdas:

public Person withPhone(String phone) {
  return copyAndChange((p, newPhone) -> p.phone = newPhone, phone);
}

private <T> Person copyAndChange(BiConsumer<Person, T> action, T newValue) {
  final Person changed = new Person(this);
  action.accept(changed, newValue);
  return changed;
}

Upvotes: 3

Dici
Dici

Reputation: 25960

You have a builder, so just use it (more concise that Marko Topolnik's answer) :

public class Person {
    public PersonBuilder copy() {
        return new PersonBuilder()
                       .withName(this.name)
                       .withAge(this.age)
                       .withPhone(this.phone);
    }

    public Person changeName(String name) {
        return copy().withName(name).build();
    }

    public Person changeAge(int age) {
        return copy().withAge(age).buid();
    }
}

Upvotes: 5

WilQu
WilQu

Reputation: 7393

Initialize Builder with an existing person. First, add the following constructors:

public Builder() { /* create an initially empty Builder */ }
public Builder(Person person) {
    /* Initialize the Builder with an existing person */
}

Now, to change the phone number:

Person personTwo = new Builder(personOne).withPhone(phone).build();

By the way, you shouldn't store phone numbers as integers.

Upvotes: 0

libik
libik

Reputation: 23029

This is what setters are for.

You basically do not want to use the "classic" ones, then dont use them and keep your fields private. No one is capable to change them then and your object is immutable.

Then you can create custom methods, similar to setters like "changeNumber", which would create new Person, set all parameters with changed phone and returns taht Person (instead of being void).

Upvotes: -1

Related Questions