Сокрытие данных и инкапсуляцияВ предыдущей части вы создали структуру, которая представляет в программах дробные числа. Почти сразу же стало очевидно, что одной из проблем этой структуры является полный доступ к ее полям из любого места в программе. Это позволяет перезаписывать значения числителя и знаменателя в любой момент, в обход всех правил, которые были ранее установлены для создания рациональных чисел. Это также противоречит сути нашего типа данных, который является абстрактным согласно установленному выше дизайну. Из того, что это абстрактный тип данных, следует, что пользователь не должен изменять эти поля напрямую. А это значит, что их нужно от пользователя как-то скрыть.
В С++ для этого есть специальный механизм [Stroustrup]. Этот механизм позволяет запретить прямое изменение полей структуры одним из двух способов. Первый способ заключается в добавлении специального слова
Обратите внимание на двоеточие после данного слова, это напоминает метки в С, которые можно использовать для возврата к нужной строке в программе. Метка
Второй способ достичь такого же эффекта – использовать ключевое слово
Оба подхода дают одинаковый результат, поэтому на практике можно использовать любой из них, но, как правило,
Независимо от того, какой метод будет выбран2, эффект будет один: программа из предыдущей главы перестанет работать, так как создать “хорошую” переменную типа
Особое внимание стоит обратить на ключевое слово
В С++ это слово приобретает новые свойства, которые легче будет понять на конкретном примере. Допустим, что этого слова там нет. Попробуем теперь создать новую переменную, используя эту функцию. Первый же вопрос, который встанет перед вами: как вызвать функцию, которая находится внутри какого-то класса? Мы знаем, что к именам структуры необходимо обращаться либо через оператор выбора (
Интуиция в этом случае оказалась верной. Авторы С++ не стали изобретать велосипед и сохранили привычный синтаксис языка C для этого случая. Но при этом код немного потерял в лаконичности: необходимо сначала создать ничем не инициализированный объект, а потом использовать функцию, чтобы изменить его значения. Более того, это даже вводит в заблуждение, потому что такой вызов функции не изменит саму переменную
Согласитесь, что такая запись выглядит странно. Ключевое слово
Правда, всплывает другой нюанс. Согласно правилам языка статические функции ничего не знают об объектах того типа, которому принадлежат. Они знают только внутреннюю структуру самого типа данных. Как следствие, форма записи
Здесь указывается на то, что функцию под таким именем нужно искать где-то внутри класса Один из самых распространенных способов – создать специальные функции внутри класса, которые дают возможность изменять поля класса при соблюдении определенных правил. Такие функции называются “мутаторами” (от слова mutate – “изменять”) или “сеттерами” (от слова set – “устанавливать”).
Фрагмент выше может показаться знакомым. Как и в самом первом примере, здесь объект под именем
В одном случае объект создается с уже заданными полями и может быть использован в других контекстах, в другом случае объект необходимо как-то инициализировать во время создания – это два разных подхода к созданию объектов. Возникает закономерный вопрос, “какой из этих способов лучше?”.
На практике они оба могут оказаться нужны, поэтому принято их комбинировать. Необходимость включения сеттеров в класс диктуется тем, какую функциональность должен предоставлять тип данных. Ведь принципиальное отличие функции
С этого момента введем два новых термина. Факт группирования данных и операций над ними внутри какой-то структуры данных мы будем называть инкапсуляцией. Совокупность всех открытых операций (т.е. функций, которые не скрыты меткой Упражнения1
Выше были упомянуты возможные побочные эффекты при использовании сеттеров. Попробуем рассмотреть один такой эффект и подумать, как его можно избежать. В предыдущем задании была разработана функция, которая сокращает дробь. Очевидно, что если изменить значение числителя или знаменателя после сокращения, сокращать дробь придется еще раз. Было бы лучше, если бы это происходило само собой: как при конструировании объекта, так и при любых изменениях числителя или знаменателя. Это означает, что функцию
Отдельно стоит отметить, что сеттеры по своей природе нарушают принцип закрытости класса. Подробней об этом и других принципах будет говориться в последующих главах, однако важно иметь ввиду, что делать поля закрытыми с помощью метки 2
Это упражнение призвано развенчать некоторые мифы, связанные с классами в С++. Для его выполнения понадобится некоторое знание указателей и того, как структурируется виртуальная память процесса в языке С. Необходимо создать переменную типа Сноски1Если говорить конкретней, слово “класс” используется в ситуациях, когда необходимо подчеркнуть особенность способа управления процессов в программе, когда классы используются для создания новых типов данных, которые управляют выполнением команд через обмен сообщениями между объектами с помощью их методов [Goldberg]. 2С этого момента в тексте будет использоваться слово 3Геттер (или "аксессор") – (от слова get, англ.) внутренняя функция какой-либо структуры данных, которая возвращает значение внутренних полей структуры, часто – без возможности их последующего изменения .
Copyright © 2023 Брынзан, Л. В. |