Есть ли способ гарантировать во время компиляции, что определенные поля в неизменяемом объекте заданы, при этом роль аргументов остается ясной?

#java #immutability

#java #неизменяемость

Вопрос:

Я экспериментирую со способами создания неизменяемых объектов. Следующие объекты builder довольно привлекательны, потому что они сохраняют роль аргументов ясной. Однако я хотел бы использовать компилятор для проверки того, что определенные поля установлены, например, при Immutable() вызове конструктора. StrictImmutableBuilder обеспечивает эти проверки, но является довольно шумным. Есть ли какой-нибудь способ получить те же проверки, но в форме LaxImmutableBuilder ? Возможно, с помощью аннотаций?

 public class Immutable {

    public static void main(String[] args) {

        new Immutable("13272873C", 23, false);
        // nice but what where those arguments?

        new LaxImmutableBuilder() {{
            refCode("13272873C");
            age(23);
            subscribed(false);
        }}.build();
        // now I know what each value represents
        // but what if I forgot to set one?

        new StrictImmutableBuilder() {
            public String refCode() { return "13272873C"; }

            public int age() { return 23; }

            public boolean subscribed() { return false; }
        }.build();
        // now I'm forced to set each field, but now 
        // we have the extra noise of "return"
        // and also "public" if we want to use
        // this outside the current package

        // is there another way? maybe using annotations?
    }

    private final String refCode;
    private final int age;
    private final boolean subscribed;

    public String getRefCode() {
        return refCode;
    }

    public int getAge() {
        return age;
    }

    public boolean isSubscribed() {
        return subscribed;
    }

    public Immutable(String a, int b, boolean c) {
        this.refCode = a;
        this.age = b;
        this.subscribed = c;
    }

}

abstract class StrictImmutableBuilder {
    public abstract String refCode();

    public abstract int age();

    public abstract boolean subscribed();

    public Immutable build() {
        return new Immutable(refCode(), age(), subscribed());
    }
}

abstract class LaxImmutableBuilder {

    private String refCode;
    private int age;
    private boolean subscribed;

    protected void refCode(String refCode) {
        this.refCode = refCode;
    }

    protected void age(int age) {
        this.age = age;
    }

    protected void subscribed(boolean subscribed) {
        this.subscribed = subscribed;
    }

    public Immutable build() {
        return new Immutable(refCode, age, subscribed);
    }

}
  

Ответ №1:

Вот шаблон, который я использую:

 class YourClass {
  // these are final
  private final int x;
  private final int y;

  private int a;
  private int b;

  // finals are passed into the constructor
  private YourClass(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public static class Builder {
    // int x, int y, int a, int b
    // whatever's final is passed into constructor
    public Builder(int x, int y) {
      this.x = x;
      this.y = y;
    }
    // a and b are optional, so have with() methods for these
    public Builder withA(int a) {
      this.a = a;
      return this;
    }
    public Builder withB(int b) {
      this.b = b;
      return this;
    }
    public YourClass build() {
      YourClass c = new YourClass (x, y);
      c.a = a;
      c.b = b;
      return c;
    }
  }
}
  

Комментарии:

1. 1: Я поступаю аналогично, но вместо того, чтобы писать конструктор, я генерирую его из базового неизменяемого класса. Это помогает поддерживать согласованность классов.

2. @PeterLawrey Я предполагаю, что вы используете инструмент для выполнения этой генерации, если да, то какой?

3. Спасибо! Это хороший шаблон, если ваши объекты имеют необязательные свойства. Я полагаю, внутренний класс также помогает людям найти конструктор для данного типа.

4. Я настроил живой шаблон IntelliJ, поэтому, когда я нажимаю «b», «tab», он автоматически генерирует для меня метод with (), что упрощает написание этих вещей

Ответ №2:

есть такой трюк: шаблон построения, безопасный для типов

http://michid.wordpress.com/2008/08/13/type-safe-builder-pattern-in-java/

но это просто слишком безумно.

Комментарии:

1. Спасибо. Хорошая находка. Я почти достаточно сумасшедший, чтобы использовать это! 🙂 Но на самом деле это не масштабируется до большого количества аргументов.