Установите значение не более одного раза с шаблоном построителя
Существует ли стандартная практика в Java при использовании шаблона компоновщика, чтобы гарантировать, что переменная-член устанавливается не более одного раза. Мне нужно убедиться, что сеттер вызывается 0 или 1 раз, но никогда больше. Я хотел бы бросить RuntimeException
некоторого типа, но я обеспокоен вопросами синхронизации, а также передовым опытом в этой области.
4 ответа
Нет ничего плохого в том, чтобы вызывать исключение, если пользователь вызывает метод незаконным способом, как вы описали, но это не очень элегантно. Идея шаблона построения состоит в том, чтобы позволить пользователям писать беглые, читаемые определения объектов, и безопасность во время компиляции является большой частью этого. Если пользователи не могут быть уверены, что сборщик преуспеет, даже если он скомпилируется, вы вводите дополнительные сложности, которые пользователи теперь должны понимать и учитывать.
Есть несколько способов выполнить то, что вы описываете, давайте рассмотрим их:
Просто дайте пользователям делать то, что они хотят
Хорошая особенность компоновщиков в том, что они позволяют вам создавать несколько разных объектов из одного компоновщика:
List<Person> jonesFamily = new ArrayList<>(); Person.Builder builder = new Person.Builder().setLastName("Jones"); for(String firstName : jonesFamilyFirstNames) { family.add(builder.setFirstName(firstName).build()); }
Я полагаю, у вас есть веская причина запретить такое поведение, но я был бы упущен, если бы не назвал этот полезный трюк. Может быть, вам не нужно ограничивать это в первую очередь.
Поднять исключение
Вы предлагаете поднять исключение, и это, безусловно, сработает. Как я уже сказал, я не думаю, что это самое элегантное решение, но вот одна реализация (с использованием предварительных условий Guava для дополнительной читабельности):
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()
если вы не используете гуаву (вы должны быть...:)). Предполагая, что вы не передаете объекты компоновщика между потоками, у вас не должно быть проблем с синхронизацией. Если да, то вам следует подумать, зачем вам нужен один и тот же компоновщик в разных потоках - это почти наверняка анти-паттерн.Не предоставляйте метод вообще
Это самый чистый и ясный способ запретить пользователю вызывать метод, который вам не нужен - не предоставляйте его в первую очередь. Вместо этого переопределите либо конструктор строителя, либо
build()
метод, чтобы они могли по желанию передать значение в это время, но не в другое время. Таким образом, вы четко гарантируете, что значение может быть установлено не более одного раза для каждого построенного объекта.public class Builder { // ... public Obj build() { ... } public Obj build(Object onceOnly) { ... } }
Используйте разные типы для предоставления определенных методов
На самом деле я этого не делал, и это может быть более запутанным, чем оно того стоит (в частности, вам, вероятно, придется использовать самодостаточный родовой для методов в
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();
}
}
Это стандартная практика.
Вы можете использовать шаблон Builder, изложенный в Effective 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
действительно неизменный.
Вы можете определить свою переменную как final
,
final MyClass object;
РЕДАКТИРОВАТЬ: Если он устанавливает более одного раза, вы получите ошибку во время компиляции.
Или, как уже упоминалось, вы можете проверить состояние переменной в установщике и для обеспечения безопасности потока объявить метод установщика как synchronized
,
public synchronized void setMyObject(Object object)
{
if (this.myObject == null)
{
this.myObject = object;
} else {
throw new RuntimeException();
}
}
Используйте метод setter даже в конструкторе или где-либо еще.
Примечание: использование synchronized
Методы могут привести к нестабильности производительности при огромной обработке.