Урок 115. Инкапсуляция. Геттеры и Сеттеры

   ⁄ 

 Обновлено 15 Фев 2018  ⁄ 

⁄   3004

В предыдущем уроке мы узнали, что переменные-члены класса по умолчанию являются закрытыми. Новички, которые изучают объектно-ориентированное программирование, часто с трудом понимают, почему устроено именно так.

Зачем делать переменные-члены закрытыми?

В качестве ответа, воспользуемся аналогией. В современной жизни мы имеем доступ ко многим электронным устройствам. К телевизору есть пульт дистанционного управления, которым его можно включать/выключать. Управление автомобилем позволяет в разы быстрее передвигаться между двумя точками. С помощью фотоаппарата можно делать снимки.

Все из этих трёх вещей используют общий шаблон: они предоставляют простой интерфейс для вас (кнопка, руль и т.д.) для выполнения действия. Однако, то как эти устройства фактически работают – скрыто от вас. Для нажатия кнопки на пульте дистанционного управления, вам не нужно знать, что выполняется «под капотом» пульта для взаимодействия с телевизором. Когда вы нажимаете на педаль газа в своем автомобиле, вам не нужно знать, как двигатель внутреннего сгорания приводит в движение колеса. Когда вы делаете снимок, вам не нужно знать, как датчики собирают свет в пиксельное изображение.

Такое разделение интерфейса и реализации чрезвычайно полезно, поскольку оно позволяет использовать объекты, без необходимости знать то, как эти объекты работают. Это значительно снижает сложность использования этих устройств и значительно увеличивает их количество (устройства с которыми можно взаимодействовать).

По аналогичным причинам разделение реализации и интерфейса полезно и в программировании.

Инкапсуляция

В объектно-ориентированном программировании инкапсуляция (или скрытие информации) — это процесс скрытого хранения деталей реализации объекта. Пользователи обращаются к объекту через открытый интерфейс.



В C++ инкапсуляция реализована с помощью спецификаторов доступа. Как правило, все переменные-члены класса являются закрытыми (скрывая детали реализации), а большинство функций-членов являются открытыми (открывая интерфейс для пользователя). Хотя требование к пользователям использовать публичный интерфейс может показаться более обременительным, чем просто открыть доступ к переменным-членам, но на самом деле это дает большое количество полезных преимуществ, которые улучшат возможность повторного использования кода и его поддержку.

Инкапсулированные классы проще в использовании и уменьшают сложность ваших программ.

С полностью инкапсулированным классом вам нужно только знать, какие функции-члены являются доступными для использования, какие аргументы они принимают и какие значения возвращают. Не нужно знать, как класс реализован внутри. Например, класс, содержащий список имен, может быть реализован с использованием динамического массива строк C-style, std::array, std::vector, std::map, std::list или одной из многих других структур данных. Для использования этого класса, вам не нужно знать то, как он реализован. Это значительно снижает сложность ваших программ, а также уменьшает количество ошибок. Это является ключевым преимуществом инкапсуляции.

Все классы стандартной библиотеки C++ инкапсулированы. Представьте, насколько сложнее был бы процесс изучения C++, если бы вам нужно было бы знать, как реализованы std::string, std::vector или std::cout, чтобы их использовать!

Инкапсулированные классы помогают защитить ваши данные и предотвращают их неправильное использование.

Глобальные переменные опасны, так как нет строгого контроля над тем, кто имеет доступ к глобальной переменной и как её используют. Классы с открытыми членами имеют ту же проблему, только в меньших масштабах.

Например, допустим нам нужно написать строковый класс. Мы могли бы начать так:

Эти две переменные связаны: m_length всегда должен соответствовать длине строки, удерживаемой m_string. Если бы m_length был бы открытым, то любой мог бы изменить длину строки без изменения m_string (или наоборот). Это точно привело бы к проблемам. Делая как m_length, так и m_string закрытыми, пользователи вынуждены использовать методы для взаимодействия с классом (а эти методы могут гарантировать, что m_length и m_string всегда будут иметь корректные значения).

Мы также можем сами увеличить защиту нашего класса от неправильного использования. Например, рассмотрим класс с открытой переменной-массивом:

