Эта статья продолжает цикл материалов об объектно-ориентированном программировании. Если вы ещё не ознакомились с введением в ООП, следует это сделать.
Геттеры и сеттеры – ещё одна интересная концепция ООП, которая хорошо сочетается с инкапсуляцией.
Давайте снова рассмотрим объект-банковский счет.
Для того, чтобы получить доступ к свойству sum этого объекта, можно использовать два способа. Первый способ – это просто написать имя свойства:
account.sum = 100;
Это мы установили значение свойства sum в 100. А если нужно получить значение sum, то напишем так
var a = account.sum;
Второй способ – это использовать методы. В частности, их можно использовать для сокрытия прямого доступа к свойству sum, чем мы уже занимались:
account.setSum(100);
Метод setSum (будем считать, что мы его уже написали) принимает на вход число, делает различные проверки и присваивает его свойству sum. Теперь добавим ещё один метод для получения значения sum:
var a = account.getSum();
Вот мы и получили два метода. Один из них записывает значение в свойство sum, а другой сообщает нам это значение. Можно пользоваться! Это самые обычные методы.
Но кроме того, они обладают одной волшебной особенностью, и потому так и называются "магические методы".
Особенность в том, что одного приставка "get", а у другого "set". Это значит, что один из них геттер, а другой сеттер.
Как всегда, это лишь пример. Мы тут изучаем не языки, а концепции. В разных языках реализация геттеров и сеттеров может выглядеть по-разному. Но когда вы до неё доберетесь, вы уже будете знать, что это такое.
Геттер нужен для того, чтобы получить значение, а сеттер – для того, чтобы установить его. Ну да, мы это и так видим, а в чем волшебство-то? Мы же можем написать метод с любым немагическим именем, который будет точно так же устанавливать или получать значение любого свойства.
Волшебство в том, что имея такие магические методы, мы можем писать не
account.setSum(100);
а просто:
account.sum = 100;
То есть это будет выглядеть так, как будто мы обращаемся к свойству напрямую, но вместо этого незаметно будет работать метод-геттер или метод-сеттер.
При этом даже свойство, которое мы хотим менять, не обязано называться как метод, или даже вообще не обязано существовать. Вызываться будет метод, который сам разберется, с какими свойствами ему работать.
Как работает этот механизм? Когда мы пишем такую строчку:
account.sum = 100;
То транслятор поступает так:
"Программист хочет присвоить свойству sum число 100. Есть ли у объекта account метод "set" + "Sum", то есть setSum()? Если есть, то я не буду присваивать свойству sum число 100. Я вызову метод setSum(), передам в него число 100, и пусть он сам разбирается".
То же самое происходит, когда мы хотим получить значение свойства:
var a = account.sum;
"Программист хочет получить значение свойства sum. Есть ли у объекта account метод "get" + "Sum", то есть getSum()? Если есть, то я не буду отдавать значение свойства sum. Я вызову метод getSum(), и пусть он сам отдает что хочет."
Это всё работает, конечно же, только тогда, когда в языке есть поддержка геттеров и сеттеров. Если её нет, то ваши геттеры и сеттеры превратятся в тыкву, то есть будут самыми обычными методами.
Однако часто бывает так, что поддержку геттеров и сеттеров можно написать руками самостоятельно. Для этого в некоторых языках у объектов есть ещё более магические методы типа __get() и __set(), которые срабатывают вообще на любое обращение к любому существующему или несуществующему свойству объекта, и переписав или расширив эти методы, вы сможете сделать свою реализацию геттеров и сеттеров.
Зачем это вообще надо? Во-первых, для красоты и простоты, и для взаимодействия с другими объектами и программистами. Во-вторых, иногда так действительно надо, но сейчас это рано обсуждать, вы когда-нибудь сами столкнетесь с такой необходимостью.
Хочу, однако, предупредить. За внешней простотой геттеров и сеттеров кроется одна очень опасная опасность.
Давайте ещё раз посмотрим на банковский счет и его свойство sum. Если бы я использовал прямой доступ к sum, я бы мог бы всякий раз писать account.sum везде, где мне он потребуется. Так как свойство sum это просто число, то я бы получал это число и всё.
Теперь представьте, что есть геттер account.getSum(), который, например, залезает в базу данных, выполняет какой-то запрос и наконец возвращает мне значение sum.
Если я буду писать account.sum, для меня это будет выглядеть как обычное свойство, и таким образом создастся ложное ощущение, что я могу его использовать везде, где потребуется, как и раньше. Но теперь это не просто число, которое просто возвращается мне. Теперь каждый раз геттер будет выполнять запрос к базе. И сколько раз я это напишу – столько запросов и выполнится. И не дай бог написать такое в цикле на тысячи повторений. Понимаете, чем пахнет?
Такие проблемы часто случаются, особенно у новичков. Способы их решения есть, но в любом случае программист должен заранее продумывать, как и кто будет пользоваться его геттерами и сеттерами. И не забывать подробно их документировать.