Подтвердить что ты не робот

Java: метод псевдо-setter для неизменяемых классов

Скажем, у меня есть класс Foo на Java, который имеет неизменные данные:

class Foo {
    final private int x;
    public int getX() { return this.x; }
    final private OtherStuff otherstuff;
    public Foo(int x, OtherStuff otherstuff) { 
       this.x = x;
       this.otherstuff = otherstuff;
    }   
    // lots of other stuff...
}

Теперь я хотел бы добавить метод утилиты, который создает значение "sibling" с идентичным состоянием, но с новым значением x. Я мог бы назвать его setX():

class Foo
{
    ...
    Foo setX(int newX) { return new Foo(newX, this.otherstuff); }
    ...
}

но семантика setX() отличается от стандартного соглашения setter для изменяемых объектов bean, поэтому почему-то это не кажется правильным.

Какое лучшее имя для этого метода?

Должен ли я называть его withX() или newX() или что-то еще?


edit: дополнительный приоритет в моем случае: у меня есть скриптовые клиенты (через JSR-223 и экспортирующую объектную модель), которые могут легко получить объект Foo. Однако громоздко, чтобы вызвать конструкторы или создать строителей или что-то еще. Поэтому мне желательно предоставить этот метод в качестве удобства для скриптовых клиентов.

4b9b3361

Ответ 1

Оригинальная статья: Неизменныесеттеры: соглашения об именах (из Programming.Guide)


withX(...)

Это де-факто стандартное соглашение об именах для неизменяемых сеттеров. Это, например, имя по умолчанию для сеттеров, созданных структурой Immutables. Вот пример:

Foo newFoo = foo.withX(1047);

Существует опция @Value.Style для изменения этого шаблона, но сама опция называется with="...", в которой подчеркивается, что такое соглашение по умолчанию.

Будучи наиболее распространенным соглашением, легко найти примеры этого. Гуава и пакет времени Java равны двум.

Просто x(...)

Другой подход заключается в том, чтобы вообще не иметь префикса. Это можно увидеть, например, в построителях, сгенерированных структурой Immutables:

Foo foo = ImmutableFoo.builder()
                      .x(1047)
                      .y("Hello World")
                      .build();

Если вы используете этот подход непосредственно в неизменяемом классе (то есть без компоновщика), вы обычно будете использовать его как перегрузку для получателя:

Foo newFoo = foo.x(5);  // setter - one argument
int x = newFoo.x();     // getter - no arguments

Это соглашение используется, например, в среде Java Spark.

setX(...)

Некоторые API используют то же соглашение об именах, что и для сеттеров в изменяемых классах. Это имеет очевидный недостаток, который может быть удивительным, когда вы новичок в коде. Работаем с BigInteger и пишем…

bigInt.setBit(2);

… например, будет ошибкой, поскольку возвращаемый объект отбрасывается. С этим шаблоном именования вы должны привыкнуть писать

BigInteger newBigInt = bigInt.setBit(2);

deriveX(...)

Чтобы подчеркнуть тот факт, что новое значение является производным от существующего объекта, вы можете использовать deriveX(...). Постоянный класс Font в Java API следует этому шаблону. Если вы хотите создать новый шрифт, например, с определенным размером, который вы используете

Font newFont = font.deriveFont(newSize);

Класс Font существует с незапамятных времен. Это соглашение не очень распространено на сегодняшний день.

Неизменяемый объект, являющийся операндом

Когда неизменный объект сам по себе является операндом преобразования, он в действительности не является установщиком в традиционном смысле, и нет необходимости иметь префикс для метода. Например...

BigDecimal newBigDec = bigDec.multiply(BigDecimal.TEN);

… имеет такую же сигнатуру, что и установщик, но multiply явно лучше, чем любое другое альтернативное имя метода.

То же самое с String.substring, Path.resolve и т.д.

Ответ 2

withX() звучит нормально, потому что это соглашение используется для некоторых шаблонов Builder.

Это скорее "частичный клон" или "строитель", чем "сеттер"...

Если вы посмотрите на java.lang.String (также неизменяемый), существуют всевозможные методы, которые возвращают новую String на основе старой (подстрока, toLowerCase() и т.д.)...

Обновить: см. также ответ от aioobe [ deriveFoo()], который мне нравится - возможно, он более ясен, особенно для тех, кто не знаком с шаблонами Builder.

Ответ 3

Я бы назвал его withX(value). В нем говорится, что это будет что-то с x = value.

Если в классе было много полей, я бы боялся:

obj.withX(1).withY(2).withZ(3).withU(1)...

Поэтому я мог бы использовать шаблон построителя - ввести измененный вариант данного класса с только данными и методами для создания исходного класса с его текущим состоянием. И там я бы назвал эти методы x(), y(), z() и сделал их return this. Таким образом, это будет выглядеть так:

Immutable im2 = new Mutable(im1).x(1).y(2).z(3).build();

Ответ 4

Определенно не сеттер, так как он фактически создает и возвращает новый объект. Я думаю, что семантика factory была бы более подходящей опцией в этом случае

public Foo newFooWith(int x) {
   return new Foo(x, other);
}

Альтернативой может быть вариант конструктора копирования

class Foo {
    public Foo(Foo foo, int x) {
      return new Foo(x, foo.getOtherStuff());
    }
}