Что такое инкапсуляция с
Перейти к содержимому

Что такое инкапсуляция с

  • автор:

Инкапсулируй это

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

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

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

Пример: работа с денежными величинами. Не секрет, что во многих e-commerce системах денежные величины реализованы в виде чисел с плавающей запятой. Думаю, все из нас в курсе, что при простом сложении двух «целых» чисел, представленных в виде переменных с плавающих запятой, может образоваться «немного не целое число». Поэтому при такой реализации там и тут приходится вставлять вызов функции округления. Это и есть размазывание знаний об устройстве сущности по всей программе. Инкапсуляция в данном случае — собрать (спрятать) в одном месте знание о том, что деньги представлены в виде величины с плавающей запятой, и что её постоянно приходится округлять при самых невинных операциях. Спрятать так, чтобы при использовании сущности «деньги» речь об округлении даже не заходила. При инкапсуляции не будет никаких проблем заменить реализацию «денег» с числа с плавающей на число с фиксированной запятой.

  • инкапсуляция
  • сокрытие реализации
  • Программирование
  • Совершенный код

Определение

Инкапсуляция это набор инструментов для управления доступом к данным или методам которые управляют этими данными. С детальным определением термина “инкапсуляция” можно ознакомиться в моей предыдущей публикации на Хабре по этой ссылке. Эта статья сфокусирована на примерах инкапсуляции в Си++ и Си.

Инкапсуляция в Си++

По умолчанию, в классе ( class ) данные и методы приватные ( private ); они могут быть прочитаны и изменены только классом к которому принадлежат. Уровень доступа может быть изменен при помощи соответствующих ключевых слов которые предоставляет Си++.

В Си++ доступно несколько спецификаторов, и они изменяют доступ к данным следующим образом:

  • публичные ( public ) данные — доступны всем;
  • защищенные ( protected ) — доступны только классу и дочерним классам;
  • приватные ( private ) —доступны только классу которому они принадлежат.

Для краткости, только два уровня (приватный и публичный) будут освещены в примерах.

Пример инкапсуляции

В классе Contact , публичные переменные и методы доступны из основной программы ( main ). Приватные переменные и методы могут прочитаны, вызваны или изменены только самим классом.

#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >void print_numbers() < cout >; int main() < Contact Tony; Tony.print_numbers(); // cout 

Попытка напечатать или изменить приватную переменную mobile_number из основной программы ( main ) вызовет ошибку при компиляции потому как доступ к приватным данным в классе ограничен.

Нарушение инкапсуляции с Друзьями (Хорошая практика)

В Си++ присутствует ключевое слово “друг” ( friend ) которое позволяет добавить исключения в общие правила доступа к данным. Если функция или класс названы другом ( friend ) класса Contact — они получают свободный доступ к защищенным или приватным данным.

Существует два основных правила дружбы — дружба не наследуется и не взаимна. Также, наличие “друзей” не изменяет уровень защищенности данных — приватные данные остаются приватными с исключением в виде “друга”.

#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >// Declaring a global 'friend' function friend void print_numbers( Contact some_contact ); >; void print_numbers( Contact some_contact ) < cout int main()

В этом примере, функция print_numbers() — обычная функция, не метод класса Contact . Объявление функции print_numbers() “другом” класса Contact — единственная причина по которой функция print_numbers() имеет доступ к приватным данным. Если убрать строку с определением друга — код не скомпилируется.

Примечание: друзьями лучше не злоупотреблять. Добавление друга стоит рассматривать как исключение, не как общую практику.

Нарушение инкапсуляции с Преобразованием типов и Указателями (Плохая практика)

Прежде всего, стоит заметить что использовать указатели и преобразование типов таким способом — плохая идея. Этот способ не гарантирует получения нужных данных. Он плохо читается и плохо поддерживается. Невзирая на это, он существует.