Если бы пользователи могли бы напрямую обращаться к массиву, то они могли бы использовать недопустимый индекс:

Однако, если мы сделаем массив закрытым, то мы сможем заставить пользователя использовать функцию, которая, первым делом, проверяет корректность индекса:

Таким образом, мы защитим целостность нашей программы. Примечание: функция at() в std::array и std::vector делает что-то похожее!

Инкапсулированные классы легче изменять.

Рассмотрим следующий простой пример:

Хотя эта программа работает нормально, но что произойдет, если мы решим переименовать m_number1 или изменить тип этой переменной? Мы бы сломали не только эту программу, но и большую часть программ, которые используют класс Values!

Инкапсуляция дает возможность изменить способ реализации классов, не нарушая при этом работу всех программ, которые их используют.

Вот инкапсулированная версия класса выше, но которая использует функции для доступа к m_number1:

Теперь давайте изменим реализацию класса:

Обратите внимание, поскольку мы не изменяли прототипы каких-либо функций в публичном интерфейсе нашего класса, наша программа, использующая класс, продолжает работать без каких-либо изменений или проблем.

Точно так же, если ночью гномы пробрались в ваш дом и заменили внутреннюю часть вашего пульта телевизора другой (но совместимой) технологией, вы, вероятно, даже не заметили бы!

Инкапсулированные классы легче отлаживать.

И, наконец, инкапсуляция помогает отлаживать программу, когда что-то идет не так. Часто причиной неправильной работы программы является некорректное значение одной из переменных. Если каждый имеет прямой доступ к переменной, то отслеживание части кода, которая изменила переменную, может быть затруднительным (так как значение мог изменить любой, кто имеет доступ). Однако, если для изменения значения нужно вызывать один и тот же метод, то вы можете просто использовать точку остановы (breakpoint) для этого метода и посмотреть, как каждый вызывающий объект изменяет значение, пока не  увидите, где что-то идет не так.

Функции доступа

В зависимости от класса может быть уместно (в контексте того, что делает класс), чтобы была возможность получать или устанавливать значения закрытых переменных класса.

Функция доступа (функция access) — это короткая открытая функция, задачей которой является получение или изменение значения закрытой переменной-члена. Например:

Здесь getLength() — это функция доступа, которая просто возвращает значение m_length.

Функции доступа обычно бывают двух вариантов: геттеры и сеттеры. Геттер (getter) — это функция, которая возвращает значение закрытой переменной-члена класса. Сеттер (setter) — это функция, которая позволяет установить значение закрытой переменной-члена класса.

Вот пример класса, который использует геттеры и сеттеры для всех своих закрытых переменных-членов:

В этом классе нет проблем с тем, чтобы пользователь мог напрямую получать или устанавливать значения любой из закрытых переменных-членов, так как есть полный набор геттеров и сеттеров. В примере выше с классом MyString для переменной m_length не было предоставлено сеттера, так как не нужно было, чтобы пользователь мог напрямую устанавливать длину (длина должна быть задана только в соответствии с изменениями строки).

Правило: Предоставляйте функции доступа только когда есть смысл, чтобы пользователь мог получать и устанавливать значения.



Хотя иногда вы можете увидеть, что геттер возвращает неконстантную ссылку на переменную-член — этого следует избегать, так как в таком случае нарушается инкапсуляция, позволяя caller-у изменять внутреннее состояние класса вне класса. Лучше, чтобы ваши геттеры использовали тип возврата по значению или по константной ссылке.

Правило: Геттеры должны использовать тип возврата по значению или по константной ссылке. Не используйте для геттеров тип возврата по неконстантной ссылке.

Итого

Как вы можете видеть, инкапсуляция дает много преимуществ взамен небольшого количества приложенных усилий. Основное преимущество инкапсуляции – использовать класс без необходимости знать его реализацию. Инкапсулированные классы:

  проще в использовании;

  уменьшают сложность программы;

  защищают и предотвращают данные от неправильного использования;

  легче изменять;

  легче отлаживать.

Это значительно облегчает использование классов, с которыми мы не знакомы.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (28 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО