Инкапсуляция - это что такое? Инкапсуляция в программировании. Принцип инкапсуляции

24.06.2019

Этот вопрос кому-то покажется интересным, кому-то забавным, кому-то ни о чём.
В программировании, как и в других науках, существует терминология. Каждый термин описывает определённое явление. И тот, кто претендует называть себя специалистом в данной области, как бы хочешь не хочешь, а должен этой терминологией владеть. Поэтому все выпады и реплики на предмет “кому нужны эти пустые формулировки, я же программист, а не преподаватель”, можете оставить при себе. Звучат они не в вашу пользу, и характеризуют вас как не специалиста.

Есть в английском языке прекрасная фраза “It’s a must ”. Которой на русском языке я боюсь не найти таких же лаконичных аналогов. Но примерно звучит так: “НАДО”.
Так вот, здесь и сейчас мы поговорим как раз о том, что многим не кажется простым вопросом. В чём разница между инкапсуляцией и сокрытием данных.

В чём-то я, конечно, повторюсь, поскольку отдельно тема инкапсуляции уже рассматривалась мной в одной из статей. Но повторение, как говорится,…а в прочем, ближе к делу.
Так вот, начнём с инкапсуляции. Те, кто знаком с процедурными языками, типа С, знают не по наслышке, что такое проблема глобальных переменных. Когда данные находятся
отдельно от методов, которые ими оперируют.

Или С++, который хоть и считается объектно-ориентированным, всё же вполне допускает наличие переменных, хранящихся вне классов или структур.
Когда я думаю, как это выглядит в реальном мире, когда законы инкапсуляции нарушаются, в силу каких-то причин, почему-то очень живо представляется бабушка, сидящая на диванчике, а рядом с ней на тумбочке – её вставные челюсти, парик, очки и протез левой ноги у дивана. Жуткая картина.
А ещё представляется, как я прихожу в ресторан, заказываю утку под вишнёвым соусом, а повар с сияющей улыбкой прямо на моих глазах рубит ей голову, потрошит и кидает в кипяток. Тоже мало приятного.

В первом случае – хотелось бы какой-то целостности объекта, во втором – поменьше деталей о процессе.

Это и есть то, на чём базируются инкапсуляция и сокрытие данных. Детали реализации и данные упакованы в объект, и объект является их полноправным владельцем.

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