Си++ получил в наследство от Си множество инструментов, один из которых — преобразование типов ( typecasting ). По умолчанию, все переменные и методы в классе приватные. В то же время, стандартный уровень доступа к данным в структуре ( struct ) — публичный. Возможно создать структуру или полностью публичный класс в котором данные будут расположены идентично данным в классе Contact и используя преобразование типов получить доступ к приватным данным.

#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >void print_numbers() < cout >; struct Contact_struct < int mobile_number; int home_number; >; int main() < Contact Tony; Contact_struct * structured_Tony; Tony.print_numbers(); structured_Tony = (Contact_struct *) & Tony; structured_Tony->mobile_number = 20; structured_Tony->home_number = 30; Tony.print_numbers(); return 0; >

Приватные данные были прочитаны и изменены благодаря преобразованию типов

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

Традиционно считается что инкапсуляция — один из ключевых ООП принципов. Тем не менее, это не лимитирует использование этого принципа в процедурно-ориентированных языках. В Си, инкапсуляция используется давно, невзирая на отсутствие ключевых слов “приватный” и “публичный”.

Приватные переменные

В контексте инкапсуляции, все данные в Си могут быть рассмотрены как публичные по умолчанию. Уровень доступа к переменным в структурах ( struct ) может быть изменен на приватный если изолировать их определение от основной программы. Нужный эффект может быть достигнут при использовании отдельных заголовочных (header, .h) и исходных (source, .c) файлов.

В данном примере, структура была определена в отдельном исходном файле “private_var.c”. Поскольку инициализация структуры в Си требует выделения и освобождения памяти, несколько вспомогательных функций были добавлены.

#include "private_var.h" #include #include struct Contact < int mobile_number; int home_number; >; struct Contact * create_contact() < struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); > void delete_contact( struct Contact * some_contact )

В соответствующем заголовочном файле "private_var.h", структура Contact была объявлена, но ее содержание осталось скрытым для основной программы.

#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif /* PRIVATE_VAR */

Таким образом, для “main.c” содержание структуры неизвестно и попытки прочитать или изменить приватные данные вызовут ошибку при компиляции.

#include "private_var.h" #include int main() < struct Contact * Tony; Tony = create_contact(); // printf( "Mobile number: %d\n", Tony->mobile_number); // will cause compile time error delete_contact( Tony ); return 0; >

Получение доступа к приватным переменным с Указателями

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

Доступ к переменным в структуре ограничен. Тем не менее, спрятаны только переменные, не память в которой хранятся данные. Указатели можно рассматривать как ссылку на адрес памяти, и если эта память доступна программе — данные сохраненные в этой памяти можно прочитать и изменить. Если указатель назначен на память в которой структура хранит свои данные — их можно прочитать. Используя то же определение структуры (те же “.c” и “.h” файлы) и модифицированный “main.c” файл, ограничение доступа было преодолено.

#include "private_var.h" #include int main()

Данные в структуре были прочитаны и модифицированы

Приватные функции

Функции, будучи внешними ( extern ) по умолчанию, видимы во всей так называемой единице трансляции ( translation unit ). Другими словами, если несколько файлов скомпилированы вместе в один объектный файл, любой из этих файлов сможет получить доступ к любой функции из любого другого файла. Использование ключевого слова “статический” ( static ) при создании функции ограничит ее видимость до файла в котором она была определена.Следовательно, для обеспечения приватности функции необходимо выполнить несколько шагов:

  • функция должна быть объявлена статической ( static ) либо в исходном файле (.c), либо в соответствующем заголовочном файле (.h);
  • определение функции должно находиться в отдельном исходном файле.

В данном примере, в файле “private_funct.c”, была определена статическая функция print_numbers() . К слову, функция delete_contact() успешно вызывает print_numbers() поскольку они находятся в одном файле.

#include "private_funct.h" #include #include struct Contact < int mobile_number; int home_number; >; struct Contact * create_contact() < struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); > static void print_numbers( struct Contact * some_contact ) < printf("Mobile number: %d, ", some_contact->mobile_number); printf("home number = %d\n", some_contact->home_number); > void delete_contact( struct Contact * some_contact )

