1. Чисто плюсовый вариант, основанный на семантике ссылок.
class Foo {
Value field_;
public:
Value& field() { return field_; }
const Value& field() const { return field_; }
};
Использование:
Foo foo;
foo.field() = field_instance;
field_instance = foo.field();
Плюсы: краткость текста, близость к нотации "свойств", и возможность использования присвоения в каскаде (foo1.field() = foo2.field() = 2;).
Минусы: использование синтаксиса вызова функции слева от знака присваивания выглядит непривычно.
2. Способ в стиле Java.
class Foo {
Value field_;
public:
void setField(const Value& value) { field_ = value; }
const Value& getField() const { return field_; }
};
Использование:
Foo foo;
foo.setField(field_instance);
field_instance = foo.getField();
Плюсы: ясность и очевидность синтаксиса.
Минусы: многословность из приставок "get" и "set".
3. Cтиль Objective-C
class Foo {
Value field_;
public:
void setField(const Value& value) { field_ = value; }
const Value& field() const { return field_; }
};
Использование:
Foo foo;
foo.setField(field_instance);
field_instance = foo.field();
Плюсы: краткость текста при чтении (нет почти бессмысленной приставки "get") и очевидность при записи.
Минусы: я пока не нашел таковых.
Понятно, что все три способа имеют право жизнь. Но с точки зрения стилистики, стоит выбрать один и придерживаться его в рамках одного проекта.
Лично я почти всегда предпочитал способ #1, но после своего последнего поста я перешел на третий.
Использую #2
ОтветитьУдалитьИспользую #3 (он же используется в Qt)
ОтветитьУдалитьВообще думаю, что надо стараться избегать пропертей.
ОтветитьУдалитьЕсли какое-то поле меняется и анализируется извне, то зачем оно вообще в этом классе?
Или может быть имеет смысл анализ и изменение поля разместить в соответствующих методах этого класса и отказаться вообще от проперти?
То есть предоставить к переменной только косвенный доступ, через соответствующую логику. ИМХО было бы логично.
PS: Я сам не всегда этому следую... :)
Есть еще один вариант:
class Foo {
Value field_;
public:
Value field() const { return field_; }
void field(const Value &f) { field_ = f; }
};
То есть те же геттеры/сеттеры только без get/set :) В нашем проекте часто используется. :(
"ясность и очевидность синтаксиса" - это самое важное в стилистике кода, ИМХО. Поэтому №2
ОтветитьУдалить@Андрей Валяев: А передача по значению, а не по ссылке в getter - это специально? Может дорого обойтить.
ОтветитьУдалить2Александр: По ссылке чревато ошибками... Это ж getter... Он должен вернуть значение, а не ссылку на мембера. Которую, кроме того, необходимо константить, дабы избежать злоупотреблений.
ОтветитьУдалитьКроме того существует RVO...
Писать в данном контексте ссылку - это однозначная преждевременная оптимизация. Если профайлер покажет узкое место - можно и поставить, не велика проблема. Но по значению как-то вернее ИМХО.
Из
Value getter() const;
const Value &getter() const;
Я выбираю первое - оно короче и понятнее. :)
Кроме того я сильно сомневаюсь, что второе быстрее... Может быть быстрее, а может быть и одинаково... от компилятора зависит.
Сам пользуюсь №2, но по прочтении, думаю, что №3 лучше.
ОтветитьУдалитьВообще конечно не все так однозначно... В этом и прелесть программирования.
ОтветитьУдалитьМожет случиться так, что по значению передавать нельзя.
Но ИМХО лучше вообще избегать геттеров и сеттеров. Если класс не контролирует и не отвечает за содержимое переменной, то какой смысл ее защищать? public: Value field; И дело с концом.
Или всетаки возложить на класс какую-то ответственность, которая автоматически избавит нас от геттеров/сеттеров.
А если Value имеет методы, то их не получится вызвать, что возможно в способе №1
ОтветитьУдалитьЕсли поле доступно на запись извне, то нет смысла во всех этих геттерах/сеттерах.
ОтветитьУдалитьПеренести его в public и выкинуть все эти недофункции нафиг.
Код будет понятнее, короче и проще любого из вышеприведённых вариантов.
@Oehgt:
ОтветитьУдалитьА если я в один прекрасный день захочу во время присваивания еще что-то делать?
Например писать в журнал?
По всей программе трассировки добавлять?
О_о я прозрел :) Никогда не думал, что стиль #3 называется стилем Objectiv-C. Для себя его определял как QT-подобный стиль.
ОтветитьУдалитьЯ придерживаюсь стилей #2 и #3 - смотря где как и зачем. Вообще, считаю, что проект должен обладать своим Code Convention, который составляется рабочей группой программистов. Всегда можно найти компромис в стилистике :)
@Stas:
ОтветитьУдалитьВ этот прекрасный день произойдёт "рефакторинг", поле перейдёт в закрытую часть класса, а все обращения к ней - заменены на любимый setter. При этом, все изменения по всем проектам можно сделать не задействуя головной мозг вообще.
И не раньше.
А то, блин, много любителей написать код посложнее, да потом время на отладку потратить (и часто не своё время!), а этот "один прекрасный день, когда я захочу ХХХ сделать" за время жизни проекта так и не наступил.
@Qehgt:
ОтветитьУдалитьВы это всерьёз? :) Нет, я конечно понимаю, что легче в один прекрасный день пройтись по нескольким тысячам строк кода, выискивая все места, где вы присваивали/считывали значение члена и расставить get/set (и хорошо, если это в рамках только одного проекта, ибо если это во внешней библиотеке, то можно сразу вешаться), чем сразу написать по-человечески, но всё же )
По теме: предпочитаю стиль №3, ибо считаю его наиболее красивым и лаконичным. Но в некоторых случаях приходится использовать стиль №1, хотя стараюсь всячески этого избегать.
@Oehgt:
ОтветитьУдалитьТо есть вместо того, чтобы сначала написать один маленький setter(который я вообще макросом для начала запузыриваю), мне надо будет по всему проекту менять обращения к переменной на вызов set метода и это сэкономит мне время? Или вы предлагаете перегрузить оператор присваивания для объекта? А если POD тип? А если мне именно на этом уровне иерархии нужно добавить отладку?
@alexanius:
ОтветитьУдалитьВ чём проблема-то? Что такое насколько тысяч строк для обычного проекта? Такие вещи при рефакторинге происходят постоянно - интерфейс там изменился/добавился/разделился, сопутствующие классы поэтому требуется изменить и т.п. Изменение исходников при этом - тривиальнейшая операция, если у вас почему-то это сложности вызывает, значит на каком-то уровне вы что-то не так делаете.
@Stas:
Да, так как удаление или добавление setter'ов - быстрая дешёвая операция.
ps: уважаемые комментаторы как-то забыли причину появления этого топика, а именно предыдущее сообщение в этом блоге, где автор показывает, к чему приводит увлечение getter'ами и setter'ами на пустом месте.
@Qehgt:
ОтветитьУдалитьЗатруднения проявляются в самой необходимости выполнения лишней работы тогда, когда этого можно было избежать в самом начале. И всё из-за странного желания пойти вразрез с одной из основных концепций ООП без каких-либо на то оснований. Код при написании get/set не усложняется вообще, и, как минимум, не усложняет отладку.
А предыдущее сообщение показывает, к чему может привести невнимательность и использование не очевидного синтаксиса при написании кода. Геттеры и сеттеры как концепция там вообще ни при чём.
>Затруднения проявляются в самой необходимости выполнения лишней работы тогда, когда этого можно было избежать в самом начале.
ОтветитьУдалитьО, какая верная мысль. Внимание, вопрос - чем полный доступ на чтение/запись к закрытому полю отличается от public доступа к этому полю? Тем, что мы замусорили описание класса дополнительными методами и прокачали скилл отладки? Как это соотносится со стремлением избегать лишней работы?
>И всё из-за странного желания пойти вразрез с одной из основных концепций ООП без каких-либо на то оснований.
О, да. Сначала закроем поле, чтобы не забывать про инкапсуляцию, а затем дадим полный доступ к этому полю, так как иначе работать невозможно. Главное, что всё концептуально :)
@Qehgt:
ОтветитьУдалитьОтличается тем, что доступ не полный, тем, что мы скрываем детали реализации класса и не даём возможности случайно поменять значение члена. Особенно актуально в случаях с указателем на объект. А скилл отладки прокачивается, когда внезапно мы получаем ошибку сегментации, что ещё не так страшно, или получаем какое-то левое значение, которое вообще может остаться незамеченным.
В большинстве случаев полный доступ к полю давать не нужно и даже вредно. Профит от концептуальности выше чем профит от времени, как-бы сэкономленном на написании 2 функций.
>Отличается тем, что доступ не полный
ОтветитьУдалитьЯ не про "что такое getters and setters вообще, и что ими можно делать", а про данный конкретный вариант использования, который здесь автор рассматривает.
>Профит от концептуальности выше
Ох, грустно-то как всё. Вы серъёзно считаете, что getter+setter (в том виде, как предлагает автор) это и есть инкапсуляция, а не прямое её нарушение?
Хм, извиняюсь, я не совсем правильно понял Ваш первый комментарий. Да, в данном конкретном примере оно, конечно, ничем не отличается с точки зрения прав доступа, но на мой взгляд всё равно предпочтительней чем просто публичный доступ хотя бы по тем причинам, которые указал @Stas. Но тут уже, видимо, дело личных предпочтений.
ОтветитьУдалитьСобственно бывают транспортные структуры (без логики), и наверное никому не придет в голову в насаждать в таких структурах проперти методы. :)
ОтветитьУдалитьЧто касается отладки - эдак можно дойти до того, что на каждый чих делать специальный прокси метод, чтобы максимально замусорить ими код и увеличить потенциальное количество ошибок. И все ради отладки, которая может быть когда нибудь понадобиться... :) При большом количестве буков в тексте - это более вероятно.
ИМХО - лучше написать меньше буков, меньше придется отлаживать. Отладка совершенно не аргумент в пользу геттеров/сеттеров.
Еще есть принцип единой ответственности. :)
Кто нибудь может словами объяснить какой смысл существования метода? :)
void setField(const Value& value)
{
field_ = value;
}
>Еще есть принцип единой ответственности. :)
ОтветитьУдалить>Кто нибудь может словами объяснить какой смысл >существования метода? :)
>void setField(const Value& value)
>{
>field_ = value;
>}
В этом методе смысла не вижу. Зато, если добавить проверку, например, на диапазон разрешенных значений для field_ и возвращать какой-то результат, то смысл может появиться. :)
Собственно в этом и заключается контраргумент против глупых пропертей.
ОтветитьУдалитьА если добавить проверку - то и название по идее надо менять, потому что он станет уже не просто setField а checkAnsSetField. :)
Но лучше конечно что нибудь более глубокомысленное. А то получается дублирование между кодом и именем. :)
TrySetField :)
ОтветитьУдалитьGetter+setter, даже в том примере, который приводит автор не является нарушением инкапсуляции, потому как принцип инкапсуляции(базовый принцип ООП) заключается в сокрытии реализации объекта от пользователя и работа с ним через интерфейс(спецификацию).Защита данных означает не то, что их нельзя модифицировать, а то, что с ними можно работать лишь используя методы содержащего их объекта. Что дает возможность варьировать его поведение(не изменяя сам интерфейс). Запись же напрямую, это и есть прямое нарушение принципа инкапсуляции, т.к есть объект, кто-то там снаружи ковыряется в его данных, а он ни сном не духом.
ОтветитьУдалитьЛучше уж тогда вообще вынести эти данные.Класс ведь это данные + методы для работы с ними, не так ли?
А уж как удобно будет потом этот код отлаживать, я вообще молчу -
ни проверку, ни трассировку не добавить и вообще
эти обращения отслеживать будет гораздо труднее.
Примеры нарушением не являются, но сами по себе явно избыточны. Пока не известно какую задачу должен решать этот класс. :)
ОтветитьУдалитьВместо Foo foo вообще можно написать Value foo и будет счастье. :)
А по поводу возможностей трассировки я уже высказался.
ОтветитьУдалитьТрассировка не является смыслом существования программы и поэтому не является достаточным основанием для создания лишних программных сущностей.
Value foo не нуждается во внутренней трассировке. :)
Смотря в какой программе, я бы не говорил столь категорично. К тому же кроме трассировки есть журналирование, к примеру переходов между состояниями у системы. Вот вы говорите Value foo не нуждается в трассировке. Может быть, сегодня.
ОтветитьУдалитьА вот когда мы напишем программу, в коде которой из разных потоков, из сотни мест происходит к ней обращение, такая необходимость может появиться. Может понабиться делать какое-нибудь промежуточное преобразование, проверку.
И что значит, менять теперь код по всей программе, модифицировать эти обращения?
Потому, что вначале это типа было избыточно..
Чем добавление примитивного трехстрочного метода
замусоривает код? У меня кстати проперти определяются макросом в одну строку.
Отлично читается и уж багов никаких точно не привносит.
void SetField(const Value& value) {
ОтветитьУдалитьm_Field = value;
}
Value GetField() const {
return m_Field;
}
Использую когда нужно и читать, и задавать данные. По причине понятности. В геттере передаю не ссылку, а копию.
Value Field() const {
return m_Field;
}
Использую, когда нужно только читать, и запись в это поле в обозримом будущем не предвидится.
Вообще, геттеры и сеттеры рассматриваю как полноценные методы классов, а не как некие "свойства". Кроме того, считаю, что сеттеров в коде должно быть как можно меньше.
Публичный доступ к мемберам классов открывать неправильно.
2Stas: Если у тебя по всей программе идет обращение к одному полю одного класса - у тебя проблема, слишком жесткая связанность.
ОтветитьУдалитьЕсть мнение что даже в классе нужно использовать доступ к переменным через методы... Когда у нас нету ни одного прямого доступа к переменной - у нас конечно есть возможность оттрассировать ее через метод доступа.
Но обычно в методах класса никто такой ерундой не заморачивается. Прямой доступ понятнее. Так что трассировка всеравно не полная.
Собственно ты трассируешь не использование конкретной переменной - а использование класса. Тогда тебе нет смысла параноидально строить сеттеры возле каждой переменной, следуй за логикой приложения.
Сильно удивился, не найдя способа аксессоров в «стиле Smalltalk». Хорошо, что уже в третьем комментарии Андрей Валяев про них сообщил. Странно также, что в Objective-C, один из корней которого уходит в Smalltalk, используется не этот стиль.
ОтветитьУдалитьЕсли кому интересно, в Smalltalk сигнатуры геттера и сеттера — «field» и «field:» соответственно (двоеточие обозначает место для передачи аргумента, т. е. у геттера нет аргументов, а у сеттера есть один).
В нотации Java-аксессоров не только getField, то такжe isField и hasField, что тоже способствует читабельности синтаксиса.
ОтветитьУдалить