Существует ли стандартная практика Java при использовании шаблона построителя, чтобы гарантировать, что переменная-член установлена не более одного раза. Мне нужно убедиться, что сеттер называется 0 или 1 раз, но не больше. Я хотел бы бросить RuntimeException
какого-то типа, но я беспокоюсь о проблемах синхронизации, а также о лучших практиках в этой области.Задайте значение не более одного раза с шаблоном построителя
ответ
Нет ничего с поднятием исключения, если пользователь вызывает метод незаконным способом, как вы описываете, но это не очень элегантно. Идея шаблона построителя заключается в том, чтобы позволить пользователям писать свободно доступные, понятные определения объектов, а безопасность во время компиляции - большая часть этого. Если пользователи не могут быть уверены в том, что строитель будет успешным, даже если он скомпилируется, вы вводите дополнительную сложность, которую пользователи теперь должны понимать и учитывать.
Есть несколько способов сделать то, что вы описываете, позволяет исследовать их:
Просто позволяют пользователям делать то, что они хотят
Одна хорошая вещь о строителей, они могут позвольте вам построить несколько разных объектов из одного и того же строителя:
List<Person> jonesFamily = new ArrayList<>(); Person.Builder builder = new Person.Builder().setLastName("Jones"); for(String firstName : jonesFamilyFirstNames) { family.add(builder.setFirstName(firstName).build()); }
Я предполагаю, что у вас есть веская причина для запрета но я был бы упущен, если бы не вызвал этот полезный трюк.Возможно, вам не нужно ограничивать это в первую очередь.
возбудит исключение
Вы предлагаете повысить исключение, и это, конечно, работать. Как я уже сказал, я не думаю, что это самое элегантное решение, но вот одна реализация (с использованием Guava «s Preconditions, для дополнительного удобства чтения):
public class Builder { private Object optionalObj = null; // ... public Builder setObject(Object setOnce) { checkState(optionalObj == null, "Don't call setObject() more than once"); optionalObj = setOnce; } // ... }
Это поднимает
IllegalStateException
, так что вы можете просто позвонитьthrow new IllegalStateException()
если вы не используете Guava (вы должны быть ... :)). Предполагая, что вы не передаете объекты-конструкторы вокруг потоков, у вас не должно быть проблем с синхронизацией. Если да, то вы должны подумать, почему вам нужен один и тот же строитель в разных потоках - это почти наверняка анти-шаблон.Не предоставляйте метод на всех
Это самый чистый, ясный способ запретить пользователю вызова метода вы не хотите, чтобы они - не дают его в первом место. Вместо этого переопределите конструктор строителя или метод
build()
, чтобы они могли необязательно передать значение в это время, но ни в какое другое время. Таким образом, вы четко гарантируете, что значение может быть установлено не более одного раза на построенный объект.public class Builder { // ... public Obj build() { ... } public Obj build(Object onceOnly) { ... } }
Используйте различные типы разоблачить некоторые методы
Я на самом деле не сделали этого, и это может быть более запутанной, чем она стоит (в частности, вы, вероятно, необходимо использовать self-bounding generic для методов в
Builder
), но это пришло мне в голову, когда я писал, и может быть очень явным для определенных случаев использования. Имейте свой ограниченный метод в подклассе строителя, и этот метод возвращает родительский тип, поэтому предотвращает повторный вызов вызывающего метода. Примером может помочь, если это не имело смысла:public class Builder { // contains regular builder methods } public class UnsetBuilder extends Builder { public Builder setValue(Object obj) { ... } } // the builder constructor actually returns an UnsetBuilder public static UnsetBuilder builder() { ... }
Тогда мы можем назвать что-то вроде:
builder().setValue("A").build();
Но мы получим ошибку компиляции, если мы пытались позвонить:
builder().setValue("A").setValue("B").build();
setValue()
потому, что возвращаетBuilder
, в котором отсутствует методsetValue()
, поэтому предотвращение второй случай. Это было бы сложно понять точно (что, если пользователь переводитBuilder
обратно вUnsetBuilder
?), Но с некоторыми усилиями сделает то, что вы ищете.
Object objectToSet;
boolean isObjectSet = false;
void setObject(Object object) throws RuntimeException {
if(!isObjectSet) {
objectToSet=object;
isObjectSet=true;
} else {
throw new RuntimeException();
}
}
Это стандартная практика.
Это, однако, не удовлетворяет требованиям синхронизации OPs. – rompetroll
@rompetroll Я думаю, что он означает безопасность потоков при выбросе исключения. Это было бы прекрасно, так как он мог поймать исключение. В противном случае просто добавьте синхронизацию до void ... – BAR
Вы можете определить свою переменную как final
.
final MyClass object;
EDIT: Если он устанавливает более чем один раз вы получите ошибку компиляции.
Или как упомянуто другими, вы можете проверить состояние переменной в инкубаторе и безопасности потоков, объявить метод установки в качестве synchronized
.
public synchronized void setMyObject(Object object)
{
if (this.myObject == null)
{
this.myObject = object;
} else {
throw new RuntimeException();
}
}
Используйте метод setter даже в конструкторе или в другом месте.
Примечание: Использование методов synchronized
может привести к нестабильности производительности при огромной обработке.
Вы можете использовать шаблон Builder, обозначенный как Эффективная Java. Это не совсем соответствует вашим требованиям, но я думаю, что это должно соответствовать вашим потребностям. Дай мне знать.
class Foo {
private final Object objectToSet;
private Foo(Foo.Builder builder) {
objectToSet = builder.getObjectToSet();
}
public static class Builder {
private Object objectToSet;
public Builder() { }
private Object getObjectToSet() {
return objectToSet;
}
public Builder objectToSet(Object objectToSet) {
this.objectToSet = objectToSet;
return this;
}
public Foo build() {
return new Foo(this);
}
}
}
Тогда позже:
Object someObject = 10;
Foo foo = new Foo.Builder()
.objectToSet(someObject)
.build();
// A Foo's `objectToSet` can only be set once
Это позволяет определить класс (этот случай Foo
), который имеет свойства, которые могут быть установлены только один раз. В качестве бонуса вы можете сделать Foo
действительно неизменным.
OP уже использует шаблон строителя.Он хочет предотвратить случай, когда кто-то звонит, чтобы использовать ваш пример, 'new Foo.Builder(). ObjectToSet (someObject) .objectToSet (someOtherObject) .build();'. – dimo414
Что мешает нам делать builder.objectToSet (foo) .objectToSet (bar)? – rompetroll
@ dimo414. Открыто для интерпретации, что именно подразумевается под «шаблоном построителя» и «убедитесь, что сеттер вызван 0 или 1 раз» - итоговый конечный объект из 'Builder.build()' infact не может иметь сеттер потому что у него нет одного определенного. В конечном счете до OP для интерпретации. – jdphenix
Вторая, вероятно, лучшее решение моей проблемы. Синхронизация не является строгим требованием, было просто приятно иметь. Никогда не знаете, как код будет использоваться, когда он покинет мой стол. Вероятно, можно просто добавить комментарий «не потокобезопасный» в javadocs. Первоначальная проблема, которую я пыталась решить, заключалась в анализе xml в определенной структуре класса; сама структура xml может принимать различные формы. Я зацикливал и заполнял класс с помощью построителя. Единственное требование, которое существует, состоит в том, что если существует определенное, то оно может быть установлено только один раз. – bigfy
Обеспечение строителей поточно-безопасными является высоким требованием, и один я не думаю, что вы должны в целом обеспечить. Конструкторы 'Immutable *' Guava выглядят небезопасными потоками (например, 'ImmutableList.Builder' поддерживается массивом, который можно изменить). По-моему, любой пользователь, который пытается использовать произвольный объект-строитель по потокам, делает ошибку. – dimo414
Другим (личным фаворитом) вариантом № 4 будет шаблон _Marco Castigliego_ [Step Builder] (http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html), где по существу «Builder» '' s методы определены в интерфейсах, каждый интерфейс представляет собой «шаг» в процессе сборки, и каждый метод возвращает последующий интерфейс «step». Сам «Builder» реализует эти интерфейсы, тем самым раскрывая данный поднабор его общей функциональности при каждом вызове. Общие методы (например, 'reset()') все еще могут быть доступны, помещая их в базовый интерфейс, расширенный другими. – Uux