В соответствующем заголовочном файле "private_funct.h", print_numbers() была декларирована как статическая функция.

#ifndef PRIVATE_FUNCT_H #define PRIVATE_FUNCT_H struct Contact; struct Contact * create_contact(); static void print_numbers( struct Contact * some_contact ); void delete_contact( struct Contact * my_points ); #endif /* PRIVATE_FUNCT_H */

Основная программа, “main.c”, успешно вызывает print_numbers() опосредовательно через delete_contact() , поскольку обе функции находятся в одном документе. Тем не менее, попытка вызвать print_numbers() из основной программы вызовет ошибку.

#include "private_funct.h" #include int main() < struct Contact * Tony; Tony = create_contact(); // print_numbers( Tony ); // will cause compile time error delete_contact( Tony ); return 0; >

Получение доступа к приватным функциям

Вызвать функцию print_numbers() из основной программы возможно. Для этого можно использовать ключевое слово goto или передавать в main указатель на приватную функцию. Оба способа требуют изменений либо в исходном файле “private_funct.c”, либо непосредственно в теле самой функции. Поскольку эти методы не обходят инкапсуляцию а отменяют её, они выходят за рамки этой статьи.

Заключение

Инкапсуляция существует за пределами ООП языков. Современные ООП языки делают использование инкапсуляции удобным и естественным. Существует множество способов обойти инкапсуляцию и избежание сомнительных практик поможет ее сохранить как в Си, так и в Си++.

  • инкапсуляция
  • oop concepts
  • encapsulation

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

Инкапсуляция в программировании — это принцип, согласно которому внутреннее устройство сущностей нужно объединять в специальной «оболочке» и скрывать от вмешательств извне. Доступ к объектам возможен через специальные открытые методы, а напрямую обратиться к их содержимому нельзя.

«IT-специалист с нуля» наш лучший курс для старта в IT

Инкапсуляцию также описывают как принцип разделения логики и поведения. Логика — то, как что-то устроено внутри. Поведение — то, как оно взаимодействует с другими сущностями. Разделение этих двух понятий упрощает взаимодействие объектов в коде.

Обычно содержимое заключается в специальную программную оболочку, которая закрывает данные от внешних обращений. Принцип похож на автоматическую коробку передач: вам не приходится вручную управлять каждым элементом системы, более того — во время управления машиной к ним нет доступа. Есть только коробочка с рычагом, в качестве которой в программировании выступают открытые методы. Это понятно из названия: сущность оказывается «в капсуле», изолированной от внешнего мира.

Профессия / 8 месяцев
IT-специалист с нуля

Попробуйте 9 профессий за 2 месяца и выберите подходящую вам

vsrat_7 1 (1)

Для чего нужна инкапсуляция

Инкапсуляция считается одним из четырех основных принципов ООП — объектно-ориентированного программирования. Этот подход представляет сущности в коде как классы и объекты, позволяет «строить» код из них, как из кирпичиков. Но чтобы объекты грамотно выполняли свои задачи и не ломались, нужна инкапсуляция.

Объяснить ее значимость поможет подход «от обратного». Вот как выглядела бы работа с кодом без инкапсуляции:

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

Поэтому инкапсуляция нужна — так код становится более упорядоченным и повышается отказоустойчивость. Ведь если сломается инкапсулированный объект, это не повлияет на работу других. Примерно как сломанная кнопка не приводит к поломке всего устройства.

Инкапсуляция, абстракция и сокрытие данных

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

Абстракция помогает работать с объектами и не обращать внимания на то, как они устроены внутри. А для этого как раз нужна инкапсуляция.

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

  • Сокрытие нужно, чтобы обеспечить безопасность данных, и подразумевает невозможность доступа;
  • Инкапсуляция нужна, чтобы обеспечить целостность объекта и дать возможность пользоваться им, не вдаваясь в подробности его реализации. При этом технический доступ к объекту в некоторых случаях может сохраниться.

