Возможности создания неизменяемого класса в Java - PullRequest
2 голосов
/ 25 февраля 2011

Каковы возможности создания неизменного компонента в Java. Например, у меня есть неизменный класс Person. Какой хороший способ создать экземпляр и заполнить приватные поля. Публичный конструктор не кажется мне хорошим из-за большого количества входных параметров, так как класс будет расти в остальной части приложения. Спасибо за любые предложения.

public class Person {

private String firstName;
private String lastName;
private List<Address> addresses;
private List<Phone> phones;

public List<Address> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<Phone> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

РЕДАКТИРОВАТЬ : уточните вопрос.

Ответы [ 9 ]

7 голосов
/ 25 февраля 2011

Вы можете использовать шаблон построения .

public class PersonBuilder {
  private String firstName;
  // and others...

  public PersonBuilder() {
    // no arguments necessary for the builder
  }

  public PersonBuilder firstName(String firstName) {
    this.firstName = firstName;
    return this;
  }

  public Person build() {
    // here (or in the Person constructor) you could validate the data
    return new Person(firstName, ...);
  }
}

Затем вы можете использовать его так:

Person p = new PersonBuilder.firstName("Foo").build();

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

  • Вам не нужно указывать значения, которые вы хотите сохранить по умолчанию
  • Вы можете расширить класс Person и конструктор, не объявляя несколько конструкторов и не переписывая каждый код, который создает Person: просто добавьте методы в конструктор, если кто-то их не вызывает, он не не имеет значения.
  • Вы можете обойти объект построителя, чтобы разные куски кода могли устанавливать разные параметры Person.
  • Вы можете использовать конструктор для создания нескольких похожих Person объектов, которые могут быть полезны для модульных тестов, например:

    PersonBuilder builder = new PersonBuilder().firstName("Foo").addAddress(new Address(...));
    Person fooBar = builder.lastName("Bar").build();
    Person fooBaz = builder.lastName("Baz").build();
    assertFalse(fooBar.equals(fooBaz));
    
2 голосов
/ 25 февраля 2011

с интерфейсами. Сделайте это:

public interface Person {
    String getFirstName();
    String getLastName();

    // [...]
}

И ваша реализация:

// PersonImpl is package private, in the same package as the Factory
class PersonImpl {

    String getFirstName();
    void setFirstName(String s);
    String getLastName();
    void setLastName(String s);

    // [...]
}

// The factory is the only authority to create PersonImpl
public class Factory {

    public static Person createPerson() {
        PersonImpl result = new PersonImpl();

        // [ do initialisation here ]

        return result;
    }
}

И никогда не подвергайте реализацию местам, где вы хотите, чтобы Person был неизменным.

2 голосов
/ 25 февраля 2011

Хорошее решение - сделать поля final, добавить конструктор private и использовать Builders в своем коде. В нашем проекте мы объединили шаблон Builder с каркасом проверки, чтобы после создания объекта мы были уверены, что он неизменен и действителен.

Вот краткий пример:

public class Person {

public static class Builder {

    private String firstName;
    private String lastName;
    private final List<String> addresses = new ArrayList<String>();
    private final List<String> phones = new ArrayList<String>();

    public Person create() {
        return new Person(firstName, lastName, addresses, phones);
    }

    public Builder setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public Builder setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    public Builder addAddresse(String adr) {
        if (adr != null) {
            addresses.add(adr);
        }
        return this;
    }

    public Builder addPhone(String phone) {
        if (phone != null) {
            phones.add(phone);
        }
        return this;
    }
}

// ************************ end of static declarations **********************

private final String firstName;
private final String lastName;
private final List<String> addresses;
private final List<String> phones;

private Person(String firstName, String lastName, List<String> addresses, List<String> phones) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.addresses = addresses;
    this.phones = phones;
}

public List<String> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<String> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

В моем примере вы можете видеть, что все сеттеры в Builder возвращают экземпляр Builder, чтобы вы могли легко связывать вызовы сеттеров. Это очень полезно.

Вы можете взглянуть на образец Строителя, представленный Джошуа Блохом.

Как я уже говорил, в сочетании с платформой проверки (см., Например, http://www.hibernate.org/subprojects/validator.html), это действительно мощно.

2 голосов
/ 25 февраля 2011

Вы должны взглянуть на шаблон builder .

1 голос
/ 25 февраля 2011

Инициализация в конструкторе, тем не менее, является самым простым и безопасным способом достижения неизменности, поскольку это единственный способ иметь поля final в вашем неизменяемом классе (который является стандартной идиомой и имеет полезные эффекты, особенно если ваш классиспользуется в многопоточной среде).Если в вашем классе много свойств, это может быть признаком того, что он пытается сделать слишком много.Подумайте о том, чтобы разделить его на более мелкие классы или выделить группы связанных свойств в классы составных свойств.

Использование Builder (с закрытым конструктором) возможно, однако ему все еще нужен способ установки свойств объекта.в процессе постройки.Таким образом, вы возвращаетесь к исходной дилемме параметров конструктора против доступа к закрытым членам.В последнем случае вы не можете объявить свойства строящегося объекта как final, что является ИМХО большим минусом.И в первом случае у вас все еще есть тот же длинный список параметров конструктора, который вы хотели бы избежать в первую очередь.Только сейчас с большим количеством дополнительного шаблонного кода поверх него.

0 голосов
/ 25 февраля 2011
  1. Есть final fields.
  2. Сделать класс "окончательным" классом, объявив final public class Person
  3. не используйте setXXX() методы для установки значения, так как оно изменит состояние переменной. однако getXXX() методы разрешены.
  4. Используйте приватный конструктор, чтобы вы могли устанавливать поля, используя сам конструктор.

Следуйте приведенным выше инструкциям для неизменного класса.

0 голосов
/ 25 февраля 2011

Используйте шаблон фабрики:

  • пусть Person будет интерфейсом только с функциями "get"
  • создайте PersonFactory с соответствующим API для построения объекта Person
  • PersonFactory создает объект, который реализует интерфейс Person и возвращает это
0 голосов
/ 25 февраля 2011

Вы можете создать «неизменяемый» компонент, сделав интерфейс только для чтения, а затем превратив реализацию в изменяемый компонент.Обход интерфейса не допускает мутации, но когда вы создаете объект и получаете реализацию, вы можете делать все что угодно:

public interface Person {
   String getFirstName();
   String getLastName();
   // ... other immutable methods ...
}

public class MutablePerson implements Person {
   // ... mutable functions, state goes here ...
}
0 голосов
/ 25 февраля 2011

Используйте final поля для всех ваших переменных экземпляра.Вы можете создать конструктор, если хотите, и не выставлять сеттеры, например,

public class Person {
   private final String firstName;
   ....

   public Person(String firstName, ... ) {
       this.firstName = firstName;
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...