Java & Immutable Object pattern

Photo Credit: WeGraphics

Java & Immutable Object pattern

Immutable Object - шаблон проектирования, который подразумевает создание объекта, который не может быть изменен. Используется как для устранения дорогих операций копирования и сравнения, так и в многопоточной среде, для предотвращения Shared Mutable State между потоками.

Хороший пример

В Java может быть реализован так:

final class Unicorn {
	private final String name;
	private final String symbol;

	public Unicorn(final String name, final String symbol) {
		this.name = name;
		this.symbol = symbol;
	}

	public String getName() {
		return name;
	}

	public String getSymbol() {
		return symbol;
	}
}

Unicorn lady = new Unicorn("Lady Rainicorn", "rainbow");

Теперь можем передавать наш объект между потоками, никто его не изменит. Звучит хорошо, если не доводить до фанатизма.

История из жизни

Чекстайл завалил билд. Причина - превышено допустимое количество параметров конструктора. Посмотрели - конструктор на 22 параметра. Сразу возникли вопросы: “WAT?”, “А зачем такой большой плоский объект?”, “Как же его создавать?”. Ответы, которые можно услышать: - Плоская структура объекта вызвана тем, что мапили JSON. - Не понятно, но пропустим. - Большой конструктор вызван тем, что общий подход к построению системы предполагает, что все Data Objects будут Immutable. - Воу, Воу. Палехче! - Создавать руками планируется только в тестах. Можно билдер создать. Но все равно, все поля класса дожны быть инициализированы в конструкторе, ведь они final и …. рассказ про то, как Java Memory Model могла когда-то поступить в многопоточной среде при инициализации полей. - Хитро, хитро.

Проблему поправили. Теперь чекстайл позволяет передавать до 23 параметров в метод ;)

Байки про Java Memory Model же как правило сводятся к рассказам о том, что другой поток может увидеть частично проинициализированный объект, т.е. такой объект, часть полей которого для другого потока будет null’ами, хотя они и были проинициализированы в родительском потоке. JSR 133, касающийся Java Memory Model, даёт гарантию initialization safety — обещает, что частично проинициализированных объектов никто не увидит, но только если объект был корректно создан. Корректно в данном случае означает, что ссылка на объект не была никуда передана из конструктора. Если вы захотите положить this в коллекцию, установить его в качестве callback или listener прямо из конструктора (например, button.setOnClickListener(this)), то даже final поля, согласно JSR 133, вам могут не помочь.

Мнения

Несколько месяцев опросов коллег помогли создать список “За и Против”. ### Pros #### Value Object Это такая разновидность неизменяемых объектов, представляющих какое-то значение. Например, String или Integer. #### Работа в многопоточной среде. Здорово, когда объекты, которые разделяют несколько потоков, сохраняют свое состояние неизменным, особенно если вы не очень понимаете, как всё это работает. Это позволяет предотвратить появление множества сложно обнаруживаемых ошибок. #### Partially immutable Частично неизменяемый объект может быть не только полезен, но и логичен. Если у вас есть класс Engine, экземпляры которого с одной стороны не могут существовать с не установленным полем VIN, а с другой стороны — это поле никогда не меняется, VIN можно сделать final и требовать обязательного указания в конструкторе. ### Cons #### Java EE Контейнеры приложений. В большинстве случаев они прячут от программиста многопоточную среду в которой приложение работает. Это снимает много вопросов, связанных с синхронизацией. (прим. Добавляет много других. Специфических ;)) #### Mutable Immutable Иногда люди доходят до фанатизма и стараются сделать immutable даже те объекты, которые явно предполагают изменения, в т.ч. в многопоточной среде. Делать класс Position неизменяемым, а затем требовать его пересоздания каждый раз, когда понадобилось изменить координаты объекта — что-то странное. Если вы так беспокоитесь о многопоточности, зачем нагружаете сборщик мусора таким объемом ненужной работы? #### JavaBeans JavaBeans — классы в языке Java, написанные по определённым правилам. Классический подход к DataObject в Java. Используется в большинстве библиотек. Многие фреймворки будут надеяться найти сеттеры и геттеры в вашем объекте. #### Maintainability Java — язык в возрасте. Да, он позволяет создавать Immutable объекты. Однако, работать с ними не просто. Особенно с большими. Не хватает синтаксического сахара. Вот пример паттерна на других ЯП Groovy:

@Immutable
final class Pony {
	String name
	String color
	Integer age
}
new Pony(name = "Princess Celestia", color = "white", age = "70")

Scala:

case class Pony(name :String, color :String, age :Integer)
new Pony(name = "Princess Celestia", color = "white", age = "70")

Java:

public class ImutableCatSpecificationDataObject {

    private String name;
    private String color;
    private String foo;
    private String bar;
    private String addYourFavoritePropertyHere;

    public ImutableCatSpecificationDataObject(String name, String color, String foo, String bar, String addYourFavoritePropertyHere) {
        this.name = name;
        this.color = color;
        this.foo = foo;
        this.bar = bar;
        this.addYourFavoritePropertyHere = addYourFavoritePropertyHere;
    }

    // -- getters omitted --
}

Попробуйте добавить в таком конструкторе новый параметр между name и color, а затем поправить все его вызовы. Или удалить bar. Представьте, каково приходится парням с конструкторами на 23 параметра, когда эти параметры добавляются, убираются, становятся опциональными или меняются местами.

Выводы

Использовать Immutable Object паттерн можно и иногда даже нужно. Как часто? Из проекта в проект мнение различается кардинально. А что думаете вы?