Соответственно, различаются и реализации обоих принципов. Хотя тут многое зависит от языка программирования: в некоторых языках, например, C++, инкапсуляция без сокрытия считается бессмысленной. А в других, таких как Python, есть инкапсуляция, но нет сокрытия. Есть и языки, которые жестко разделяют два понятия — так, что они описываются по-разному.

Курс для новичков «IT-специалист
с нуля» – разберемся, какая профессия вам подходит, и поможем вам ее освоить

Как выглядит реализация инкапсуляции

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

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

Функции внутри объекта называются методами, а данные — свойствами. Они разные, но объединены внутри одной сущности, как и предписывает принцип инкапсуляции.

Как разработчики инкапсулируют данные

Методы и свойства. Мы уже говорили о том, что классы и объекты — важный инструмент инкапсуляции. Стоит рассказать подробнее о методах и свойствах объектов. За счет них во многом обеспечивается инкапсуляция.

  • Свойства — это переменные, «лежащие» внутри объектов. Они могут быть любого типа, а иногда и сами являются объектами. В переменных хранятся данные. Если какая-то переменная является свойством, ее содержимое по умолчанию скрыто. Как открывать и запрещать доступ к свойствам, мы расскажем ниже.
  • Методы — это функции внутри объектов. Они не просто хранят данные, а совершают какое-то действие. Важное отличие метода от обычной функции — его можно вызвать только для объекта, в котором он находится. Вызов метода отличается и внешне: в большинстве языков сначала пишется имя объекта, а потом, через точку — название метода и аргументы. А еще для вызова метода ничего не нужно импортировать — все уже есть в объекте, и это еще один пример целостности инкапсулированной сущности.

Геттеры и сеттеры. Выше мы говорили, что свойства по умолчанию скрыты от посторонних. Несмотря на то что во многих случаях обратиться к ним напрямую через объект можно, в ООП это считается не лучшей практикой. Поэтому для работы со свойствами используют специальные методы — геттеры и сеттеры. Названия таких методов обычно начинаются со слов get и set — другие имена не запрещены синтаксисом, но считаются плохим тоном.

  • Геттер — это метод объекта, который возвращает значение свойства этого объекта. Например, у объекта в свойстве value написано 15. Тогда геттер getValue() будет возвращать 15.
  • Сеттер — это метод, который изменяет значение свойства, задает его (set). Например, гипотетический метод setValue(x) будет менять значение свойства value. Аргумент x — это новое значение свойства, при вызове сеттера туда подставляются данные.

Геттеры и сеттеры важны для инкапсуляции, потому что позволяют не вмешиваться во внутреннюю структуру объекта. Они работают с ним сами, а значит, нет нужды «лезть» в объект извне — данные передадут геттеры и сеттеры. Так снижается риск ошибки при работе с данными в объекте.

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

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

  • public — объект, функция или метод доступны для всех. Другие сущности могут читать оттуда данные и изменять их. Свойство публичного объекта можно получить, просто написав его название через точку: .. С такими данными легко работать, для них не нужны геттеры и сеттеры, но информация оказывается уязвима.
  • private — содержимое объекта доступно только для других его составных частей. Например, метод объекта может вызывать данные из этого же объекта напрямую — а какой-нибудь код снаружи уже не сможет.
  • protected — содержимое объекта доступно ему самому и его производным, например, потомкам.

Модификаторы задаются в классе, который описывает объект. Их можно задать самому классу или его содержимому — разным методам и свойствам.

В некоторых языках, например, C#, есть еще вариант internal — доступ открыт только из одного файла. В других файлах нельзя будет создать такой объект. Такой модификатор задается классу.

Ключевые слова. Ряд языков поддерживает и другие возможности, способствующие инкапсуляции. Обычно они реализуются с помощью ключевых слов. Например, static дает возможность сделать метод класса статичным — и запускать, даже если объект этого класса не создан.

