Средства доступа и модификаторы (также известные как сеттеры и геттеры) полезны по трем основным причинам:
- Они ограничивают доступ к переменным.
- Например, переменная может быть доступна, но не может быть изменена.
- Они проверяют параметры.
- Они могут вызвать некоторые побочные эффекты.
Университеты, онлайн-курсы, учебные пособия, статьи в блогах и примеры кода в Интернете - все подчеркивают важность методов доступа и модификаторов, в настоящее время они почти чувствуют себя необходимыми для кода. Таким образом, их можно найти, даже если они не предоставляют никаких дополнительных значений, как в приведенном ниже коде.
public class Cat {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
Тем не менее, очень часто можно найти более полезные модификаторы, которые действительно проверяют параметры и выдают исключение или возвращают логическое значение, если введен неверный ввод, что-то вроде этого:
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
Но даже в этом случае я почти никогда не вижу, чтобы модификаторы вызывались из конструктора, поэтому наиболее распространенный пример простого класса, с которым я сталкиваюсь, это:
public class Cat {
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Но можно подумать, что этот второй подход намного безопаснее:
public class Cat {
private int age;
public Cat(int age) {
//Use the modifier instead of assigning the value directly.
setAge(age);
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Видите ли вы похожую модель в своем опыте или мне просто не повезло? И если вы это сделаете, то как вы думаете, что вызывает это? Есть ли очевидный недостаток в использовании модификаторов из конструкторов или они просто считаются более безопасными? Это что-то еще?
источник
Ответы:
Очень общие философские рассуждения
Как правило, мы просим, чтобы конструктор предоставил (в качестве постусловий) некоторые гарантии о состоянии построенного объекта.
Как правило, мы также ожидаем, что методы экземпляра могут предполагать (в качестве предварительных условий), что эти гарантии уже выполняются, когда они вызываются, и им нужно только следить за тем, чтобы не нарушить их.
Вызов метода экземпляра из конструктора означает, что некоторые или все эти гарантии могут быть еще не установлены, что затрудняет рассуждение о том, выполнены ли предварительные условия метода экземпляра. Даже если вы понимаете это правильно, это может быть очень хрупким, например. переупорядочение вызовов метода экземпляра или других операций.
Языки также различаются по тому, как они разрешают вызовы методов экземпляра, которые наследуются от базовых классов / переопределяются подклассами, пока конструктор все еще работает. Это добавляет еще один уровень сложности.
Конкретные примеры
Ваш собственный пример того, как вы думаете, это должно выглядеть, сам по себе неверен:
это не проверяет возвращаемое значение из
setAge
. Видимо, вызов сеттера не является гарантией правильности в конце концов.Очень простые ошибки, например, в зависимости от порядка инициализации, такие как:
где моя временная регистрация сломала все. Упс!
Существуют также языки, такие как C ++, где вызов сеттера из конструктора означает потерянную инициализацию по умолчанию (чего, по крайней мере, для некоторых переменных-членов стоит избегать)
Простое предложение
Это правда, что большая часть кода написана не так, но если вы хотите, чтобы ваш конструктор был чистым и предсказуемым, и все же повторно использовать логику до и после условия, лучшим решением будет:
или еще лучше, если это возможно: закодируйте ограничение в типе свойства и сделайте так, чтобы оно проверяло свое собственное значение при присваивании:
И, наконец, для полной полноты, самоутверждающаяся оболочка в C ++. Обратите внимание, что хотя он все еще выполняет утомительную проверку, поскольку этот класс больше ничего не делает , его относительно легко проверить.
Хорошо, это не совсем завершено, я пропустил различные конструкторы и назначения копирования и перемещения.
источник
Когда объект создается, он по определению не полностью сформирован. Рассмотрим, как вы установили сеттер:
Что, если часть проверки
setAge()
включала проверкуage
свойства, чтобы гарантировать, что объект может только возрастать? Это условие может выглядеть так:Это кажется довольно невинным изменением, поскольку кошки могут перемещаться во времени только в одном направлении. Единственная проблема заключается в том, что вы не можете использовать его для установки начального значения,
this.age
поскольку оно предполагает, что оноthis.age
уже имеет допустимое значение.Отказ от сеттеров в конструкторах проясняет, что единственное , что делает конструктор, это устанавливает переменную экземпляра и пропускает любое другое поведение, которое происходит в сеттере.
источник
Здесь уже есть несколько хороших ответов, но никто до сих пор не заметил, что вы спрашиваете здесь две вещи в одной, что вызывает некоторую путаницу. ИМХО ваш пост лучше всего рассматривать как два отдельных вопроса:
если в установщике есть проверка ограничения, не должно ли оно быть в конструкторе класса, чтобы убедиться, что оно не может быть обойдено?
почему бы не вызвать сеттер напрямую для этого из конструктора?
Ответ на первые вопросы явно «да». Конструктор, если он не выдает исключение, должен оставить объект в допустимом состоянии, и если определенные значения запрещены для определенных атрибутов, то нет никакого смысла позволять ctor обойти это.
Однако ответом на второй вопрос обычно является «нет», если вы не принимаете меры, чтобы избежать переопределения метода установки в производном классе. Следовательно, лучшая альтернатива - реализовать проверку ограничения в частном методе, который можно повторно использовать как из установщика, так и из конструктора. Я не собираюсь здесь повторять пример @Useless, который показывает именно этот дизайн.
источник
Вот немного глупого кода Java, который демонстрирует виды проблем, с которыми вы можете столкнуться, используя неконечные методы в своем конструкторе:
Когда я запускаю это, я получаю NPE в конструкторе NextProblem. Конечно, это тривиальный пример, но если у вас несколько уровней наследования, все может быстро усложниться.
Я думаю, что основная причина, по которой это не стало распространенным, заключается в том, что он делает код немного сложнее для понимания. Лично у меня почти никогда нет методов установки, и мои переменные-члены почти неизменно (каламбур) конечны. Из-за этого значения должны быть установлены в конструкторе. Если вы используете неизменяемые объекты (и для этого есть много веских причин), вопрос будет спорным.
В любом случае, хорошая цель - повторно использовать валидацию или другую логику, и вы можете поместить ее в статический метод и вызвать его как из конструктора, так и из установщика.
источник
name
поле).this
ссылку на какой-либо другой метод, потому что вы не можете быть уверены, что он полностью инициализирован.Это зависит!
Если вы выполняете простую проверку или запускаете в установщике непослушные побочные эффекты, которые нужно нажимать каждый раз при установке свойства: используйте установщик. Альтернатива, как правило, состоит в том, чтобы извлечь логику установщика из установщика и вызвать новый метод, за которым следует фактический набор как из установщика, так и из конструктора - что фактически совпадает с использованием установщика! (За исключением того, что теперь вы поддерживаете свой, по общему признанию, маленький двухстрочный сеттер в двух местах.)
Однако , если вам нужно выполнить сложные операции, чтобы расположить объект до того, как конкретный установщик (или два!) Может успешно выполняться, не используйте установщик.
И в любом случае, есть тестовые случаи . Независимо от того, по какому маршруту вы идете, если у вас есть юнит-тесты, у вас будет хороший шанс выявить подводные камни, связанные с любым из вариантов.
Я также добавлю, что если мои воспоминания об этом далеком прошлом хотя бы отдаленно точны, это тот тип рассуждений, которым меня учили и в колледже. И это вовсе не противоречит успешным проектам, над которыми мне довелось работать.
Если бы мне пришлось угадывать, я пропустил заметку «чистый код» в какой-то момент, который выявил некоторые типичные ошибки, которые могут возникнуть при использовании сеттеров в конструкторе. Но, со своей стороны, я не был жертвой таких ошибок ...
Итак, я бы сказал, что это конкретное решение не должно быть стремительным и догматичным.
источник