Урок №115. Инкапсуляция

  Юрий  | 

    | 

  Обновл. 11 Июн 2019  | 

 18249

 ǀ   4 

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

Таким образом, мы защитим целостность нашей программы.

Примечание: Функция at() в std::array и std::vector делает что-то похожее!

Преимущество №3: Инкапсулированные классы легче изменить.

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

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

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

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

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

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

Преимущество №4: С инкапсулированными классами легче проводить отладку.

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

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

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

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

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

Функции доступа обычно бывают двух типов:

   Геттеры — это функции, которые возвращают значения закрытых переменных-членов класса.

   Сеттеры — это функции, которые позволяют присваивать значения закрытым переменным-членам класса.

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

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

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

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

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

Заключение


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

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

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

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

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

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

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

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (91 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 4

  1. Аватар Ануар:

    Основное преимущество инкапсуляции – использовать класс без необходимости знать его реализацию.

    Мне вот не понятно все еще. Я написал функцию и использую ее каждый раз когда мне нужно.Там есть параметры которую я даю и они возвращают, либо выполняют просто. Так вот. У меня еще не было проблем с тем, чтобы я должен обязательно знать реализацию функции которую я написал. Может быть это из-за недостатка опыта. Но мне кажется, что проблем с этим возникать не должно. Написал функцию и забыл что она там внутри делает. отправляешь ей параметры и получаешь ответ. В чем проблема?

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

    1. Аватар kenjyminamori:

      Я думаю автор несколько неоднозначен выразил свою мысль. В статье раскрывается главный смысл инкапсуляции как механизм для создания ИНТЕРФЕЙСОВ(см Википедию). По-сути, это и является "не знанием реализации".

      Все это придумано только для того, чтобы использовать код повторно. Вот вы написали свой класс. Даю вам голову на отсечение, что даже вы будете смотреть на него как в первый раз через полгодика. Вам нужно использовать класс в другой программе, но насколько это безопасно? Можно ли менять вот эту перемнную? А можно ли напрямую вызывать вот эту функцию? Чтобы ответить на этот вопрос, надо заново сесть и разобраться во всем классе, на это может уйти куча времени и сил. А теперь представьте, что вы заранее побеспокоились и обозначили, что можно использовать, а что трогать нельзя, потому что все сломается. То что использовать можно — это ваши публичные поля, а то что нельзя — приватные. Теперь, вам достаточно пройтись по хедеру файла, и вы сразу поймете как все работает не зная реализации.

  2. Аватар Торос:

    "Если бы пользователи могли бы напрямую обращаться к массиву, то они могли бы использовать недопустимый индекс:
    Однако, если мы сделаем массив закрытым, то мы сможем заставить пользователя использовать функцию, которая, первым делом, проверяет корректность индекса:"
    Кто имеется ввиду под пользователем? Это программист который использует класс? Или пользователь программы? не понимаю

    1. Юрий Юрий:

      Пользователь — тот, кто использует программу (не программист, который её пишет).

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

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