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

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

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

#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; }

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

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

Основы ООП

Объектно-ориентированное программирование стоит на трех китах своего мироздания:

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

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

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

Суть понятия "инкапсуляция"

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

Модификаторы доступа

Существуют такие модификаторы доступа, которыми, в числе прочих, способна манипуляривать инкапсуляция (программирование Java):

  • public ("паблик" - публичный, открытый, доступ) - общий доступ как для текущих объектов и классов, так и для внешнего мира;
  • private ("прайват" - приватный, частный, скрытый доступ) - закрытый доступ, суть которого полностью противоположна предыдущему. Обеспечивает доступ только из текущего класса;
  • protected ("протектед" - защищенный, полускрытый, доступ) - доступ для текущего класса и производных от него;
  • по умолчанию - неуказанный модификатор доступа подразумевает, что поле/метод видно для всего текущего пакета классов.

В языке C# ("Си Шарп"), помимо указанных (исключая последний), есть еще такие модификаторы:

  • internal ("интернал" - внутренний доступ) - общедоступность в текущем сборе, закрытый доступ по всем остальным случаям;
  • internal protected ("интернал протектед" - внутренний защищенный доступ) - объединение двух модификаторов в один, в котором проявляются свойства обоих из них.

Роль инкапсуляции

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

Объект и инкапсуляция

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

Преимущество инкапсуляции

Инкапсуляция - это способ упростить процесс кодирования. Многочисленные строки кода остаются "за кулисами", а в основном классе работа идет с экземплярами объектов.

Идея защиты данных

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

Единицы инкапсуляции

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

Используется также следующая терминология:

  • члены - это код и данные, входящие в класс;
  • поля, или переменные экземпляра - так называются данные, которые определяет класс;
  • функции-члены - в них содержится сам код. Функции-члены - общее название. Частный случай - методы.

Инкапсуляция на конкретном примере

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

* Примечание:

description - это описание метода/свойства/переменной, то есть комментирование того, что, собственно, происходит в программе. Продемонстрировано при помощи открывающихся/закрывающихся тегов

using System;

namespace OOPLibrary.Auto

/// Данный класс предназначен для того, чтобы описывать свойства и действия автомобиля

public class Auto

/// Переменная, созданная для того, чтобы в нее записывать, сколько автомобилю лет, так как внешнее вмешательство в это свойство разработчик считает лишним

/// оно помечается модификатором private, то есть закрытый, частный доступ (см. описание выше).

private int _age;

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

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

private bool _isMoving;

/// В данной строковой переменной должна содержаться информация о цвете автомобиля. Он может подвергаться изменениям со стороны внешнего воздействия

/// потому для Цвета выбран модификатор открытого доступа "паблик".

public string Color;

/// В данном конкретном случае допускаем, что имя автомобиля тоже можно менять

/// присваивается модификатор паблик (открытый доступ для всех, вне зависимости от класса или сборки).

public string Name;

/// Открывается конструктор класса и все свойства, выраженные переменными и заданные чуть ранее, получают свои значения

public Auto()

_age = 5;

_isMoving = false;

Color = "Фиолетовый";

Name = "Шкода Октавиа";

/// Метод реализует возврат значения возраста авто. Зачем это необходимо?

/// закрытый модификатор доступа не делает его возможным для изменения клиентом.

/// Возвращает возраст автомобиля.

public string GetAge()

return "На данный момент выбранной машине" + _age + " лет.";

/// Если автомобиль не движется, этот метод реализует старт движения. Выполняется проверка переменной, обознающей, в каком состоянии находится автомобиль (едет он или нет), и, в зависимости от результатов, выполняется соответствующее действие/выводится соответствующее сообщение.

public void Start()

if (_isMoving)

Console.WriteLine("Движение уже было начато");

_isMoving = true;

Console.WriteLine("На старт, внимание.. Вперед! Поехали!");

/// Если движение было начато, то этот метод его останавливает. Та же программная логика, что и в предыдущем рассмотренном случае.

public void Stop()

if (_isMoving)

_isMoving = false;

Console.WriteLine("Стоп, машина");

Console.WriteLine("Ошибка. Автомобиль и так стоит на месте, не движется");

/// Осуществляется поворот налево, если имеет место движения автомобиля

public void MoveLeft()

if (_isMoving)

Console.WriteLine("Осуществлен поворот налево");

Console.WriteLine("Ошибка. Автомобиль стоит на месте. Функция поворота на данный момент недоступна");

/// Аналогичный метод с поворотом направо

public void MoveRight()

if (_isMoving)

Console.WriteLine("Поворот направо был осуществлен успешно");

Console.WriteLine("Ошибка. Автомобиль еще не двигался с места. Поворот направо является на данный момент действием, невозможным для выполнения.");

Но в действительности обширно встречается и в других (см. подтипизация на записях и полиморфизм записей и вариантов). В ООП инкапсуляция тесно связана с принципом абстракции данных (не путать с абстрактными типами данных , реализации которых предоставляют возможность инкапсуляции, но имеют иную природу). Это, в частности, приводит к другому распространённому заблуждению - рассмотрению инкапсуляции неотрывно от сокрытия . В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk , Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности сокрытия в принципе. Другие (Standard ML , OCaml) жёстко разделяют эти понятия как ортогональные и предоставляют их в семантически различном виде (см. сокрытие в языке модулей ML).

Энциклопедичный YouTube

    1 / 3

    ✪ Что такое геттеры и сеттеры для класса. Методы get и set. Инкапсуляция это. Пример. C++ Урок #76

    ✪ Python Tutorial - 39 - Classes Encapsulation

    ✪ OCJA (1Z0 - 808) || Object Oriented Programming: Encapsulation

    Субтитры

Подробности

PHP5

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

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

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

Есть в английском языке прекрасная фраза “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); } }

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

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

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