И вот это уже называется сокрытие данных (data hiding ). Сокрытие данных достигается посредством модификаторов доступа (в С# – это модификаторы private , public , protected , protected internal), каждый из которых определяет свой уровень доступности членов типа.

Рассмотрим пример. Здесь определяется класс Person , который инкапсулирует 2 приватных поля – имя (name) и возраст (age). Модификатор private в языке C# означает, что доступ к помеченному полю возможен только в пределах самого класса, а не через экземпляр и не для наследников. Контроль доступа на чтение и запись предоставляют свойства Name и Age . В них проверяется назначаемые величины, на диапазон и формат.

Class Person { private string name; private int age; public string Name { get { return name; } set { if (value.Length >=2 && value.Length <= 25) { name = value; } else { throw new FormatException("the length of name can"t be less than 2 or longer than 25"); } } } public int Age { get { return age; } set { if (value > 1 && value < 120) { age = value; } else { throw new ArgumentOutOfRangeException(); } } } public override string ToString() { return String.Format("name - {0}, age - {1}", Name, Age); } } class Program { static void Main() { Person person = new Person() { Name = "John", Age = 147 }; Console.WriteLine(person); } }

Можно сказать, что сокрытие – это одна из задач инкапсуляции. Но само по себе сокрытие данных инкапсуляцией не является.
Также сказать, что инкапсуляция – это сокрытие данных, тоже было бы неверно. Потому что её задачи выходят за рамки сокрытия.

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

Таким образом, разграничивать понятие инкапсуляции и сокрытия данных всё-таки нужно и важно видеть грань между ними.

Минимальность и инкапсуляция

В "Эффективном использовании C++" (Effective C++), я приводил доводы в пользу интерфейсов класса, которые являются полными и минимальный . Такие интерфейсы позволяют клиентам класса делать что-либо, что они могли бы предположительно хотеть делать, но классы содержат методов не больше, чем абсолютно необходимо. Добавление функций вне минимума, необходимого для того, чтобы клиент мог сделать его работу, как я писал, уменьшает возможности повторного использования класса. Кроме того, увеличивается время трансляции для программы клиента. Джек Ривес (Jack Reeves) написал, что добавление методов сверх этих требований, нарушает принцип открытости-закрытости, производит жирные интерфейсы класса, и в конечном счете ведет к загниванию программного обеспечения . Возможно увеличение числа параметров, чтобы уменьшить число методов в классе, но теперь мы имеем дополнительную причину, чтобы отказаться от этого: уменьшается инкапсуляция класса.

Конечно, минимальный интерфейс класса – не обязательно самый лучший интерфейс. Я отметил в "Эффективном использовании C++", что добавление функций сверх необходимости может быть оправданным, если это значительно увеличивает эффективность класса, делает класс, более легким в использовании или предотвращает вероятные клиентские ошибки . Основываясь на его работе с различными строковыми классами, Джек Ривес отмечает, что для некоторых функций трудно ощутить, когда их делать не членами, даже если они могли быть не друзьями и не методами . "Наилучший" интерфейс для класса может быть найден только после балансировки между многими конкурирующими параметрами, среди которых степень инкапсуляции является лишь одним.

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

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

Из книги Сущность технологии СОМ. Библиотека программиста автора Бокс Дональд

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

Из книги Как функции, не являющиеся методами, улучшают инкапсуляцию автора Мейерс Скотт

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

Из книги Информатика и информационные технологии: конспект лекций автора Цветкова А В

Инкапсуляция и функции – не члены Мы теперь видим, что приемлемый способом оценки инкапсуляции является количество функций, которые могли бы быть разрушены, если изменяется реализация класса. В этом случае становится ясно, что класс с n методами более инкапсулирован, чем

Из книги Информатика и информационные технологии автора Цветкова А В

Из книги Язык программирования С# 2005 и платформа.NET 2.0. автора Троелсен Эндрю

автора Реймонд Эрик Стивен

Инкапсуляция Первым принципом ООП является инкапсуляция. По сути, она означает возможность скрыть средствами языка несущественные детали реализации от пользователя объекта. Предположим, например, что мы используем класс DatabaseReader, который имеет два метода Open() и Close().//

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

Инкапсуляция на основе методов чтения и модификации Давайте снова вернемся к рассмотрению нашего класса Employee. Чтобы "внешний мир" мог взаимодействовать с частным полем данных fullName, традиции велят определить средства чтения (метод get) и модификации (метод set). Например://

Из книги Основы объектно-ориентированного программирования автора Мейер Бертран

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

Из книги TCP/IP Архитектура, протоколы, реализация (включая IP версии 6 и IP Security) автора Фейт Сидни М

Из книги Сетевые средства Linux автора Смит Родерик В.

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

Из книги Операционная система UNIX автора Робачевский Андрей М.

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

Из книги автора

24.5.4 Инкапсуляция защищенной полезной нагрузки Заголовок инкапсуляции защищенной полезной нагрузки протокола IP (IP Encapsulating Security Payload) применяется как для режима транспорта, так и для режима туннеля.Формат этого заголовка показан на рис. 24.8. Получатель использует индекс SPI

Из книги автора

Инкапсуляция и извлечение данных Стек протоколов хорошо иллюстрирует перемещение данных между программными компонентами, поддерживающими сетевое взаимодействие, однако он не дает ответа на вопрос, какие же изменения претерпевает информация на этом пути. На различных

Из книги автора

Инкапсуляция IP При работе в локальной сети на базе технологии CSMA/CD возможны два варианта инкапсуляции датаграмм IP в кадры уровней LLC и MAC.Первый заключается в использовании кадров Ethernet 2.0. В этом случае поле данных (1500 октетов) полностью принадлежит IP-датаграмме, a SAP

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

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

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

#include #define M_PI 3.1415926 /* вспомогательные */ #define EPS 1e-9 /* константы */ using namespace std; typedef double (*function) (double); //указатель на функцию вида double имя(double) struct tab { //структура-табулятор функций double x1, x2; //пределы табулирования int n; //количество шагов от x1 до x2 function f; //конкретная функция, которую табулируем, //заданная указателем на неё static void run(tab t); //функция, которая табулирует f(x) };

Какая конкретно функция табулируется, будет определять указатель f , включённый в структуру. Передавая структуру типа tab в функцию табулирования run , мы сможем решить поставленную задачу:

Void run(tab t) { //функция табулирования любой функции double dx = (t.x2 - t.x1) / t.n; cout << endl; cout.width(10); cout << "X"; cout.width(10); cout << "Y"; for (double x = t.x1; x <= t.x2 + EPS; x += dx) { cout << endl; cout.width(10); cout << x; cout.width(10); cout << t.f(x); } }

Допишем программу, построив таблицы значений нескольких функций:

//конкретные функции для примера double f1(double x) { return sin(x); } double f2(double x) { return cos(x); } double f3(double x) { return x*x; } int main() { tab fun1 = { 0, M_PI, 10, f1 }; run(fun1); //описали и табулировали одну функцию tab fun2 = { 0, M_PI/2, 10, f2 }; run(fun2); //а теперь совсем другую fun1.f = f3; run(fun1); //программно поменяли функцию в структуре, переставив //указатель на другой объект cin.sync(); cin.get(); return 0; }

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

  • в функцию run приходится передавать структуру или указатель на неё;
  • по-прежнему функция run никак не связана со структурой;
  • нельзя разделить или ограничить доступ к переменным и функциям структуры.

Можно сделать функцию "полноценным" членом структуры, но тогда всё равно придётся использовать инструментарий классов, немного забегая вперёд (показаны только изменённые участки кода):

Struct tab { double x1, x2; int n; function f; void run(); //изменено }; //... void tab::run(void) { //изменено double dx = (this->x2 - this->x1) / this->n; cout << endl; cout.width(10); cout << "X"; cout.width(10); cout << "Y"; for (double x = this->x1; x <= this->x2 + EPS; x += dx) { cout << endl; cout.width(10); cout << x; cout.width(10); cout << this->f(x); } } //... int main() { //изменено tab fun1 = { 0, M_PI, 10, f1 }; fun1.run(); tab fun2 = { 0, M_PI / 2, 10, f2 }; fun2.run(); fun1.f = f3; fun1.run(); cin.sync(); cin.get(); return 0; }

Примечания :

1. В этом коде this - "указатель объекта на самого себя", this->x1 означает, что надо взять переменную x1 , описанную именно внутри текущей структуры (в fun1 при вызове fun1.run();), а не в другой структуре или глобально.

2. Оператор:: имеет в C++ наивысший приоритет и означает указание области видимости объекта; в нашем случае то, что функция run относится к структурному типу tab .

В таком коде видно, что функция относится именно к структуре, мы вызываем её в виде fun1.run() , а не run(fun1) . Кроме того, мы сэкономили память стека, не передавая целые структуры внутрь функций.

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

Итак, объектно-ориентированное программирование (ООП) - парадигма программирования, основанная на понятиях класса и объекта .

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

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

Например, у класса "Студент" могут быть такие свойства как фамилия, группа, дата рождения, стипендия и т.д., методы "показать данные", "сменить номер группы", "начислить стипендию" и т.п.

Объекты всегда принадлежат одному или нескольким классам, которые определяют поведение объекта и являются его моделью. Термины "объект" и "экземпляр класса" взаимозаменяемы.

Класс - абстрактный тип данных, определяющий интерфейс и реализацию для всех своих экземпляров.

Например, объектом (экземпляром) класса "Студент" является конкретный "студент Иванов":

Student Ivanov; //или чаще: Student *Ivanov = new Student ();

Объекты обладают тремя базовыми свойствами (они же – три базовых принципа ООП ):

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

Так, для класса "Служащий" свойство "Зарплата" может быть приватно. А для начисления зарплаты или получения сведений о ней мы можем предусмотреть в классе публичные методы "ПолучитьВеличинуЗП", "НачислитьЗП" и т.п. Вообще, чаще всего свойства класса приватны, а основные методы публичны. Это связано ещё и с тем, что прямое изменение свойств класса в виде объект.свойство=значение не даёт возможности выполнения дополнительных действий и затрудняет модификацию программы.

Пояснение этой важной мысли:

Object.property=a;

конечно, выглядит естественней, чем

Object.setproperty(a);

Но что, если свойство, изменённое нами "напрямую", меняет кто-то или что-то ещё? А если понадобилось при каждом "прямом" присваивании значения свойству сделать нечто дополнительно (сообщить в налоговую, что на счету добавилось денег)? А если нужно при присваивании провести встроенный контроль на допустимость значения? А в программе уже 1000 записей вида object.property=a ? В случае же object.setproperty(a) мы изменим только один-единственный метод setproperty в классе объекта.

2. Наследование - механизм языка, позволяющий описать новый класс на основе уже существующего (родительского, базового) класса. Класс-потомок может добавить собственные методы и свойства, а также пользоваться родительскими методами и свойствами.

Например, класс "Студент" в нашей программе может быть потомком класса "Человек", и пользоваться рядом его свойств и методов ("Фамилия", "Дата рождения"), а также иметь свои, отсутствующие в базовом классе ("номер группы", "стипендия").

3. Полиморфизм – это единообразная обработка разнотипных данных, возможность перегрузки классами своих методов и методов классов-предков.

Например, базовый класс "Геометрическая фигура" может иметь метод "Нарисовать", по-разному реализованный в классах-потомках "Прямоугольник" и "Окружность". Но для программиста все методы будут вызываться под одним именем draw , что удобно.

В языках программирования, построенных на парадигме ООП, обычно имеется иерархия встроенных классов, на вершине которой находится класс с именем вроде Object . А все остальные классы являются его потомками того или иного иерархического "поколения".

Итак, использование классов позволяет:

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

Общий вид оператора описания класса следующий:

Class ИмяКласса { private: //область видимости в пределах класса, //доступ имеют только функции-члены данного класса Список членов класса; protected: //могут использоваться методами данного //и производных от него классов Список членов класса; public: //видимы вне класса, может осуществляться //доступ извне Список членов класса; };

Например,

Class Student { private: char *name; //имя - приватный член класса public: void show (void); //функция "показать" – публичная void setname (char *); //функция "установить имя" - публичная };

По умолчанию члены класса приватны, так что private: в начале можно было не указывать.

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

Void Student::show (void) { //функция относится к классу Student }

Доступ к членам класса осуществляет оператор. (точка), как и для структур, он позволяет связать свойство или метод с конкретным экземпляром класса. При использовании указателей, как и со структурами, конструкция (*указатель).поле заменяется на указатель->поле.

Для того, чтобы вызываемая функция точно "знала", с каким из объектов класса она работает, Си++ неявно передает в любую нестатическую функцию дополнительный скрытый параметр - указатель на текущий объект, называемый this . Параметр this видим в теле вызываемой функции, устанавливается при вызове в значение адреса начала объекта и может быть использован для доступа к членам объекта.

Void Student::setname (char *name) { strcpy (this->name, name); }

Указание this позволит не перепутать имеющие одинаковые имена name параметр функции и имя одного из свойств класса.

Конструктор

Класс на Си++ может иметь любое количество конструкторов , предназначенных для инициализации данных класса при создании нового экземпляра. Конструктор всегда имеет то же имя, что и класс, в котором он определён, с созданием экземпляра всегда связано явное или неявное выполнение конструктора. Если отсутствует явно описанный конструктор, создается конструктор по умолчанию. Конструктор вызывается компилятором явно при создании объекта и неявно при выполнении оператора new, применяемого к объекту, а также при копировании объекта данного класса. Конструктор не возвращает значений. Как правило, он описан в public-секции класса. В остальном он оформляется как обычная функция, например с явно описанным конструктором наш класс примет вид:

#include using namespace std; class Student { private: char *name; public: Student (char *); //конструктор с 1 параметром void show (void); void setname (char *); }; Student::Student (char *name=NULL) { //реализация конструктора if (name) { this->name = new char ; if (this->name) setname (name); } else this->name=NULL; } void Student::setname (char *name) { strcpy (this->name, name); } void Student::show (void) { cout << endl; if (this->name) cout << this->name; else cout << "NULL"; } int main() { Student *Ivanov = new Student ("Ivanov"); Ivanov->show(); Student Petrov("Petrov"); Petrov.show(); cin.sync(); cin.get(); return 0; }

Заметим, что в этом несложном классе уже хватает "подводных камней", например, функция setname не проверяет, достаточно ли места там, куда она копирует строку:

Petrov.setname("Popandopulo"); //крах программы

С другой стороны, мы не можем "заставлять" функцию каждый раз перераспределять память, освобождая прежнее содержимое – ведь для объектов из стека, таких как Petrov , делать delete попросту нельзя. Одним из возможных решений было бы "не копировать лишнего", учитывая то, что имени может быть и не задано:

Void Student::setname (char *name) { if (this->name) { int len = strlen(this->name); strncpy (this->name, name, len); this->name="\0"; } } //... Student Petrov("Petrov"); Petrov.setname("Popandopulo"); Petrov.show(); //Popand Student Sidorov; Sidorov.setname("Sidorov"); Sidorov.show(); //NULL

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

Student Copeikin = *Ivanov;

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

Void Student::setname (char *name) { if (this->name) delete this->name; int len = strlen(name); if (len) { this->name = new char ; if (this->name!=NULL) strcpy(this->name,name); } }

Но и это будет работать при условии, что свойство this->name предварительно инициализировалось в конструкторе до вызова setname .

Конструктор копирования для класса X имеет один аргумент типа &X и выполняется при присваивании экземпляров класса:

Student *Ivanov = new Student ("Ivanov"); Student Copeikin = *Ivanov;

Компилятор "опознаёт" конструктор копирования потому, что его параметром является ссылка на объект класса. Это тот самый объект, что находится справа от знака "присвоить", а объект слева от " = " доступен через this . Напишем реализацию конструктора копирования:

Public: //... Student (Student &); //конструктор копирования //... Student::Student (Student &from) { //реализация конструктора копирования this->name = new char ; if (this->name) setname (from.name); }

Деструктор

Деструктор выполняет действия, противоположные действиям конструктора, то есть, "разрушает" объект данного класса и вызывается явно или неявно. Неявный вызов деструктора связан с прекращением существования объекта из-за завершения области его определения. Явное уничтожение объекта выполняет оператор delete . Деструктор имеет то же имя, что класс, но предваренное символом ~ . Деструкторы не могут получать аргументы и быть перегружены. Класс может объявить только один общедоступный деструктор. Если класс не содержит объявления деструктора, компилятор автоматически создаст его.

Для нашего класса явный деструктор может иметь вид:

Student::~Student () { //реализация деструктора if (name) delete name; }

Здесь в области видимости внутри тела деструктора нет других переменных с именем name , так что this->name можно не писать.

При этом деструктор был описан в секции public класса:

Public: //... ~Student(); //деструктор

Если мы, при наличии явного деструктора, в приведённом выше коде

Student Copeikin = *Ivanov;

не задали явного конструктора копирования, конструктор копирования по умолчанию выполнил Copeikin.name = Ivanov->name , то есть, не скопировал строку, а лишь установил указатель Copeikin.name на тот же адрес памяти, куда показывал Ivanov->name . После явного или неявного удаления объекта Ivanov следствием попытки показать значение Copeikin.name будет крах программы. Подобные "потери памяти" очень трудноуловимы, поэтому за работой явных деструкторов нужно следить особенно внимательно.

Существует "правило большой тройки" для любых классов: если в классе есть явный деструктор, то требуется явный конструктор копирования и оператор "=". Возможно, имеет смысл определять в этом случае и явный конструктор без аргументов.

Пример 1 . Класс "паскалевских" массивов на C++. Для этих массивов элементы можно нумеровать с произвольного целого числа, а не только с нуля.

#include #include #define MAXDOUBLE 1.797693E+308 using namespace std; class Array { private: int low,hi; double *value; public: Array() : low(0), hi(0), value(NULL) {} Array(int); Array(int,int); ~Array(); double get (int index); void set (double value,int index); //Способ лучше: переопределить оператор в классе }; Array::Array (int k1, int k2) { low=k1; hi=k2; value = new double ; } Array::Array (int n) { new Array (0,n-1); } Array::~Array () { delete value; } double Array::get (int index) { return (indexhi ? MAXDOUBLE: value); } void Array::set (double newvalue,int index) { if (index>=low || index<=hi) value=newvalue; } int main () { Array my(-5,5); for (int i=-5; i<=5; i++) { my.set(i+5.,i); cout << "\nKey=" << i << ", value=" << my.get(i); } my.set(my.get(5),-5); cout << "\nItem -5 changed=" << my.get(-5); cout << "\nKey=-10 (bad), value=" << my.get(-10) << endl; system("pause"); return 0; }

Узнав о переопределении операторов, мы сможем обращаться к объектам класса в виде my вместо my.get(5) .

Оператор: (двоеточие) в конструкторе класса означает "список инициализации". Это список, в котором через запятую перечислены пары из имени члена класса и значения, которое необходимо ему присвоить, взятого в круглые скобки. Через список инициализация членов класса происходит перед тем, как выполняется вход в тело конструктора, и выполняется быстрее. К тому же, константные данные можно инициализировать только через список.

Пример 2 . Определим альтернативный класс "Студент" и выполним над его объектами основные описанные в лекции действия.

#include #include #include #include using namespace std; class Student { private: //Обычно атрибут дается основным свойствам char *Name; int Group; public: //Основные методы //Конструкторы: Student (void); //по умолчанию: Student *s=new Student(); Student (char *,int); //фамилия и номер группы: //Student *s2=new Student("Petrov",210); Student (Student &); //копирования ~Student (); //деструктор void setName (char *); //Функции для получения char *getName (void); //и установки свойств void setGroup (int); //с атрибутом private int getGroup (void); //Прочие функции void showStudent (void); }; Student::Student (void) { Name = NULL; Group = 0; } Student::Student (char *newName,int newGroup=0) { Name =(char *)malloc((strlen(newName)+1)*sizeof(char)); if (Name != NULL) { strcpy(Name,newName); Group = 0; } Group = newGroup; } Student::Student (Student &From) { setName (From.Name); setGroup (From.Group); } Student::~Student () { delete Name; } void Student::setName (char *Name) { int n=strlen(Name); if (n>0) { this->Name = new char ; if (this->Name!=NULL) strcpy(this->Name,Name); } } void Student::setGroup (int g) { Group=g; } char * Student::getName () { return Name; } int Student::getGroup () { return Group; } void Student::showStudent (void) { printf ("\n%s,%d\n",Name,Group); } int main () { Student *NoName = new Student (); Student *Ivanov = new Student ("Ivanov"); Ivanov->setGroup(110); Student *Petrov319 = new Student ("Petrov",319); NoName->showStudent(); Ivanov->showStudent(); Petrov319->showStudent(); delete Petrov319; Student Popov("Popov",210); Popov.showStudent(); Popov=*Ivanov; Popov.showStudent(); system("pause"); return 0; }

Немного примеров-рассуждений о конструкторах и деструкторах есть также в .

Программа, разработанная с помощью ОО подхода (объектно-ориентированного), представляет собой действующую модель решаемой задачи.

Терминология

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

От общего к частному: ООП » (инкапсуляция, наследование, полиморфизм, [композиция]) » GRASP » GoF

Преимущества, цели и принципы ОО подхода

  1. Естественность . Построение модели на функциональном уровне, а не на уровне реализации. Использование объектов из конкретной доменной модели (предметной области).
  2. Надежность , сопровождение , расширение . Достигается за счет изолированности компонент системы. Изменение одной компоненты не влияет (не должно влиять) на другие части.

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

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

Три характерных признака эффективной инкапсуляции

  1. Абстракция - процесс упрощения сложной схемы взаимодействия объектов путём изъятия всех несущественных, оставляя только корневые объекты и супер-важные связи. Преимущества : упрощение решения и повторное использование объектов из разных доменных областей. Важно : грань между абстракцией и детализацией очень тонка.
  2. Сокрытие реализации (за интерфейсом) - нужно для защиты объектов от пользователя и наоборот.
  3. Делегирование ответственности .

Правила эффективной абстракции

  1. Нужно рассматривать общий случай, а не конкретный;
  2. Нужно распознать основной принцип, а не только общую последовательность;
  3. Абстрагируясь, нужно вспоминать о решаемой задаче;
  4. Абстракцию можно применять к задачам, которые уже решались вами не менее 3-х раз;
  5. Абстракция не всегда очевидна. А также не возможно написать абстрактную программу на любой случай;

Как эффективно скрывать реализацию и создавать слабосвязанные объекты

  1. Доступ к данным абстрактного типа должен осуществляться только через методы интерфейса (скрываем реализацию);
  2. Исключить возможность бесконтрольного доступа к структурам данных;
  3. Не следует угадывать тип данных, если поведение метода не описано в интерфейсе;
  4. Быть осторожным при написании 2-х тесно связанных типов;

Наследование

Наследование используется для простоты расширения класса. При наследовании нужно соблюдать сигнатуру методов (конструкторы тоже, с версии PHP 5.4). При наследовании допускается понижение строгости при указании области видимости (уровня доступа).

Полиморфизм

Полиморфизм ввели для простого и гибкого изменения программ.

Классы, методы, свойства

Типы классов:

  • Абстрактный;
    • Класс, который содержит по крайней мере один абстрактный метод, должен быть определен как абстрактный;
    • Нельзя создать экземпляр абстрактного класса;
  • Интерфейс;
    • Методы интерфейсов не могут содержать реализацию и должны быть публичными;
    • Класс не может реализовать два интерфейса, содержащих одноименную функцию;
    • Интерфейсы могут наследоваться;
    • Сигнатуры методов в реализующем классе должны точно соответствовать сигнатурам в интерфейсе;

Типы методов:

  • Абстрактный;
    • Не содержат реализации, а требует ее в дочернем классе;
  • Статический;
    • Реализация не переопределяется (в отличие от абстрактных);

Инкапсуляция (программирование)

В языках программирования инкапсуля́ция имеет одно из следующих значений, либо их комбинацию:

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

Инкапсуляция - один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией , полиморфизмом и наследованием).

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

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

  • предельная локализация изменений при необходимости таких изменений,
  • прогнозируемость изменений (какие изменения в коде нужно сделать для заданного изменения функциональности) и прогнозируемость последствий изменений.

Примеры

C++

Class A { public : int a, b; //данные открытого интерфейса int ReturnSomething() ; //метод открытого интерфейса private : int Aa, Ab; //скрытые данные void DoSomething() ; //скрытый метод } ;

Класс А инкапсулирует свойства Aa, Ab и метод DoSomething, представляя внешний интерфейс ReturnSomething, a, b.

C#

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

Class NoEncapsulation { public double Value ; public string ValueString; }

При этом мы можем отдельно изменять как само значение Value , так и его строковое представление, и в некоторый момент может возникнуть их несоответствие (например, в результате исключения). Пример реализации с использованием инкапсуляции:

Class EncapsulationExample { private double valueDouble; private string valueString; public double Value { get { return valueDouble; } set { valueDouble = value ; valueString = value . ToString () ; } } public string ValueString { get { return valueString; } set { double tmp_value = Convert. ToDouble (ValueString) ; //здесь может возникнуть исключение valueDouble = tmp_value; valueString = ValueString; } } }

Здесь доступ к переменным valueDouble и valueString возможен только через свойства Value и ValueString . Если мы попытаемся присвоить свойству ValueString некорректную строку и возникнет исключение в момент конвертации, то внутренние переменные останутся в прежнем, согласованном состоянии, поскольку исключение вызывает выход из процедуры.

Delphi

PHP5

Class A { private $a ; // скрытое свойство private $b ; // скрытое свойство private function DoSomething() //скрытый метод { //actions } public function ReturnSomething() //открытый интерфейс { //actions } } ;

В этом примере закрыты свойства $a и $b для класса A с целью предотвращения повреждения этих свойств другим кодом, которому необходимо предоставить только права на чтение.

Java

Class A { private int a; private int b; private void doSomething() { //скрытый метод //actions } public int returnSomething() { //открытый метод return a; } }

JavaScript

A = function() { // private var _property; var _privateMethod = function() { /* actions */ } // скрытый метод // public this .getProperty = function() { // открытый интерфейс return _property; } this .setProperty = function(value) { // открытый интерфейс _property = value; _privateMethod() ; } }

См. также


Wikimedia Foundation . 2010 .

Смотреть что такое "Инкапсуляция (программирование)" в других словарях:

    - (лат. in в, capsula коробочка; итал. incapsulare закупоривать) 1. Изоляция, закрытие чего либо мешающего, ненужного, вредного с целью исключения отрицательного влияния на окружающее. (Поместить радиоактивные отходы в капсулу, закрыть… … Википедия

    В объектно ориентированном программировании сокрытие внутренней структуры данных и реализации методов объекта от остальной программы. Другим объектам доступен только интерфейс объекта, через который осуществляется все взаимодействие с ним. По… … Финансовый словарь

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

    У этого термина существуют и другие значения, см. Интерфейс (значения). Интерфейс (от лат. inter «между», и face «поверхность») семантическая и синтаксическая конструкция в коде программы, используемая для специфицирования… … Википедия

    У этого термина существуют и другие значения, см. Полиморфизм. Эта статья или раздел нуждается в переработке. Пожалуйста, улучшите статью … Википедия

    У этого термина существуют и другие значения, см. Контейнер. Контейнер в программировании структура (АТД), позволяющая инкапсулировать в себя объекты разных типов. Среди «широких масс» программистов наиболее известны контейнеры, построенные … Википедия

    У этого термина существуют и другие значения, см. Абстракция (значения). Типичное представление архитектуры компьютера в … Википедия

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

Похожие статьи