зачем нужны getter и setter
Есть два типа свойств объекта.
Первый тип это свойства-данные (data properties). Мы уже знаем, как работать с ними. Все свойства, которые мы использовали до текущего момента, были свойствами-данными.
Второй тип свойств мы ещё не рассматривали. Это свойства-аксессоры (accessor properties). По своей сути это функции, которые используются для присвоения и получения значения, но во внешнем коде они выглядят как обычные свойства объекта.
Геттеры и сеттеры
Свойства-аксессоры представлены методами: «геттер» – для чтения и «сеттер» – для записи. При литеральном объявлении объекта они обозначаются get и set :
Геттер срабатывает, когда obj.propName читается, сеттер – когда значение присваивается.
Например, у нас есть объект user со свойствами name и surname :
Снаружи свойство-аксессор выглядит как обычное свойство. В этом и заключается смысл свойств-аксессоров. Мы не вызываем user.fullName как функцию, а читаем как обычное свойство: геттер выполнит всю работу за кулисами.
Давайте исправим это, добавив сеттер для user.fullName :
Дескрипторы свойств доступа
Дескрипторы свойств-аксессоров отличаются от «обычных» свойств-данных.
То есть, дескриптор аксессора может иметь:
Например, для создания аксессора fullName при помощи defineProperty мы можем передать дескриптор с использованием get и set :
Ещё раз заметим, что свойство объекта может быть либо свойством-аксессором (с методами get/set ), либо свойством-данным (со значением value ).
Умные геттеры/сеттеры
Геттеры/сеттеры можно использовать как обёртки над «реальными» значениями свойств, чтобы получить больше контроля над операциями с ними.
Использование для совместимости
У аксессоров есть интересная область применения – они позволяют в любой момент взять «обычное» свойство и изменить его поведение, поменяв на геттер и сеттер.
Давайте его сохраним.
Добавление геттера для age решит проблему:
Теперь старый код тоже работает, и у нас есть отличное дополнительное свойство!
Геттеры и сеттеры в Javascript
Javascript — очень изящный язык с кучей интересных возможностей. Большинство из этих возможностей скрыты одним неприятным фактором — Internet Explorer’ом и другим дерьмом, с которым нам приходится работать. Тем не менее, с приходом мобильных телефонов с актуальными браузерами и серверного JavaScript с нормальными движками эти возможности уже можно и нужно использовать прям сейчас. Но по привычке, даже при программировании для node.js мы стараемся писать так, чтобы оно работало в IE6+.
В этой статье я расскажу про интересный и не секретный способ указывать изящные геттеры и сеттеры и немножко покопаемся в исходниках Mootools. Частично это информация взята из статьи John Resig, частично лично мой опыт и эксперименты.
Стандартные геттеры
Что такое Геттеры и Сеттеры, надеюсь знают все. Обычно и кроссбраузерно это выглядит так:
Можно пойти дальше и написать более изящный вариант:
Нативные геттеры/сеттеры
Но есть более удобный способ, который работает во всех серверных движках и современных браузерах, а именно Firefox, Chrome, Safari3+, Opera9.5+ — задание сеттера и геттера для свойства так, чтобы продолжать обращатся к свойству, как свойству. У такого подхода есть несколько преимуществ:
1. Более изящная запись. Представим ORM:
2. Если апи, которое базируется на свойствах уже есть и его нельзя менять (а очень нужно).
Есть два способа задать такой геттер/сеттер:
Через объект:
Через методы __defineGetter__ и __defineSetter__:
Определяем поддержку браузером
Из этого можно получить лёгкий способ определения, поддерживает ли браузер геттеры или не поддерживает:
Как быть с наследованием?
Таким образом нашему target передадутся не значения родительского source, а функции-геттеры/сеттеры.
Что следует помнить
MooTools
Мутулз не поддерживает по-умолчанию такую возможность. И, хотя я уже предложил патч, мы можем с лёгкостью (слегка изменив исходники) заставить его понимать геттеры и сеттеры.
Итак, какая наша цель?
Более того, в классах унаследованных через Implements и Extends тоже должны работать геттеры и сеттеры родительского класса. Все наши действия будут происходить в файле [name: Class] внутри анонимной функции.
Во-первых, внутри функции, в самом верху, определим функцию, которая перезаписывает только геттеры и сеттеры. И хотя мы отказалась от устаревших браузеров — стоит застраховаться.
Конечно, если наш скрипт с такими геттерами попадёт в устаревший браузер, то он просто упадёт, но это страховка от того, чтобы кто-то случайно не взял этот файл и не прицепил его к себе на сайт, а потом недоумевал, что такое с ишаком.
Мы видим, что если __lookupGetter__ не поддерживается, то функция просто ничего не сделает.
Теперь заставляем работать getterы и setterы во время создания класса и наследования (Extends). Для этого:
Отдельным движением надо реализовать наследование геттеров и сеттеров от примесей (Implements). Для этого надо найти встроенные Мутаторы и добавить всего одну строку:
Все, теперь сеттеры и геттеры реализуются и мы с лёгкостью можем их наследовать и использовать. Наслаждайтесь)
Сеттер или геттер-ссылка — как сделать правильный выбор?
Контекст
Когда вы создаете класс, вам часто приходится реализовывать методы доступа к одному или нескольким атрибутам этого класса для того, чтобы можно было редактировать их.
Сделать это можно двумя способами:
С помощью сеттера, метода, который принимает новое значение атрибута в качестве аргумента.
С помощью геттера-ссылки (reference-getter), метода, который возвращает ссылку на сам атрибут.
Некоторые могут поспорить со мной, заявив что для этого есть и другие методы, но я остаюсь верен убеждению, что это всего лишь вариации сеттера или геттер-ссылки.
Сеттер
Сеттер по отношению к классу является интерфейсом только для записи (write-only). Вы указываете значение, и соответственно обновляете класс.
Зачастую он более или менее напрямую обновляет атрибут, копируя/перемещая(move) предоставленные вами данные.
Геттер-ссылка
Геттер-ссылка (reference-getter) представляет собой метод, который возвращает ссылку на атрибут, что дает непосредственный доступ к нему, в том числе предоставляя возможность его редактирования.
Этот метод особенно полезен для атрибутов-объектов для того, чтобы вы могли вызывать неконстантные методы непосредственно на них.
Какой метод выбрать?
Сделать выбор станет довольно просто, как только вы поймете разницу между ними.
Когда вам нужно пересоздать данные и поместить их на место уже существующих, вам пригодится сеттер. Он также подходит для редактирования простых данных (целые числа, значения чисел с плавающей запятой, указатели и т. д.) или если вам нужно подставить полностью новое значение. Еще один вариант использования сеттеров: когда вы намеренно хотите дать пользователю возможность только редактировать данные, но не читать.
Геттер-ссылка вам пригодится для редактирования данных атрибута (а не атрибута целиком). Часто нам необходимо отредактировать только часть атрибута или вызвать для них неконстантный метод.
Другими словами, сеттер заменяет атрибут, а геттер-ссылка редактирует атрибут.
Пример
Рассмотрим следующий код:
Затем мы реализуем небольшую функцию, которая изменяет размер коллекции, не удаляя никаких элементов. Элементы, выходящие за заданный размер, становятся невалидными.
Мы задаем значение m_max_size с помощью сеттера, так как это обычное целое число, значение которого меняется при изменении размера коллекции.
Мы могли бы редактировать валидность, используя более специфичный сеттер, например:
Однако, пожалуйста, помните, что писать сеттеры и для value и для validity является плохой практикой. Делать это в корзине данных с двумя атрибутами вряд ли будет правильным решением, потому что как только ваша реализация начнет расти, ваша кодовая база будет переполнена бесполезными аксессорами. Вам нужен полный доступ? Так и реализуйте полный доступ.
Заключение
Многие из вас посчитают эту тему тривиальной, но многие разработчики все еще путаются, когда следует использовать сеттер, а когда геттер-ссылку. Будьте внимательны.
Материал подготовлен в рамках специализации «C++ Developer».
Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем специализации — приглашаем на день открытых дверей онлайн. Регистрация здесь.
Публичные свойства, геттеры и сеттеры или магические методы?
Пример использования публичных свойств:
В данном примере bar является публичным свойством класса Foo. При таком подходе мы можем манипулировать данным свойством как угодно и хранить в нём любые данные.
Пример использования геттеров и сеттеров:
Пример использования магических геттеров и сеттеров:
В данном случае свойство bar не является публичным, однако в коде оно используется так, как если бы было публичным. Когда PHP не может найти соответствующего публичного свойства он вызывает соответствующий магический метод ( __get() для получения значения, __set() для установки значения). Данный подход может показаться золотой серединой, но у него есть существенный недостаток (см. недостатки ниже!). Следует также отметить, что __get() и __set() методы НЕ вызываются для публичных свойств, и вызываются в случае, если свойство помечено как protected или private и находится за пределами области видимости, или если свойство не определено.
Другие альтернативы?
До изучения PHP я использовал C#. В C# все свойства имеют методы доступа, но вам не нужно вызывать их как методы, вы можете манипулировать свойсвами напрямую и соответствующие методы будут вызываться магически. Это в некотором роде это похоже на магические методы __get() и __set() в PHP, однако свойства остаются определены и доступны. Это золотая середина и было бы очень хорошо увидеть аналогичную возможность в PHP.
Урок №115. Инкапсуляция, Геттеры и Сеттеры
Обновл. 13 Сен 2021 |
На предыдущем уроке мы узнали, что переменные-члены класса по умолчанию являются закрытыми. Новички, которые изучают объектно-ориентированное программирование, очень часто не понимают, почему всё обстоит именно так.
Зачем делать переменные-члены класса закрытыми?
В качестве ответа, воспользуемся аналогией. В современной жизни мы имеем доступ ко многим электронным устройствам. К телевизору есть пульт дистанционного управления, с помощью которого можно включать/выключать телевизор. Управление автомобилем позволяет в разы быстрее передвигаться между двумя точками. С помощью фотоаппарата можно делать снимки.
Все эти 3 вещи используют общий шаблон: они предоставляют вам простой интерфейс (кнопка, руль и т.д.) для выполнения определенного действия. Однако, то, как эти устройства фактически работают, скрыто от вас (как от пользователей). Для нажатия кнопки на пульте дистанционного управления вам не нужно знать, что выполняется «под капотом» пульта для взаимодействия с телевизором. Когда вы нажимаете на педаль газа в своем автомобиле, вам не нужно знать о том, как двигатель внутреннего сгорания приводит в движение колеса. Когда вы делаете снимок, вам не нужно знать, как датчики собирают свет в пиксельное изображение.
Такое разделение интерфейса и реализации чрезвычайно полезно, поскольку оно позволяет использовать объекты, без необходимости понимания их реализации. Это значительно снижает сложность использования этих устройств и значительно увеличивает их количество (устройства с которыми можно взаимодействовать).
По аналогичным причинам разделение реализации и интерфейса полезно и в программировании.
Инкапсуляция
В объектно-ориентированном программировании инкапсуляция (или «сокрытие информации») — это процесс скрытого хранения деталей реализации объекта. Пользователи обращаются к объекту через открытый интерфейс.
В языке C++ инкапсуляция реализована через спецификаторы доступа. Как правило, все переменные-члены класса являются закрытыми (скрывая детали реализации), а большинство методов являются открытыми (с открытым интерфейсом для пользователя). Хотя требование к пользователям использовать публичный интерфейс может показаться более обременительным, нежели просто открыть доступ к переменным-членам, но на самом деле это предоставляет большое количество полезных преимуществ, которые улучшают возможность повторного использования кода и его поддержку.
Преимущество №1: Инкапсулированные классы проще в использовании и уменьшают сложность ваших программ.
С полностью инкапсулированным классом вам нужно знать только то, какие методы являются доступными для использования, какие аргументы они принимают и какие значения возвращают. Не нужно знать, как класс реализован изнутри. Например, класс, содержащий список имен, может быть реализован с использованием динамического массива, строк C-style, std::array, std::vector, std::map, std::list или любой другой структуры данных. Для использования этого класса, вам не нужно знать детали его реализации. Это значительно снижает сложность ваших программ, а также уменьшает количество возможных ошибок. Это является ключевым преимуществом инкапсуляции.
Все классы Стандартной библиотеки C++ инкапсулированы. Представьте, насколько сложнее был бы процесс изучения языка C++, если бы вам нужно было знать реализацию std::string, std::vector или std::cout (и других объектов) для того, чтобы их использовать!
Преимущество №2: Инкапсулированные классы помогают защитить ваши данные и предотвращают их неправильное использование.
Глобальные переменные опасны, так как нет строгого контроля над тем, кто имеет к ним доступ и как их используют. Классы с открытыми членами имеют ту же проблему, только в меньших масштабах. Например, допустим, что нам нужно написать строковый класс. Мы могли бы начать со следующего: