Вызов сеттера из конструктора
Каковы "за" и "против" вызова мутатора из конструктора (если есть)
то есть:
public MyConstructor(int x) {
this.x = x;
}
против:
public MyConstructor(int x) {
setX(x);
}
public void setX(int x) {
this.x = x;
}
У вас есть предпочтения? (Это не домашняя работа, просто посмотрите на наш документ по стандартам кодирования, где говорится, что нужно всегда вызывать мутаторы при установке экземпляров var в конструкторе, и я не всегда так поступаю)
9 ответов
Лично я бы установил переменную напрямую в большинстве случаев.
Методы обычно предполагают, что экземпляр полностью сформирован к моменту их вызова. В частности, вызов переопределенного метода из конструктора - это рецепт сложного для понимания кода и трудно обнаруживаемых ошибок.
Сказав это, я часто пытаюсь сделать классы неизменяемыми в любом случае, и в этом случае не только нет никакого установщика, но вы все равно должны установить конечную переменную из конструктора (или инициализатора переменной):)
Там, где свойства имеют логику, установочная логика обычно проверяется и иногда меняет распространение на наблюдателей. Обычно я ожидаю, что параметры конструктора будут проверены явно в начале метода, и вы не захотите, чтобы какое-либо распространение изменений происходило до полного создания экземпляра.
Я соблюдаю два правила для конструкторов, чтобы минимизировать проблемы, поэтому я не буду использовать метод мутатора:
Конструкторы (не финальных классов) должны вызывать только финальные или частные методы. Если вы решили игнорировать это правило и позволить конструктору вызывать не окончательные / не приватные методы, то:
- эти методы и любые методы, которые они могут вызывать, должны быть осторожны, чтобы не предполагать, что экземпляр полностью инициализирован, и
- подклассы, которые переопределяют эти методы (подклассы, которые могут даже не знать, что конструктор суперкласса вызывает эти методы), не должны предполагать, что конструктор подкласса и конструкторы суперкласса были полностью выполнены. Эта проблема усугубляется, чем глубже в иерархии наследования суперкласс с "злым" конструктором.
Стоит ли весь этот дополнительный познавательный багаж? Вы можете разрешить исключение для простых мутаторов, которые присваивают значение только переменной экземпляра, так как это дает мало пользы, даже если кажется, что оно того не стоит.
[[@Jon Skeet упоминает об этом в своем ответе: "... В частности, вызов переопределенного метода из конструктора - это рецепт сложного для понимания кода и трудно обнаруживаемых ошибок". Но я не думаю, что последствия этой проблемы достаточно подчеркнуты. ]]
Конструкторы должны быть осторожны с утечкой this
до того, как экземпляр полностью инициализирован. В то время как предыдущее правило касалось методов внутри класса и подклассов, обращающихся к ivars, вы также должны быть осторожны с передачей (даже окончательного / частного) методов this
в другие классы и функции полезности перед this
полностью инициализирован. Чем больше не закрытых, переопределяемых методов, которые вызывает конструктор, тем выше риск утечки this
,
Некоторые ссылки на конструкторы, вызывающие не финальные, не приватные методы:
Вызывая любой public
, static
, non-final
методы внутри конструктора - решать вам, но лучше не вызывать такие методы внутри конструктора, потому что эти методы могут быть переопределены в подклассах, и фактически будет вызываться только переопределенная версия этих методов (если вы используете полиморфное поведение).
Например:
public class Foo {
public Foo() {
doSmth(); // If you use polymorphic behavior this method will never be invoked
}
public void doSmth() {
System.out.println("doSmth in super class");
}
public static void main(String[] args) {
new Bar(200);
}
}
class Bar extends Foo {
private int y;;
public Bar(int y) {
this.y = y;
}
@Override
public void doSmth() { // This version will be invoked even before Barr object initialized
System.out.println(y);
}
}
Это напечатает 0.
Для более подробной информации прочитайте главу Брюс Экель "Мышление на Java" "Полиморфизм"
Я предпочитаю устанавливать их непосредственно в конструкторе по нескольким причинам. Во-первых что-то вроде this.x = x;
это так же ясно, если не больше, чем вызов отдельного метода, который делает то же самое. Во-вторых, метод мог быть потенциально переопределен, если он не помечен как финальный, и вызов потенциально переопределенных методов из конструктора - это большая проблема в моей книге. В-третьих, большинство методов обычно предполагают, что объект уже завершен, когда они выполняются, а не на полпути к созданию. Хотя это не должно вызывать каких-либо проблем в этом простом случае, в более сложных случаях это может привести к серьезным незначительным ошибкам, которые могут выявляться годами.
Основным аргументом для использования сеттеров / получателей везде является то, что это означает, что вы можете переименовать поле, просто изменив его имя в 3 местах, его определение, методы получателя / установщика, и все должно скомпилироваться и быть в порядке. На мой взгляд, этот аргумент в настоящее время является недействительным, поскольку любая приличная современная среда IDE переименовывает все вхождения такого поля простым сочетанием клавиш.
Есть одна причина, почему наши стандарты кодирования требуют использования методов доступа (которые также могут быть частными). Если вы хотите быстро выяснить, какой код меняет поле, вы просто вызовете иерархию вызовов для установщика в Eclipse!
Это значительно экономит время в кодовой базе, которая достигла 2 миллионов строк кода, а также облегчает рефакторинг.
Если ваш сеттер не делает что-то более сложное, чем this.x = x
Я бы просто установить переменную напрямую. Параметр отображается непосредственно в вашу переменную экземпляра, и для меня это более показательно.
Я редко делаю это, и у меня никогда не было проблем из-за этого. Это также может иметь непредвиденные последствия, особенно если ваши сеттеры переопределены в подклассе или если ваши сеттеры запускают дополнительные вызовы методов, которые могут не подходить во время инициализации.
Это зависит от того, как вы относитесь к сеттерам. Если вы считаете, что класс может быть производным, вы можете разрешить такой вызов, чтобы можно было переопределить поведение в другом месте. В противном случае вы можете установить его как есть.
Разумеется, явный вызов вызывающих сеттеров также обеспечит способы введения дополнительных поведений во время настройки (например, применение защиты в случае необходимости).
Я тоже не против, и у меня нет сильных предпочтений [и не придерживаюсь некоторых строгих стандартов], просто используйте свое суждение. Но если вы идете с сеттерами, любой, кто переопределяет класс, должен знать, что объект может быть создан не полностью. При этом убедитесь, что никакой дополнительный код в установщиках не может публиковать this
ссылка.
Сеттеры могут быть полезны для стандартных проверок диапазона и т. Д., Поэтому не требуется никакого дополнительного кода для проверки ввода. Опять же, использование сеттеров - более грязный подход.
Использование поля имеет очевидные преимущества (например, уверенность в том, что тот или иной подкласс не сможет злонамеренно изменить поведение), и, как правило, предпочтительнее, esp. в библиотеках.
На самом деле c-tor является одним из 3 возможных способов создания объекта (сериализация + клон являются двумя другими), возможно, необходимо обработать некоторое переходное состояние вне c-tor, так что у вас все еще есть, что задуматься о полях против сеттеров. (Правка, есть еще одна половина пути: небезопасно, но это то, что использует сериализация)