Слово abstract описывает неполные, «схематичные» сущности, которые используются, чтобы наследовать от них более подробные. Объект абстрактного класса нельзя создать. Зато можно отнаследовать от него несколько других классов и создать уже их объекты — для того абстрактные сущности и нужны.

А слово sealed, наоборот, «запечатывает» класс и запрещает создавать его потомков. Это нужно для защиты от ситуаций, когда наследование может сломать работу кода.

Как начать работать с инкапсуляцией

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

Если вы интересуетесь IT — приглашаем на курсы! Выберите и получите новую профессию, востребованную на рынке.

IT-специалист с нуля

Наш лучший курс для старта в IT. За 2 месяца вы пробуете себя в девяти разных профессиях: мобильной и веб-разработке, тестировании, аналитике и даже Data Science — выберите подходящую и сразу освойте ее.

картинка (75)

от 8 месяцев
IT-специалист с нуля

Наш лучший курс для старта в IT. За 2 месяца вы пробуете себя в девяти разных профессиях: мобильной и веб-разработке, тестировании, аналитике и даже Data Science — выберите подходящую и сразу освойте ее.

Принципы ООП. Инкапсуляция

Принципы ООП. Инкапсуляция

Термин «инкапсуляция», он же первый принцип ООП, имеет две трактовки. Зачастую специалисты используют этот термин только в одном значении, забывая о другом, а это неверно.

Первая трактовка – в один объект или класс объединяются и данные, и методы, которые работают с этими данными. Вторая трактовка – инкапсуляция это сокрытие внутренней структуры объекта от внешних воздействий. Все изменения состояния объекта происходят только с помощью обращений к методам самого объекта.

Лично я понимаю под термином «инкапсуляция» обе эти трактовки – и соединение методов с данными, и сокрытие данных от внешних воздействий, изменение их только через обращение к методам самого объекта. Дело в том, что вторая трактовка без первой не работает совсем. Что вы будете скрывать, если у вас в объекте не будет методов и данных?

К чему относится принцип инкапсуляции? С точки зрения ООП, объектом является не только класс или объект, или как это называется в вашем языке программирования, но и другие структуры, более высокого уровня, а именно: packages, namespaces, модули, сабмодули, подсистемы и вся система целиком. Если рассматривать инкапсуляцию как сокрытие, мы понимаем, что наш объект не должен изменяться ничем снаружи, кроме его же методов. Это поможет избежать случайных взаимодействий типа «гайку открутили – жопа отвалилась».

Если к состоянию объекта имеет доступ только сам объект – я напоминаю, что состояние объекта это значения всех его полей – то это нам позволяет избежать лишних взаимосвязей, неожиданных сайд эффектов, когда объект работал-работал, и тут у него внезапно поменялось поле, и в нем уже совсем другое значение. Программа вылетает, и получаются прочие проблемы типа raise condition.

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

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

Из принципа инкапсуляции напрямую проистекает множество паттернов GRASP. Например, паттерн GRASP “Information expert” – это прямая имплементация паттерна инкапсуляции. Где должны обрабатываться данные? В объекте, который их содержит. Это частная, более специфическая формулировка той же самой инкапсуляции.

То же самое касается low coping. Меньше связей между объектами означает, что к объектам мы обращается только через нужные методы, а не дергаем все подряд, не используем reflection, чтоб поковыряться в кишках объекта. Только через паблик интерфейс, только через методы, изначально предназначенные для того, чтобы к ним обращаться.

Знание нескольких принципов освобождает от знания многих фактов. Программисты, которые помнят, что такое инкапсуляция в широком смысле – не лазить, куда не следует, не проскакивать между лейерами, не лезть в базу поверх UI – меньше сталкивается с сайд эффектами. Любое нарушение структуры программы чревато эффектами, и все их можно свести к нарушению инкапсуляции.

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

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