Batching immutable object changes?

With immutable models, what would be the best way to batch several changes?

For example, let’s have a Book that is immutable. I need to change both title and year. I could change one by one, but that would just create one additional object that is not going to be used. Is there any better way for this?

I can imagine only something with constructor:

new Book(oldBook, newTitle, newYear);

or to have external tool for cloning books, that we can use.

5

Depending on the number of fields, you might find it easier to use the Builder pattern to combine these changes than to create a separate constructor for each possible set of changes. It does requiring one object that is not going to be used (returned), but you can use one builder instance for as many books as you need to change.

public final class Book {
    private final String title;
    private final Integer year;
    private final List<String> authors;

    public static final class Builder {
        private String title = null;
        private Integer year = null;
        private List<String> authors = null;

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

        public Builder from(Book other) {
            withTitle(other.title);
            withYear(other.year);
            withAuthors(other.authors);

            return this;
        }

        public Builder withTitle(String title) {
            this.title = title;

            return this;
        }

        public Builder withYear(Integer year) {
            this.year = year;

            return this;
        }

        public Builder withAuthors(List<String> authors) {
            if (authors.isEmpty())
                this.authors = Collections.emptyList();
            else
                this.authors = new ArrayList<String>(authors);

            return this;
        }
    }

    private Book(Builder builder) {
        this.title = builder.title;
        this.year = builder.year;
        this.authors = builder.authors;
    }

    public String getTitle() {
        return title;
    }

    public Integer getYear() {
        return year;
    }

    public List<String> getAuthors() {
        return Collections.unmodifiableList(authors);
    }
}

To make a new object from an old one:

Book oldBook = new Book.Builder()//
        .withTitle("Hello")//
        .withYear(1900)//
        .withAuthors(Arrays.asList("Alice"))//
        .build();
Book newBook = new Book.Builder()//
        .from(oldBook)//
        .withTitle("World")//
        .withYear(1901)//
        .build();

One useful pattern is to define a public interface which can report all of the properties associated with the class, as well as a package-private interface which extends it with a few additional members. A package-private version of the class should be mutable, but the public-facing one should be immutable. The public-facing class should hold a reference of the private interface type, and implement its members in terms of that private interface. Additionally, all classes in the package must maintain the invariant that no instance of the mutable type may ever be modified after a reference to it has been stored in a field which could be reachable on another thread.

Invoking a method like WithName(String name) on the public face of the class could cause it to call WithName on the encapsulated object, which would in most cases constructing a NameAdder object which holds the new Name value along with a reference to the encapsulated object itself. All methods other than GetName and WithName would chain to the encapsulated object; while GetName() would return the new Name, and WithName would return a new NameAdder object which holds the new Name and a reference to the object encapsulated in the NameAdder (rather than a reference to the NameAdder itself).

An obvious problem with this approach is that more and more operands get performed upon an object, the chains of method invocations would become longer and longer. To mitigate that, the internal interface (and perhaps the public one as well) should include a Flatten method. Calling Flatten on an implementation of the interface should yield a new object which applies all of the changes that have been added to it via methods like WithName. One way of accomplishing that would be to construct a new object where every field’s value was computed by calling Get on all of the objects involved. That could be expensive and inefficient, however, if it gets invoked upon an object that’s very “deep”.

An alternative approach would be to have the private interface include an AsFlattenedMutable method which would an instance of the mutable class to which all appropriate changes had been applied, and to which no reference had ever been stored in any class field anywhere. The mutable class could implement AsFlattenedMutable to return a clone of itself; a class like NameAdder would implement AsFlattenedMutable to invoke AsFlattenedMutable upon the encapsulated object, modify the name stored in that mutable object, and return the object after the mutation. This would thus allow even a rather deep modification chain to be resolved rather quickly.

The only slight “gotcha” with that approach is if code modifies an object and then stores a reference into a non-final field, the generated code could potentially resequence the operations so that the reference to the object gets stored before all the modifications are complete. To properly solve that may require having a new object store a reference to the mutable object into a final field, and then copying the reference from that final-field into the non-final one. By my understanding of the JVM specification, storing a reference to an object into a final field of an object under construction would prevent the deferral of any pending requests beyond the return of the constructor, and reading the value of that field would prevent the non-final field from being assigned until after the execution of the constructor [if code didn’t read the reference from the new object, but instead simply used a pre-existing copy, then the call to the constructor–as well as any pending mutations to the object of interest–could be deferred until after the assignment].

Here’s a twist on Matt’s answer, using the builder pattern combined with Java 8’s new functional capabilities:

Book.java

import java.util.*;
import java.util.function.Consumer;

public class Book
{
    private String title;
    private List<String> authors;
    private int year;

    public Book(String title, List<String> authors, int year) {
         this.title = title;
         this.authors = new ArrayList<>(authors);
         this.year = year;
    }

    /*
     * Start with the current Book, create a BookBuilder with all the same properties, then send that through a functional lambda for changing what needs changed.
     */
    public Book cloneWith(Consumer<BookBuilder> bookRebuilder) {
        BookBuilder bookBuilder = new BookBuilder(getTitle(), getAuthors(), getYear());
        bookRebuilder.accept(bookBuilder);
        return new Book(bookBuilder.getTitle(), bookBuilder.getAuthors(), bookBuilder.getYear());
    }

    public String getTitle() {
        return title;
    }

    public int getYear() {
        return year;
    }

    public List<String> getAuthors() {
        return new ArrayList<String>(authors);
    }

    public String toString() {
        StringBuilder str = new StringBuilder();

        str.append(title);

        str.append(", by ");
        for(String author : authors) {
            str.append(author);
            str.append(", ");
        }

        str.append("published ");
        str.append(year);

        return str.toString();
    }
}

BookBuilder.java (this could just as easily be a static internal class)

import java.util.*;

public class BookBuilder
{
    private String title;
    private List<String> authors;
    private int year;

    public BookBuilder(String title, List<String> authors, int year) {
        this.title = title;
        this.authors = authors;
        this.year = year;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<String> getAuthors() {
        return authors;
    }

    public void setAuthors(List<String> authors) {
        this.authors = authors;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }
}

Main.java

import java.util.*;

public class Main
{
    public static void main(String[] args) {
        Book book1 = new Book("Pride and Prejudice", Arrays.asList("Jane Austen"), 1813);

        Book book2 = book1.cloneWith((baseBook)-> {
            baseBook.setTitle("Pride and Prejudice and Zombies");
            baseBook.setYear(2009);
            List<String> authors = baseBook.getAuthors();
            authors.add("Seth Grahame-Smith");
            baseBook.setAuthors(authors);
        });

        System.out.println(book1.toString());
        System.out.println(book2.toString());
    }
}

One big difference is that, instead of having to use a chain of fluent calls that might be hard to break, we can interject other logic within the lambda closure. Here BookBuilder could also be made fluent so it can be just as terse.

3

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật